I am looking for a little assistance with the VBA code in the workbook I have been working on. There is a userform with dependent dropdowns that pulls their values from "Master Sheet" in the workbook. The drop downs are functioning fine. However I have two roadblocks that I have now been able to get past. The first, The dropdowns allow the selection of "Category, Make, Model, and Add To". In the Master Sheet, "Category, Make, Model" Run from Columns A:C. Columns D:G have the equipment's, "Weight, Length, Width, Depth" information. I have not been able to have the information from columns A:F be copied based off the model selection. I have been trying have it paste in a test are for functionality with no luck. However once that would be functioning the "Add To" combo box selection in the user form would specify the range in the ECA worksheet to place that data. In the combo box selection, selecting "Keep" would place that information in range S3:Y16, "Remove" would be range S18:Y32, and "Final" would be range S35:Y47. Since numerous pieces of equipment would be added into each section when adding a piece of equipment it would place that entry in the next empty row of that range.
Link To Workbook
Picture Of Worksheets
Master Sheet
ECA Sheet
Dependent Drop Down Code
Private Sub cmbAddTo_Click()
'code needed to copy and add to selected range
End Sub
Private Sub cmdCancel_Click()
frmUser.Hide
End Sub
Private Sub UserForm_Initialize()
cmbCategory.RowSource = DynamicList(1, Null, 1, "Master Sheet", "Drop Down")
End Sub
Private Sub cmbCategory_Change()
cmbMake.RowSource = DynamicList(1, cmbCategory.Value, 2, "Master Sheet", "Drop Down")
End Sub
Private Sub cmbMake_Change()
cmbModel.RowSource = DynamicList(2, cmbMake.Value, 3, "Master Sheet", "Drop Down")
End Sub
Here is how I did it:
Function wsECA: Refers to the ECA worksheet
Enum SectonType: Numbers the sections
Function ECASection: Returns the range of a section
Function ECANewRow: Returns the range of the next empty row
Sub AddECANewRow: Add variable number of values to the new section row
Code
Public Enum SectonType
stExistingToRemain = 1
stRemoving
stFinal
End Enum
Public Sub AddECANewRow(SectionNumer As SectonType, ParamArray Values() As Variant)
Dim NewRow As Range
Set NewRow = ECANewRow(SectionNumer)
NewRow.Resize(1, UBound(Values) + 1).Value = Values
End Sub
Public Function wsECA() As Worksheet
Set wsECA = ThisWorkbook.Worksheets("ECA")
End Function
Public Function ECANewRow(ByVal SectionNumer As SectonType) As Range
Const LastColumn = 10
Dim Section As Range
Set Section = ECASection(SectionNumer)
Dim LastUsedRow As Long
Dim ColumnLastUsedRow As Long
For c = 2 To LastColumn
With Section.Columns(c)
ColumnLastUsedRow = .Cells(.Rows.Count, 1).End(xlUp).Row
If ColumnLastUsedRow > LastUsedRow Then LastUsedRow = ColumnLastUsedRow
End With
Next
LastUsedRow = LastUsedRow - Section.Row + 1
Set ECANewRow = Section.Cells(LastUsedRow + 1, 2).Resize(1, LastColumn - 1)
End Function
Function ECASection(ByVal SectionNumer As SectonType) As Range
Dim Target As Range
With wsECA
Set Target = Range("P2", .Cells(.Rows.Count, "P").End(xlUp))
End With
Dim MergedArea As Range
Dim Cell As Range
For Each Cell In Target
If Cell.MergeArea.Rows.Count > 1 Then
If MergedArea Is Nothing Then
Set MergedArea = Cell.MergeArea
SectionNumer = SectionNumer - 1
ElseIf MergedArea.Address <> Cell.MergeArea.Address Then
Set MergedArea = Cell.MergeArea
SectionNumer = SectionNumer - 1
End If
If SectionNumer = 0 Then Exit For
End If
Next
If Not MergedArea Is Nothing Then
Set ECASection = Range(MergedArea, MergedArea.EntireRow.Columns("AA"))
End If
End Function
Test
Application.Goto ECANewRow(stExistingToRemain), True
AddECANewRow stExistingToRemain,"Remain" ,3,,"Ford", "Mustang"
Application.Goto ECANewRow(stRemoving), True
AddECANewRow stFinal,"Removing" ,3,,"Chevy", "Tahoe"
Application.Goto ECANewRow(stFinal), True
AddECANewRow stRemoving,"Final" ,3,,"Dodge", "Journey"
Related
I have a basic understanding of Excel formulas but zero experience with VBA. I'm building a basic spreadsheet to keep track of people attendance. This spreadsheet is gonna be completed daily by people with even less understanding than me.
Column B is data validated from a DB table in another sheet. Columns D, E, F, G pull data from the same DB table using XLOOKUP based on the name on column B.
PROBLEM: If something in the DB table changes, like the account number of a person, every past attendance of that person is updated.
Example
I need a simple way to "lock" the data in cells that have been filled, although they should accept manual editing.
So far I'm tryng to put a button somewhere on the sheet that deletes all formulas but keeps tha value of the cells. I did some googling and got this:
Sub Remove_Formulas_from_Selected_Range()
Dim Rng As Range
Set Rng = Selection
Rng.Copy
Rng.PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Application.CutCopyMode = False
End Sub
But I don't know how to adapt the button so it checks every row of the table, and if the Column B of that row is NOT emtpy (meaning the row is filled with a person's data) only THEN deletes the formulas of that row and keeps the values.
Your file must be an .xlsm file. Add an ActiveX button. Double click on it.
Inside the created button_click() sub add one line: call doTheJob
-After paste code below:
Private Sub doTheJob()
Dim rng As Range, rw As Long, c As Long
If TypeName(Selection) = "Range" Then
If MsgBox("Change formulas with Values in selected range?", vbDefaultButton2 + vbExclamation + vbYesNo) = vbYes Then
Set rng = Selection
rw = rng.Rows.CountLarge
Application.ScreenUpdating = False
For c = 1 To rw
If Trim(rng.Cells(c, 2).Value) <> "" Then
rng.Rows(c).Copy
rng.Rows(c).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
End If
Next
Application.CutCopyMode = False
End If
End If
End Sub
Before click the button you must select the range you interest to replace the formulas with the values.
This sub is for a sheet's module and one "fixed" table name
Private Sub doTheJob()
Dim rng As Range, rw As Long, c As Long
If MsgBox("Change formulas with Values in selected range?", vbDefaultButton2 + vbExclamation + vbYesNo) = vbYes Then
Set rng = Me.ListObjects("NameOfTable").Range
rw = rng.Rows.CountLarge
Application.ScreenUpdating = False
For c = 1 To rw
If Trim(rng.Cells(c, 2).Value) <> "" Then
rng.Rows(c).Copy
rng.Rows(c).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
End If
Next
Application.CutCopyMode = False
End If
End Sub
Use this code when the range is a Table. Modify the "NameOfTable" to the real name of your table.
This sub is for the Workbook an have to copy in a module inside folder "Modules". In sheets you wand to call this, add an ActiceX button and call the sub like below:
Public Sub doTheJob(ws As Worksheet, tablename As String)
Dim rng As Range, rw As Long, c As Long
If (Not ws Is Nothing) And tablename <> "" Then
Set rng = ws.ListObjects(tablename).Range
Else
MsgBox ("call the doTheJob with prameters a worksheet and a table name")
Exit Sub
End If
If Not rng Is Nothing Then
If MsgBox("Change formulas with Values in range " & tablename & " ?", vbDefaultButton2 + vbExclamation + vbYesNo) = vbYes Then
rw = rng.Rows.CountLarge
Application.ScreenUpdating = False
For c = 1 To rw
If Trim(rng.Cells(c, 2).Value) <> "" Then
rng.Rows(c).Copy
rng.Rows(c).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
End If
Next
Application.CutCopyMode = False
End If
Else
MsgBox ("doTheJob> Invalid table name")
End If
End Sub
'This sub is in sheets module
Private Sub CommandButton1_Click()
Call doTheJob(Me, Range("TABLE_NAMES").Value)
End Sub
I have an Excel worksheet used for product data entry.
Each individual product uses 16 rows.
Cells contain formulas, dropdown boxes that validate from another workbook and ListBoxes for multiple selection of items such as colours.
I need to copy the 16 rows to use as a template for a new product, and paste it below the previous, repeating this for each new product.
The dropdown boxes copy down fine as they are at cell level and allow each new product to have its own dropbox selection.
The issue is with copying/pasting the ListBoxes. As they are not connected to the cells, and become copies with new names, the code used for opening/closing them and outputting selections to a cell no longer works. Even if they remained with the same name they would only be relevant for the first product and not allow for individual data entry for each new product.
Here is the code used to control the ListBoxes
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
With ActiveSheet.ListBox1
If Target(1).Address = "$A$2" And .Visible = False Then
.Visible = True
Application.EnableEvents = False
[A3].Select
Application.EnableEvents = True
Else
.Visible = False
For I = 0 To .ListCount - 1
If .Selected(I) Then txt = txt & ", " & .List(I)
Next
[A2] = Mid(txt, 2) 'remove first comma and output to A2 cell
End If
End With
End Sub
ListBoxes seemed like a good solution for multiple selections while perfecting the spreadsheet for 1 dummy product, however I don't see how they could work in this application for each new product. Is there any other way to achieve this? Could a dropdown box be altered to have checkboxes for multiple selections as does a ListBox?
I have seen dropboxes used for multiple selections as per the method shown here:
How to Make Multiple Selections in a Drop Down List in Excel
However there is no way to see which items are selected, other than seeing the output in the comma separated list, which could become quite a long list. The selections needs to be visible in the list itself with checkboxes.
Any suggestions would be much appreciated.
The solution I came up with does change the look of your listbox somewhat. You were using an ActiveX listbox that gives you the nice-looking checkboxes for your multiselect. The problem I had was assigning a macro to a listbox to catch the OnAction event (each time you click on a listbox item). My solution below works with Forms Listboxes. There are a few parts to the solution.
You stated a requirement that when the user selects a cell in the "Colours" column, a listbox pops up and presents the list of color options. To achieve this, I used the Worksheet_SelectionChange event in the worksheet module:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
Dim colourRange As Range
Set colourRange = ColourArea(ActiveSheet)
If colourRange Is Nothing Then Exit Sub
If Not Intersect(Target, colourRange) Is Nothing Then
CreateColourPopUp Target
Else
DeleteAllPopUps Target
End If
End Sub
What's important to note here is that the popup is created anytime the user selects a cell in the "Colours" column and whenever a cell is selected outside of that range, the popup is deleted. The ColourArea is defined in a separate module (with all the other code in this answer Module1):
Public Function ColourArea(ByRef ws As Worksheet) As Range
'--- returns a range for the colour selections for all the products
' currently active on the worksheet
Const COLOUR_COL As Long = 6
Dim lastRow As Long
With ws
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row - 1
Set ColourArea = .Cells(2, COLOUR_COL).Resize(lastRow, 1)
End With
End Function
I coded this as separate from the Worksheet_SelectionChange because you may now, or in the future, use some other way to determine what range on the worksheet is used for your colors.
Creating the popup then happens in the code here, where the listbox is created in the cell just below the selected cell. Note again that determining the range that contains the list of colors is encapsulated in a function.
Public Function ColourListArea() As Range
Set ColourListArea = Sheet1.Range("M1:M11")
End Function
Public Sub DeleteAllPopUps(ByRef selectedCell As Range)
Dim colourBox As ListBox
For Each colourBox In selectedCell.Parent.ListBoxes
colourBox.Delete
Next colourBox
End Sub
Public Sub CreateColourPopUp(ByRef selectedCell As Range)
Set colourSelectCell = selectedCell
Dim popUpCell As Range
Set popUpCell = colourSelectCell.OFFSET(1, 0)
DeleteAllPopUps selectedCell
'--- now create the one we need, right below the selected cell
Const POPUP_WIDTH As Double = 75
Const POPUP_HEIGHT As Double = 110
Const OFFSET As Double = 5#
Dim colourBox As ListBox
Set colourBox = ActiveSheet.ListBoxes.Add(popUpCell.left + OFFSET, _
popUpCell.top + OFFSET, _
POPUP_WIDTH, _
POPUP_HEIGHT)
With colourBox
.ListFillRange = ColourListArea().Address
.LinkedCell = ""
.MultiSelect = xlSimple
.Display3DShading = True
.OnAction = "Module1.ColourBoxClick"
End With
'--- is there an existing list of colours selected?
Dim selectedColours() As String
selectedColours = Split(colourSelectCell.Value, ",")
Dim colour As Variant
For Each colour In selectedColours
Dim i As Long
For i = 1 To colourBox.ListCount
If colourBox.List(i) = colour Then
colourBox.Selected(i) = True
Exit For
End If
Next i
Next colour
End Sub
The variable colourSelectCell is declared at the module-global level (see the full module at the end of this post). You will likely have to manually adjust the width and height constants as needed.
Finally, the OnAction routine is defined as:
Public Sub ColourBoxClick()
Dim colourBoxName As String
colourBoxName = Application.Caller
Dim colourBox As ListBox
Set colourBox = ActiveSheet.ListBoxes(colourBoxName)
Dim colourList As String
Dim i As Long
For i = 1 To colourBox.ListCount
If colourBox.Selected(i) Then
colourList = colourList & colourBox.List(i) & ","
End If
Next i
If Len(colourList) > 0 Then
colourList = Left$(colourList, Len(colourList) - 1)
End If
colourSelectCell.Value = colourList
End Sub
This is where the global colourSelectCell is used.
The entire Module1 is
Option Explicit
Private colourSelectCell As Range
Public Function ColourArea(ByRef ws As Worksheet) As Range
Const COLOUR_COL As Long = 6
'--- returns a range for the colour selections for all the products
' currently active on the worksheet
Dim lastRow As Long
With ws
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row - 1
If lastRow = 0 Then
Set ColourArea = Nothing
Else
Set ColourArea = .Cells(2, COLOUR_COL).Resize(lastRow, 1)
End With
End Function
Public Sub ColourBoxClick()
Dim colourBoxName As String
colourBoxName = Application.Caller
Dim colourBox As ListBox
Set colourBox = ActiveSheet.ListBoxes(colourBoxName)
Dim colourList As String
Dim i As Long
For i = 1 To colourBox.ListCount
If colourBox.Selected(i) Then
colourList = colourList & colourBox.List(i) & ","
End If
Next i
If Len(colourList) > 0 Then
colourList = Left$(colourList, Len(colourList) - 1)
End If
colourSelectCell.Value = colourList
End Sub
Public Function ColourListArea() As Range
Set ColourListArea = Sheet1.Range("M1:M11")
End Function
Public Sub DeleteAllPopUps(ByRef selectedCell As Range)
Dim colourBox As ListBox
For Each colourBox In selectedCell.Parent.ListBoxes
colourBox.Delete
Next colourBox
End Sub
Public Sub CreateColourPopUp(ByRef selectedCell As Range)
Set colourSelectCell = selectedCell
Dim popUpCell As Range
Set popUpCell = colourSelectCell.OFFSET(1, 0)
DeleteAllPopUps selectedCell
'--- now create the one we need, right below the selected cell
Const POPUP_WIDTH As Double = 75
Const POPUP_HEIGHT As Double = 110
Const OFFSET As Double = 5#
Dim colourBox As ListBox
Set colourBox = ActiveSheet.ListBoxes.Add(popUpCell.left + OFFSET, _
popUpCell.top + OFFSET, _
POPUP_WIDTH, _
POPUP_HEIGHT)
With colourBox
.ListFillRange = ColourListArea().Address
.LinkedCell = ""
.MultiSelect = xlSimple
.Display3DShading = True
.OnAction = "Module1.ColourBoxClick"
End With
'--- is there an existing list of colours selected?
Dim selectedColours() As String
selectedColours = Split(colourSelectCell.Value, ",")
Dim colour As Variant
For Each colour In selectedColours
Dim i As Long
For i = 1 To colourBox.ListCount
If colourBox.List(i) = colour Then
colourBox.Selected(i) = True
Exit For
End If
Next i
Next colour
End Sub
EDIT: here's an example of returned a discontiguous range of cells to
allow the popups. ALSO -- add the line If Target.Cells.Count > 1 Then Exit Sub as shown to the Worksheet_SelectionChange sub so that you don't get errors selecting more than one cell.
Public Function ColourArea(ByRef ws As Worksheet) As Range
Const COLOUR_COL As Long = 6
Const PRODUCT_ROWS As Long = 16
'--- returns a range for the colour selections for all the products
' currently active on the worksheet
Dim lastRow As Long
With ws
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
If lastRow = 0 Then
ColourArea = Nothing
Else
Dim numberOfProducts As Long
numberOfProducts = (lastRow - 1) / PRODUCT_ROWS
'--- now create a Union of the first row of each of these
' product areas
Dim firstRow As Range
Dim allFirsts As Range
Set firstRow = ws.Cells(2, COLOUR_COL)
Set allFirsts = firstRow
Dim i As Long
For i = 2 To numberOfProducts
Set firstRow = firstRow.OFFSET(PRODUCT_ROWS, 0)
Set allFirsts = Application.Union(allFirsts, firstRow)
Next i
Set ColourArea = allFirsts
End If
End With
End Function
I am currently working on a userform. In this userform, data is entered in textbox4 and data is placed in textbox6 via commandbutton3 based on Vlookup. However, the vlookup must retrieve its data from the worksheet "DB - verzamelformulier" in the range A: B. Currently I get the error message: 424 object required. Can anybody help me with the code?
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("DB - verzamelformulier")
With ws
Texbox6.Formula = "VLookup(TextBox4.Value, DB - verzamelformulier!$A:$B), 2, False)"
End With
End Sub
Interesting approach, but you can't assign formulae to textboxes, only cells. Try out a function like this:
Function VerzamelFormulier(LookUpValue As Variant) As Variant
Dim WS As Worksheet
Dim R As Range
Set WS = ThisWorkbook.Worksheets("DB - verzamelformulier")
Set R = WS.Range("A:A").Find(LookUpValue, LookIn:=xlValues, Lookat:=xlWhole)
If R Is Nothing Then
' The value wasn't found.
Else
' Return the value from the cell in the same row and column B.
VerzamelFormulier = WS.Cells(R.Row, 2)
End If
End Function
Call it on TextBox4's change event so that whenever it's changed TextBox6's value is updated.
Private Sub TextBox4_Change()
TextBox6.Value = VerzamelFormulier(TextBox4.Value)
End Sub
Using Vlookup:
Option Explicit
Sub test()
Dim varResults As Variant
varResults = Application.VLookup(TextBox4.Value, ThisWorkbook.Worksheets("Db - verzamelformulier").Range("A:B"), 2, False)
If Not IsError(varResults) Then
'If there is a results
TextBox6.Value = varResults
Else
'If there is no result
End If
End Sub
I have a data set in another file that has 3 columns with thousands of rows. All 3 columns have values that are not unique.
I need 3 combo boxes.
The first combo box is for selecting from column "A" (bringing back unique values) for the different types of business units.
Next, depending on the business unit, combo box 2 is for selecting a specific customer (depending on the business unit selected).
Finally, combo box 3 is for selecting from the different cost centers that exist for a given customer.
I need unique values for all 3 columns.
I think I have combo box 1 with the following code:
Option Explicit
Private Sub UserForm_Initialize()
Dim wbExternal As Workbook '<-- the other workbook with the data
Dim wsExternal As Worksheet '<-- the worksheet in the other workbook
Dim lngLastRow As Long '<-- the last row on the worksheet
Dim rngExternal As Range '<-- range of data for the RowSource
Dim myCollection As collection, cell As Range
On Error Resume Next
Application.ScreenUpdating = False
Set wbExternal = Application.Workbooks.Open("C:\Users\sarabiam\desktop\OneFinance_Forecast_Model\FY19_New_Forecast_Model_Data_Tables.xlsm", True, True)
Set wsExternal = wbExternal.Worksheets("#2Table_Revenue") '<-- identifies worksheet
Set rngExternal = wsExternal.Range("A8:A" & CStr(lngLastRow))
Set myCollection = New collection
With ComboBox1
.Clear
For Each cell In Range("A8:A" & Cells(Rows.Count, 1).End(xlUp).Row)
If Len(cell) <> 0 Then
Err.Clear
myCollection.Add cell.Value, cell.Value
If Err.Number = 0 Then .AddItem cell.Value
End If
Next cell
End With
ComboBox1.ListIndex = 0
wbExternal.Close
Application.ScreenUpdating = True '<-- updates the worksheet on your screen
any time there is a change within the worksheet
End Sub
Here's a pretty generic approach - it only loads the data once, into an array, then uses that to reset list content on selection of a "previous" list.
Option Explicit
Const dataPath As String = "C:\Users\usernameHere\Desktop\tmp.xlsx"
Dim theData 'source data
Private Sub UserForm_Activate()
LoadData
Me.cboList1.List = GetList(1, "")
End Sub
Private Sub cboList1_Change()
Me.cboList2.Clear
Me.cboList2.List = GetList(2, Me.cboList1.Value)
Me.cboList3.Clear
End Sub
Private Sub cboList2_Change()
Me.cboList3.Clear
Me.cboList3.List = GetList(3, Me.cboList2.Value)
End Sub
'Return unique values from source data, given a specific column
' If given a value for "restrictTo", filter on match in column to "left"
' of the requested value column
Function GetList(colNum As Long, restrictTo)
Dim i As Long, n As Long, rv()
Dim dict As Object, v, ub As Long, inc As Boolean
Set dict = CreateObject("scripting.dictionary")
ub = UBound(theData, 1)
ReDim rv(1 To ub) 'will set final size after filling...
n = 0
For i = 1 To ub
v = theData(i, colNum)
'are we restricting the values we collect based on a different list?
If colNum > 1 And Len(restrictTo) > 0 Then
'is this value valid?
inc = (theData(i, colNum - 1) = restrictTo)
Else
inc = True 'collect all values
End If
If inc And Not dict.exists(v) Then
'don't already have this value - add to array and dict
n = n + 1
dict.Add v, True
rv(n) = v
End If
Next i
ReDim Preserve rv(1 To n) 'resize array to size of content
GetList = rv
End Function
'load data from external file
Private Sub LoadData()
With Workbooks.Open(dataPath).Worksheets("#2Table_Revenue")
theData = .Range(.Range("A8"), _
.Cells(.Rows.Count, 1).End(xlUp).Offset(0, 2)).Value
.Parent.Close False
End With
End Sub
Here's what I have so far. I'm attempting to populate my listbox based on the 2 combobox selections the user makes in a userform.
Private Sub Product_Type_Box_Change()
Dim Products_List As String
Dim M As Integer
Dim Manufacturers As String
Dim Product_Type As String
Dim DCSProgram2 As Workbook
Dim MLast As Long
Dim PLast As Long
Dim p As Integer
Set DCSProgram2 = ActiveWorkbook
Manufacturers = Me.MFG_Box.Value
Product_Type = Me.Product_Type_Box.Value
With DCSProgram2.Sheets("MFG_DATA")
MLast = .Cells(.Rows.Count, 1).End(xlUp).Row
For M = 1 To MLast
PLast = .Cells(.Rows.Count, 2).End(xlUp).Row
For p = 1 To PLast
If .Cells(M, 1).Value = Manufacturers And .Cells(p, 1).Value = Product_Type Then
With Products_Box
.AddItem "yay it works"
End With
End If
Next p
Next M
End With
End Sub
I've tried populating the list box without the And statement and had success with my variable Manufacturers. Below is an example of what my data is like.
Item Manufacturers Product Type Other Data
1 MFG 1 Tools 4558
2 MFG 2 Parts 4455
3 MFG 1 Tools 4585
4 MFG 3 Screws 6845
So if Manufacturers = MFG 1 and Productype = Tools then Products_Box would have the value yay it works. If I can figure this part out in the end I would like to list data from my spread sheet in the listbox so a user could pick one of multiple entries. Let me know if I can make anything more clear.
Thank You,
Geoff
If Products_Box is some other list box on the same UserForm then you're missing a Me. before it.
Furthermore if I got your aim right, I'd point out what follows:
you're looping unnecessarily
range type AutoFilter method would come in very handy
the same Products_Box treatment would be needed for MFG_Box _Change event
for all what above I'd refactor your code as follows
Option Explicit
Private Sub MFG_Box_Change()
Call UpdateProduct_Box
End Sub
Private Sub Product_Type_Box_Change()
Call UpdateProduct_Box
End Sub
Private Sub UpdateProduct_Box()
Dim Manufacturers As String
Dim Product_Type As String
Dim dataDB As Range
With Me
If .MFG_Box.ListIndex < 0 Or .Product_Type_Box.ListIndex < 0 Then Exit Sub
Manufacturers = .MFG_Box.Value
Product_Type = .Product_Type_Box.Value
End With
With ActiveWorkbook.Sheets("MFG_DATA") '<== be sure which workbook you want to refer to: ActiveWorkbook (the one whose window is currently active in excel session) or ThisWorkbook (the one in which this macro resides)
Set dataDB = .Range("A1:D1").Resize(.Cells(.Rows.Count, 1).End(xlUp).Row)
End With
With dataDB
.AutoFilter Field:=2, Criteria1:=Manufacturers ' filter data on Manufacturer
.SpecialCells(xlCellTypeVisible).AutoFilter Field:=3, Criteria1:=Product_Type ' filter data again on Product Type
Call UpdateListBox(Me.Products_Box, dataDB, 4)
.AutoFilter 'remove filters
End With
End Sub
Sub UpdateListBox(LBToFill As MSForms.ListBox, dataDB As Range, columnToList As Long)
Dim cell As Range, dataValues As Range
With LBToFill
If dataDB.SpecialCells(xlCellTypeVisible).Count > dataDB.Columns.Count Then 'if all data rows have been hidden then there last headers only, which count up to data columns number
Set dataValues = dataDB.Offset(1).Resize(dataDB.Rows.Count - 1)
.Clear ' clear listbox before adding new elements
For Each cell In dataValues.Columns(columnToList).SpecialCells(xlCellTypeVisible)
.AddItem cell.Value
Next cell
Else
.Clear ' no match -> clear listbox
End If
End With
End Sub