VBA Userform dropdown menu execution - vba

I currently have this code which allows me to launch the userform, input the an item in the text box, auto populate the date, and select from a drop down menu
then paste that information into a new row.
The cbm (combo-box) item draws its values from a separate dynamically expanding table and is a drop down menu on the userform. The date is auto populated based on todays date and the text box is draws its value from whatever the user enters.
Private Sub btnSubmit_Click()
Dim ssheet As Worksheet
Set ssheet = ThisWorkbook.Sheets("InputSheet")
nr = ssheet.Cells(Rows.Count, 1).End(xlUp).Row + 1
ssheet.Cells(nr, 3) = CDate(Me.tbDate)
ssheet.Cells(nr, 2) = Me.cmblistitem
ssheet.Cells(nr, 1) = Me.tbTicker
My goal here is, depending on what list item is selected I want the name of that item to be pasted in a column that corresponds to that item. i.e if the user selects "apples" and the 3rd column is the "apple" column, I want it to paste in that location.
I am assuming this has to be down with some type of "if" statement.
Any help is appreciated.
Here is pic of my worksheet

supposing I correctly made my guessings, try this code
Option Explicit
Private Sub btnSubmit_Click()
Dim f As Range
If Me.cmblistitem.ListIndex = -1 Then Exit Sub '<--| exit if no itemlist has been selected
If Len(Me.tbTicker) = 0 Then Exit Sub '<--| exit if no item has been input
With ThisWorkbook.Sheets("InputSheet")
Set f = .Rows(1).Find(what:=Me.cmblistitem.Value, lookat:=xlWhole, LookIn:=xlValues, MatchCase:=False) '<--| look for proper column header
If f Is Nothing Then Exit Sub '<--| if no header found then exit
With .Cells(.Cells(Rows.Count, "A").End(xlUp).Row + 1, f.Column) '<--| refer to header column cell corresponding to the first empty one in column "A"
.Resize(, 3) = Array(Me.tbTicker.Value, Me.cmblistitem.Value, CDate(Me.tbDate)) '<--| write in one shot
End With
End With
End Sub
it's commented so you can easily change columns references as per your needs
BTW as for the combobox filling you may want to adopt the following code:
Dim cell As Range
With Me
For Each cell In [myName]
.cmblistitem.AddItem cell
Next cell
End With
which is optimized having referenced Me once before entering the loop so that it's being kept throughout it without further memory accesses

Related

Userform Listbox selection & Value update based on column

I am creating an userform where user can select existing materials, and input stock quantity.
Process:
Userform open
User will select which stock column to input values (Stock1 ~ Stock10).
User will select a material on the listbox.
User can input stock quantity & select where the stock is from.
When updating, the stock quantity will be added on the selected Stock column.
I got to the part on populating the listbox; and label showing selected material in the listbox to show name & color.
Stuck at:
I am stuck on how to make:
selected stock column to be input column.
*this is the part which user will use combobox to select available stock column on the left chart.
selected listbox material to be input row.
Example:
Below are the images of the example form & userform.
Left image is where the stock data will be input (Stock1 ~ Stock3).
Right image is the userform; user will follow the process above; and it will be entered into the left chart.
Thank you all in an advance.
If I understand you correctly ....
From the animation above, when two condition is met : the ListBox for name is selected (based on what user choose) AND the ComboBox for Stock is selected (based on what user choose), then :
the cell where the user want to update the qty is activated
the textbox for QTY is filled with the active cell value
If the user change/update the value in the textbox for QTY then he click UPDATE button, the activecell value will be the textbox value.
So if your case is similar with the animation above, then maybe you want to have a look the code below which maybe you can implement it to your case :
Private Sub UserForm_Initialize()
Set rg = Range("A2", Range("A" & Rows.Count).End(xlUp))
Set rg = rg.Resize(rg.Rows.Count, 3)
'populate the ListBox for id, name and color
With ListNameColor
.ColumnCount = 3
.ColumnWidths = "0,40,40"
.List = rg.Value
End With
'populate the combobox for stock
For i = 1 To 3: cbStock.AddItem "Stock" & i: Next
End Sub
Private Sub cbStock_Change()
If ListNameColor.ListIndex <> -1 Then Call PopQty
End Sub
Private Sub ListNameColor_Click()
If cbStock.ListIndex <> -1 Then Call PopQty
End Sub
Sub PopQty()
'get the row and column as r and c variable
r = Range("A2", Range("A" & Rows.Count).End(xlUp)).Find(ListNameColor.Value).Row
c = Rows(1).Find(cbStock.Value, lookat:=xlWhole).Column
Cells(r, c).Activate
tbQty.Value = ActiveCell.Value
End Sub
Private Sub btUpdate_Click()
ActiveCell.Value = tbQty.Value
End Sub
Private Sub btCancel_Click()
Unload Me
End Sub
Please note that the item name in the combobox for stock must be exactly the same as the header name for the stock. For example : if in the header for stock the name is : STOCK-01, STOCK-02, STOCK-03, and so on, then when populating the combobox for stock must also with the same text.
Debugging the PopQty sub :
Sub PopQty()
Dim r As Integer: Dim c As Integer
'debugging
SelectedNameID = ListNameColor.Value 'is the SelectedNameID value correct in the Locals window?
'for example if the selected name is "bbb", then the value of SelectedNameID must be 2.
Set rgData = Range("A2", Range("A" & Rows.Count).End(xlUp))
rgData.Select 'is the selection correct ?
'it should select the "#" column from row 2 to the last row with number.
Set foundCell = rgData.Find(SelectedNameID)
'is the Locals window shows that the foundCell variable is NOT nothing ?
'if the foundCell variable in Locals window is not showing "Nothing" ....
foundCellRow = foundCell.Row 'is the foundCellRow value correct in the Locals window ?
'for example if the selected name is "bbb", then the foundCellRow value must be 3.
'get the row and column as r and c variable
r = Range("A2", Range("A" & Rows.Count).End(xlUp)).Find(ListNameColor.Value).Row
c = Rows(1).Find(cbStock.Value, lookat:=xlWhole).Column
Cells(r, c).Activate
tbQty.Value = ActiveCell.Value
End Sub

How do i filter a range in another sheet with VBA without activating the sheet

Good day,
I am having problems with Set Ranges and it has been quite frustrating when using set ranges from non-active sheets.
The problem is:
I have a sheet called "Dashboard". In this sheet i have a Listbox that when selected will filter values (based on listbox.column value) on a Table in another sheet called "Budget". However, i get Error 1004 (Autofilter method of Range class failed), after closing the error it filters the range. So it seems it works somehow, however it gives me error.
The code below is the one i'm using to filter the range. It is inserted in the "Dashboard" Sheet object.
Private Sub DashboardBudgetlst_Change()
Dim rng As Range
Dim i As Integer
i = Me.DashboardBudgetlst.ListIndex
If i >= 0 Then
If Me.DashboardBudgetlst.Selected(i) And Me.DashboardBudgetlst.Column(0, i) <> "" Then
Set rng = Budget.Range("B1:E" & lrow(Budget, "A"))
rng.AutoFilter 1, Me.DashboardBudgetlst.Column(1, i)
Set rng = Nothing
End If
End If
End Sub
The macro will filter a range that is used for a chart, therefore will filter the values of my chart. Also i don't want to use pivot tables as it is very slow.
Further exploring the question. How can i use Ranges from one Worksheet that are Set in another Worksheet without having to Activate the Sheet of that range? (most of the time i have to do Sheet.Activate before using the Set range for that sheet).
Would you guys know the workaround and why there is this problem with Set Ranges?
I know there are similar questions about ranges, but none with the same specifications.
Additional Information (Edit):
1- Error is on line:
rng.AutoFilter 1, Me.DashboardBudgetlst.Column(1, i)
2- Listbox index >= 0 to ensure that listbox is not empty and there's an item selected. When a listbox is empty the listindex = -1.
3- lrow(Budget, "A") calls the following function to get the last row in the specified sheet:
Function lrow(SH As Worksheet, col As String)
lrow = SH.Cells(Rows.Count, col).End(xlUp).Row
End Function
4- With msgbox rng.address just before the error line, i receive $B$1:$E$5 as the address.
5- I did a temporary workaround using
On Error Resume Next
6- Value for Me.DashboardBudgetlst.Column(1, i) is a keyword to be filtered and depends on the selection. The listbox is fed with the same range that i am filtering. So i am selecting the column "1" from the list which is under the header "Item". When i select something from the listbox i want it to filter by that Budget Item, sometimes can be "Accommodation" or anything else i have there.
7- Debug.Print on :
Debug.Print rng.AutoFilter; 1, Me.DashboardBudgetlst.Column(1, i)
Selected on Travel Expenses in listbox Returns on Immediate Window:
True 1 Travel Expenses
8- Some Screenshots:
Listbox in Dashboard Sheet (Excel View)
Range in Budget Sheet (Excel View)
Objects being used (VBA view)
It works as after i closed the error the filter would apply. However i would like to know if there's another workaround and i'm not sure about using "On Error Resume Next" (Is it bad for your code?)
I was able to duplicate the error
Depending on the number of items select in the ListBox, the issue seems to be that there are multiple _Change events being triggered
I was able to stop the error by using an event flag
Option Explicit
Private Sub DashboardBudgetlst_Change()
Dim rng As Range, i As Long, lstItm As String, crit As String, startIndex As Long
If Application.EnableEvents = False Then Exit Sub 'If flag is Off exit Sub
Application.EnableEvents = False 'Turn flag Off
With Me.DashboardBudgetlst
i = .ListIndex
If i >= 0 Then
If .Selected(i) And .Column(0, i) <> "" Then
Set rng = Budget.Range("B1:E5") ' & lrow(Budget, "A"))
rng.AutoFilter 1, .Value
End If
End If
End With
Application.EnableEvents = True 'Turn flag back On
End Sub

VBA Macro to Autofill a cell

I am trying to find a simple autofill solution to copy the formula in cell C3 into C2 after a new line has been inserted. Here is what I have that I thought would work:
Sub AutoFill()
Set SourceRange = Worksheets("Sheet 1").Range("C3")
Set fillRange = Worksheets("Sheet 1").Range("C2")
SourceRange.AutoFill Destination:=fillRange
End Sub
Basically, in C3 (and every cell in column C after row 3) has a average function that takes the previous 20 days and creates an average. I am trying to get the macro to input that formula everytime a new row gets put in (I have the code to input the new row it just won't apply the function after the new row comes in)
The cells to be filled. The destination must include the source range.
As quoted from MSDN.
So try:
Set SourceRange = Worksheets("Sheet 1").Range("C3")
Set fillRange = Worksheets("Sheet 1").Range("C2")
SourceRange.AutoFill Destination:=Range(fillRange, SourceRange)
Another note is to use Named ranges if you are inserting rows in between.
Other ways to get formulas with updated cell references
Option Explicit
Public Sub getFormula()
With Sheet1
.Range("C3").Copy
.Range("C2").PasteSpecial xlPasteFormulas
If .ListObjects.Count = 1 Then
With .ListObjects(1) 'for tables
.Cells(2, 3).Formula = .Cells(3, 3).Formula
End With
End If
End With
End Sub
.
Also, you should not use the name of VBA method as a sub name (AutoFill)
A fast way to determine VBA keywords: click on the sub name and press F1
If the help page shows Keyword Not Found your sub name should be Ok

Quicker way to filter out data based on a particular value

I am working with a workbook that currently has 3 sheets. The first sheet is an overview where the filtered data will appear. Cell D11 has the color that I am looking for. Upon entering the color cells F3:I27 Populate with information like color, shape, number and animal.
C2C-Tracker2
I would use a Pivot Table for this, however, I have another set of data in K3:M27. This data is pulled from another sheet within the workbook with a similar function.
The formula that I am using is:
=IFERROR(INDEX(cases!A:A,SMALL(IF(EXACT($D$3,cases!$C:$C),ROW(cases!$C:$C)-ROW($F$1)+1),ROW(1:1))),"")
Of course it is entered using CTRL + SHIFT + ENTER for it to work properly.
I tried using a VBA Macro that I pulled from the video below:
Excel VBA Loop to Find Records Matching Search Criteria
So many array formulas can really make your workbook very slow.
Here is a code to populate Dataset1 using arrays. It runs in less than a second.
Hope this gets you started. I have commented the code but if you still have a problem understanding, just post back :)
Sub Sample()
Dim DSOne() As String
Dim tmpAr As Variant
Dim wsCas As Worksheet: Set wsCas = ThisWorkbook.Sheets("Cases")
Dim wsMain As Worksheet: Set wsMain = ThisWorkbook.Sheets("Sheet1")
Dim lRow As Long, i As Long, j As Long
'~~> Check if user entered a color
If wsMain.Range("D3").Value = "" Then
MsgBox "Please enter a color first", vbCritical, "Missing Color"
Exit Sub
End If
'~~> Clear data for input in main sheet
wsMain.Range("F3:F" & wsMain.Rows.Count).ClearContents
'~~> Get last row of Sheet Cases
lRow = wsCas.Range("A" & wsCas.Rows.Count).End(xlUp).Row
With wsCas
'~~> Get count of cells which have that color
i = Application.WorksheetFunction.CountIf(.Columns(3), wsMain.Range("D3").Value)
'~~> Check if there is any color
If i > 0 Then
'~~> Define your array to hold those values
ReDim DSOne(1 To i, 1 To 4)
'~~> Store the Sheet Cases data in the array
tmpAr = .Range("A1:D" & lRow).Value
j = 1
'~~> Loop through the array to find the matches
For i = LBound(tmpAr) To UBound(tmpAr)
If tmpAr(i, 3) = wsMain.Range("D3").Value Then
DSOne(j, 1) = tmpAr(i, 1)
DSOne(j, 2) = tmpAr(i, 2)
DSOne(j, 3) = tmpAr(i, 3)
DSOne(j, 4) = tmpAr(i, 4)
j = j + 1
End If
Next i
'~~> write to the main sheet in 1 Go!
wsMain.Range("F3").Resize(UBound(DSOne), 4).Value = DSOne
End If
End With
End Sub
Screenshot:
Using the above approach now populate Dataset2 :)

update cell and paste it to another cell vba

I am quite new in excel vba and I would really appreciate if you can assist me.
The thing is that I have cell which updates each minute because it is linked with a function to Blomberg. The thing is that I want that each time cell updates excel copies it and pastes to another, new cell that i can observe the intra day changes.
I have come up with some codes but I can copy and paste only to one, similar cell.It looks like following:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Not Intersect(Target, Range("E4")) Is Nothing Then
Range("E4").Copy
Range("E4").PasteSpecial xlPasteValues
End If
End Sub
Any help would be highly appreciated.
If I understand your problem correctly you want to copy the value to a new cell, for logging purposes? What I would do in this case is have another sheet for logging the values named "logger_sheet" I paste a value in cell a1 when the blomberg cell updates, copy the value into my logger_sheet cell a2 when it changes copy it to a3 then a4 etc.
Here is your updated code. It assumes you have a sheet named "logger_sheet" (if you dont have one, create it) to store all the previous values. When the blomberg cell updates, it copies the value and pastes it to the next avaliable logging_sheet cell. I have developed a function that finds the last used row in a specified sheet and column. Try it out
Also there is a line you can uncomment if you want to prevent excel from flashing, I labeled it in the code
Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
target_cell = "E4"
col_to_log_data = "A"
logging_Sheet = "logger_sheet"
If Not Intersect(Target, Range("E4")) Is Nothing Then
'uncomment this line to stop the "flashing"
'Application.ScreenUpdating = False
'gets the name of the current sheet
data_sheet = Range(target_cell).Parent.Name
Range(target_cell).Select
Selection.Copy
'gets the next free row from column a of the logging sheet (the next free row is
'the last used row + 1)
next_free_row = GetLastRowByColumn(CStr(col_to_log_data), CStr(logging_Sheet)) + 1
'pastes the value
Sheets(logging_Sheet).Range(col_to_log_data & CStr(next_free_row)).PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
'switches back to the data sheet
Sheets(data_sheet).Select
'make sure you turn screen updating on (if it was never off it still works)
Application.ScreenUpdating = True
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'this finds the last row in a specific column
'PARAMS: col_to_check, the clumn we want the last row of
' Opt: sheet_name, the sheet you want to check last row of
' default is current sheet if not specified
'RETURN: the last row number used in the sheet
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetLastRowByColumn(col_to_check As String, Optional sheet_name As String)
'gets current sheet name
the_current_sheet = ActiveSheet.Name
'if the user didnt' specify a sheet use the current one
If (Len(sheet_name) = 0) Then
sheet_name = the_current_sheet
End If
'gets last row
GetLastRowByColumn = Sheets(sheet_name).Range(col_to_check & "65536").End(xlUp).Row
'returns to original sheet
Sheets(the_current_sheet).Select
End Function
If my answer solves your problem please mark it as the solution
How about this? It will transfer E4 to Sheet2 in a new row each time E4 changes.
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Target.Address = "$E$4" Then Sheets("Sheet2").Cells(Rows.Count, "F").End(xlUp).Offset(1) = Target
End Sub
I'm making the assumption you want to log every change of values.
I would advise to keep a log in a separate sheet. Let's call it LogSheet.
Sub WriteLog(ByRef r As range)
Dim Lastrow as integer
With ThisWorkBook.WorkSheets("LogSheet")
LastRow = .Cells(.Rows.Count,"A").End(XlUp).Row
.Range("A" & LastRow + 1).Value = Now & " - " & r.Value
End With
End Sub
This sub will basically write all changes in column A of our log sheet with a timestamp!
Now, we need to make changes to your code in order to tell, to make logs whenever there is a change. To do so, we're going to make a call to our function and tell to copy the content of the range("E4") (The one that gets updated all the time)
If Not Intersect(Target, Range("E4")) Is Nothing Then
'add this line
WriteLog(ActiveSheet.Range("E4"))
Try it now.