VBA UserForm Multiple Dependent Dynamic ComboBox - excel

Basically I am trying to create multiple dependent dynamic Combo Boxes on a UserForm as it pulls the values from a LookupList Worksheet as seen below
LookupList Worksheet
How it should work:
ComboBox1 would list the company
ComboBox2 is dependent on ComboBox1
ComboBox3 is dependent on ComboBox2
Example:
ComboBox1: Company = Mercedes
ComboBox2: Mercedes Model = A Class
ComboBox3: A Class Model = AMG
I have tried the code below but got stuck on ComboBox2
Private Sub UserForm_Initialize()
'dynamically call ComboBox1 when form initialize
With Worksheets("LookupList")
ComboBox1.List = .Range("A2:A" & .Range("a" & .Rows.Count).End(xlUp).Row).Value
End With
End Sub
Private Sub ComboBox1_Change()
Dim index As Integer
'list ComboBox1 and look for dependency
index = ComboBox1.ListIndex
ComboBox2.Clear
ComboBox3.Clear
'call values in ComboBox2
Select Case index
Case Is = 0
'Calls Mercedes Car Model contents dynamically
With Worksheets("LookupList")
ComboBox2.List = .Range("C2:C" & .Range("c" & Rows.Count).End(xlUp).Row).Value
End With
Case Is = 1
'Calls BMW Car Model contents dynamically
With Worksheets("LookupList")
ComboBox2.List = .Range("G2:G" & .Range("g" & Rows.Count).End(xlUp).Row).Value
End With
End Select
End Sub
Private Sub ComboBox2_Change()
Dim index As Integer
Dim verify_data As Long
index = ComboBox2.ListIndex
verify_data = ComboBox2.ListIndex
'Calls values in ComboBox3
Select Case index
If verify_data = 0 Then
'If Mercedes A Class Model is selected from ComboBox2, list A Class model types
Case Is = 0
With Workseets("LookupList")
ComboBox3.List = .Range("D2:D" & .Range("d" & Rows.Count).End(xlUp).Row).Value
End With
End If
End Select
End Sub

(Posted on behalf of the question author).
I managed to resolve my own issue as shown in code below. If anyone has a shorter method, please feel free to share.
Private Sub ComboBox2_Change()
Dim index As Integer
'list ComboBox2 and look for dependency
index = ComboBox2.ListIndex
ComboBox3.Clear
If Me.ComboBox2.Value = "A Class" Then
With Worksheets("LookupLists")
ComboBox3.List = .Range("D2:D" & .Range("d" &
Rows.Count).End(xlUp).Row).Value
End With
End If
If Me.ComboBox2.Value = "B Class" Then
With Worksheets("LookupLists")
ComboBox3.List = .Range("E2:E" & .Range("e" &
Rows.Count).End(xlUp).Row).Value
End With
End If
End Sub

One way to shorten the code is to refactor it with better syntax and assign each lookup list to a named range.
Private Sub ComboBox2_Change()
Dim index As Integer
'list ComboBox2 and look for dependency
index = ComboBox2.ListIndex
ComboBox3.Clear
Dim whichName as String
Select Case index
Case "A Class": whichName = "aClass" 'assumed named range scoped to worksheet
Case "B Class": whichName = "bClass" 'assumed named range scoped to worksheet
End Select
ComboBox3.List = Worksheets("LookupLists").Range(whichName).Value
End Sub

Try
Private Sub UserForm_Initialize()
Dim Ws As Worksheet
Set Ws = Worksheets("LookupList")
With Ws
ComboBox1.List = .Range("A2:A" & .Range("a" & Rows.Count).End(xlUp).Row).Value
End With
End Sub
Private Sub ComboBox1_Change()
Dim Ws As Worksheet
Dim rngT As Range, rngHead As Range
Set Ws = Worksheets("LookupList")
Set rngHead = Ws.Range("a1", "i1")
ComboBox2.Clear
ComboBox3.Clear
Set rngT = rngHead.Find(ComboBox1.Value, LookIn:=xlValues, Lookat:=xlPart)
If Not rngT Is Nothing Then
ComboBox2.List = Ws.Range(rngT.Offset(1, 0), rngT.End(xlDown)).Value
End If
End Sub
Private Sub ComboBox2_Change()
Dim Ws As Worksheet
Dim rngT As Range, rngHead As Range
Set Ws = Worksheets("LookupList")
Set rngHead = Ws.Range("a1", "i1")
ComboBox3.Clear
If ComboBox2.Value <> "" Then
Set rngT = rngHead.Find(ComboBox2.Value, LookIn:=xlValues, Lookat:=xlPart)
If Not rngT Is Nothing Then
ComboBox3.List = Ws.Range(rngT.Offset(1, 0), rngT.End(xlDown)).Value
End If
End If
End Sub

Related

Excel, VBA, UserForm: Populate combobox based on selection in another combobox

I have created a userform with some comboboxes, based on combobox2 I would like to populate combobox1.
In combobox2 there are 6 items to choose from 17, 19, 21, 23, 25, 25+
Based on the selected item in Combobox1 I would like to populate combobox2 as following:
If ItemA is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (T6:T1000)
If ItemB is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (V6:V1000)
If ItemC is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (X6:X1000)
If ItemD is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (Z6:Z1000)
If ItemE is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (AB6:AB1000)
If ItemF is selected, Combobox1 should be populated from range in Sheet ”supply_to_production” range (AD
6:AD1000)
I have tried the code bellow, it is not giving me any error but is also does not give me any list in combobox1.
`
Private Sub UserForm_Initialize()
With ComboBox2
.AddItem "17"
.AddItem "19"
.AddItem "21"
.AddItem "23"
.AddItem "25"
.AddItem "25+"
End With
End Sub
Private Sub ComboBox1_Update()
Dim index As Integer
index = ComboBox2.ListIndex
ComboBox1.Clear
Select Case index
Case "17":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!T6:T1000]
Case "19":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!V6:V1000]
Case "21":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!X6:X1000]
Case "23":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!Z6:Z1000]
Case "25":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!AB6:AB1000]
Case "25+":
ComboBox1.List = [SUPPLY_TO_PRODUCTION!AD6:AD1000]
End Select
End Sub
`
Something like this should work for you:
Private Sub UserForm_Initialize()
Me.ComboBox2.List = Array(17, 19, 21, 23, 25, "25+")
End Sub
Private Sub ComboBox2_Change()
'Don't have magic numbers, have an easy way to change hard-coded values
Const lStartRow As Long = 6
Me.ComboBox1.Clear
Dim sColumn As String
Select Case CStr(Me.ComboBox2.Value)
Case "17": sColumn = "T"
Case "19": sColumn = "V"
Case "21": sColumn = "X"
Case "23": sColumn = "Z"
Case "25": sColumn = "AB"
Case "25+": sColumn = "AD"
End Select
If Len(sColumn) = 0 Then Exit Sub 'No valid option selected from ComboBox2
'Dynamically size the data
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("SUPPLY_TO_PRODUCTION")
Dim rData As Range: Set rData = ws.Range(ws.Cells(lStartRow, sColumn), ws.Cells(ws.Rows.Count, sColumn).End(xlUp))
Me.ComboBox1.List = rData.Value
End Sub
It looks like you're compiling, where Option Explicit may have saved you?
Try referencing the full range and using the ComboBox2.Value for your Select, such that:
Private Sub ComboBox1_Change()
Dim columnReference as String
Select Case ComboBox2.Value
Case "17":
columnReference = "T"
Case "19":
columnReference = "V"
Case "21":
columnReference = "X"
Case "23":
columnReference = "Z"
Case "25":
columnReference = "AB"
Case "25+":
columnReference = "AD"
End Select
With Sheets("SUPPLY_TO_PRODUCTION")
ComboBox2.List = .Range(.Cells(6,columnReference),.Cells(1000,columnReference)).Value
End With
End Sub
Edit1: Added name of subroutine... Private Sub ComboBox1_Change(), which should trigger the event. Plus, showing images of a quick example which follows this model:
Option Explicit
Private Sub UserForm_Initialize()
ComboBox1.List = Array("animal", "food")
End Sub
Private Sub combobox1_change()
Dim columnReference As Long
Select Case ComboBox1.Value
Case "animal"
columnReference = 1
Case "food"
columnReference = 2
End Select
With Sheets(1)
ComboBox2.List = .Range(.Cells(2, columnReference), .Cells(4, columnReference)).Value
End With
End Sub
Populate Combo Box Based On Selection in Another Combo Box
UserForm e.g. UserForm1 (careful)
Private Sub UserForm_Initialize()
ComboBox2.List = Array("17", "19", "21", "23", "25", "25+")
End Sub
Private Sub ComboBox2_Change()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets("SUPPLY_TO_PRODUCTION")
Dim arg As Range: Set arg = ws.Range("T6:T1000," _
& "V6:V1000,X6:X1000,Z6:Z1000,AB6:AB1000,AD6:AD1000")
Dim aIndex As Long: aIndex = ComboBox2.ListIndex + 1 ' zero-based
Dim Data() As Variant: Data = GetColumnRange(arg.Areas(aIndex))
Dim dict As Object: Set dict = DictColumn(Data)
If dict Is Nothing Then Exit Sub
ComboBox1.List = dict.Keys
End Sub
Standard Module e.g. Module1
Function GetColumnRange( _
ByVal rg As Range, _
Optional ByVal ColumnNumber As Long = 1) _
As Variant
If rg Is Nothing Then Exit Function
If ColumnNumber < 1 Then Exit Function
If ColumnNumber > rg.Columns.Count Then Exit Function
With rg.Columns(ColumnNumber)
If rg.Rows.Count = 1 Then
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = .Value
GetColumnRange = Data
Else
GetColumnRange = .Value
End If
End With
End Function
Function DictColumn( _
ByVal Data As Variant, _
Optional ByVal ColumnIndex As Variant) _
As Object
Const ProcName As String = "DictColumn"
On Error GoTo ClearError
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive
Dim c As Long
If IsMissing(ColumnIndex) Then
c = LBound(Data, 2) ' use first column Index2
Else
c = CLng(ColumnIndex)
End If
Dim Key As Variant
Dim r As Long
For r = LBound(Data, 1) To UBound(Data, 1)
Key = Data(r, c)
If Not IsError(Key) Then ' exclude error values
If Len(CStr(Key)) > 0 Then ' exclude blanks
dict(Key) = Empty
End If
End If
Next r
If dict.Count = 0 Then Exit Function ' only error values and blanks
Set DictColumn = dict
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

Delete checkbox from a Specific Cell with VBA

I'm putting together a spreadsheet that should populate checkboxes in a specific column when the spreadsheet opens if the appropriate A Column/Row is not empty. It should also remove checkboxes when it finds that same A column to be empty. My VB is correctly creating the checkboxes, but I cannot figure out how to tell the code to delete the checkbox from a specific cell.
Most articles I find mention removed ALL checkboxes, but I'm looking to do it conditionally. Any guidance would be greatly appreciated.
Private Sub Workbook_Open()
'declare a variable
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
'calculate if a cell is not blank across a range of cells with a For Loop
For x = 2 To 1000
If ws.Cells(x, 1) <> "" Then
Call Add_CheckBox(CInt(x))
Else
Call Delete_CheckBox(CInt(x))
End If
Next x
End Sub
Private Sub Add_CheckBox(Row As Integer)
ActiveSheet.CheckBoxes.Add(Cells(Row, "T").Left, Cells(Row, "T").Top, 72, 12.75).Select
With Selection
.Caption = ""
.Value = xlOff '
.LinkedCell = "AA" & Row
.Display3DShading = False
End With
End Sub
Private Sub Delete_CheckBox(Row As Integer)
Dim cb As CheckBox
If cb.TopLeftCell.Address = (Row, "T") Then cb.Delete
End Sub
Naming the CheckBoxes will make it easier to maintain your code.
Private Sub Workbook_Open()
Const CheckBoxPrefix As String = "Sheet1TColumnCheckBox"
'declare a variable
Dim CheckBoxName As String
Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
'calculate if a cell is not blank across a range of cells with a For Loop
Dim r As Long
For r = 2 To 1000
CheckBoxName = CheckBoxPrefix & r
If Len(ws.Cells(r, 1)) > 0 Then
If Not WorksheetContainsCheckBox(CheckBoxName, ws) Then Add_CheckBox CheckBoxName, ws.Cells(r, 1), ws.Cells(r, "AA")
Else
If WorksheetContainsCheckBox(CheckBoxName, ws) Then ws.CheckBoxes(CheckBoxName).Delete
End If
Next
End Sub
Private Sub Add_CheckBox(CheckBoxName As String, Cell As Range, LinkedCell As Range)
With Cell.Worksheet.CheckBoxes.Add(Cell.Left, Cell.Top, 72, 12.75)
.Caption = ""
.Value = xlOff '
.LinkedCell = LinkedCell
.Display3DShading = False
.Name = CheckBoxName
End With
End Sub
Function WorksheetContainsCheckBox(CheckBoxName As String, ws As Worksheet)
Dim CheckBox As Object
On Error Resume Next
Set CheckBox = ws.CheckBoxes(CheckBoxName)
WorksheetContainsCheckBox = Err.Number = 0
On Error GoTo 0
End Function
Try something like this (put a checkbox "in" A1 but not C1)
Sub tester()
Debug.Print Delete_CheckBox([A1])
Debug.Print Delete_CheckBox([C1])
End Sub
'Return True if able to delete a checkbox from range `rng`
Private Function Delete_CheckBox(rng As Range) As Boolean
Dim cb As CheckBox
For Each cb In rng.Worksheet.CheckBoxes
If Not Application.Intersect(cb.TopLeftCell, rng) Is Nothing Then
Debug.Print "Deleting checkbox in " & cb.TopLeftCell.Address
cb.Delete
Delete_CheckBox = True
Exit For 'if only expecting one matched checkbox
End If
Next cb
End Function

dynamic listbox

I'm looking to add a checkbox that displays any "Not Found" items.
When this I would like to edit the Listitem
My code currently is working when I open userform2 and edit the Listbox without checking checkbox1.
However, when I checkbox1 is true, it correctly displays the only "Not Found" but when I go to edit the list item I receive run-time error 1004 method range of object _global fail
on:
Set rCell = Range(.RowSource).Resize(1).Offset(.ListIndex)
my full code: for userform2
Private Sub ListBox2_Click()
TextBox1.Enabled = True
TextBox1.Value = ListBox2.Value
End Sub
Private Sub TextBox1_Change()
Dim rCell As Range
With ListBox2
Set rCell = Range(.RowSource).Resize(1).Offset(.ListIndex)
rCell.Value = TextBox1.Value
End With
End Sub
Private Sub CheckBox1_Click()
OptimizedMode True
If userform2.CheckBox1.Value = True Then
Worksheets("Table").Range("A1").AutoFilter Field:=1, Criteria1:="Not Found", Operator:=xlOr, Criteria2:="="
userform2.ListBox2.RowSource = vbNullString
userform2.ListBox2.ColumnHeads = False
Dim rng As Range
Dim Cel1 As Range
Dim LR As Long
Dim ws As Worksheet
Set ws = Sheets("Table")
With ws
LR = .Cells(.Rows.Count, "A").End(xlUp).Row
Set rng = .Range("A2:A" & LR).SpecialCells(xlCellTypeVisible)
With userform2.ListBox2
.ColumnCount = 1
For Each Cel1 In rng
.AddItem CStr(Cel1.Value)
.List(.ListCount - 1, 1) = Cel1.Offset(0, 1).Value
Next Cel1
End With
End With
End If
If CheckBox1.Value = False Then
With userform2.ListBox2
.RowSource = "Table!A2:A1048576"
End With
End If
OptimizedMode False
End Sub
You use CheckBox1_Click event to control ListBox2.RowSource. If CheckBox1 is TRUE you clear RowSource and then add items to the list.
When RowSource is cleared Range(.RowSource) is the same as Range("") which of course errors.
Under these conditions, you'll need to devise another method to determine which row the ListBox refers to.

Autofill listbox from textbox text VBA

I want a userform where as text is being typed into textbox1, possible values that match the text from the sheet "Data" populate listbox1 and similarly for textbox2 and listbox2. Currently when I type in textbox1, it's filling listbox2 with values from column A in the sheet that is open, instead of the sheet "Data".
Private Sub TextBox1_Change()
Dim i As Integer, ws As Worksheet
ListBox1.Visible = True
Set ws = Sheets("Data")
For i = 2 To Sheets("Data").Range("D6000").End(xlUp).Row
If UCase(Left(ws.Cells(i, 1), Len(TextBox1.Text))) = UCase(TextBox1.Text) Then
ListBox1.AddItem Cells(i, 1)
End If
Next i
End Sub
Private Sub TextBox2_Change()
Dim i As Integer, ws As Worksheet
ListBox2.Visible = True
Set ws = Sheets("Data")
For i = 2 To Sheets("Data").Range("B6000").End(xlUp).Row
If UCase(Left(ws.Cells(i, 1), Len(TextBox2.Text))) = UCase(TextBox2.Text) Then
ListBox2.AddItem Cells(i, 1)
End If
Next i
End Sub
Edit: What I ended up using:
Private Sub TextBox1_Change()
Dim i As Long
Dim arrList As Variant
Me.ListBox1.Clear
If Sheet7.Range("D" & Sheet7.Rows.Count).End(xlUp).Row > 1 And Trim(Me.TextBox1.Value) <> vbNullString Then
arrList = Sheet7.Range("D1:D" & Sheet7.Range("D" & Sheet7.Rows.Count).End(xlUp).Row).Value2
For i = LBound(arrList) To UBound(arrList)
If InStr(1, arrList(i, 1), Trim(Me.TextBox1.Value), vbTextCompare) Then
Me.ListBox1.AddItem arrList(i, 1)
End If
Next i
End If
If Me.ListBox1.ListCount = 1 Then Me.ListBox1.Selected(0) = True
End Sub
Private Sub TextBox2_Change()
Dim i As Long
Dim arrLists As Variant
Me.ListBox2.Clear
If Sheet7.Range("B" & Sheet7.Rows.Count).End(xlUp).Row > 1 And Trim(Me.TextBox2.Value) <> vbNullString Then
arrLists = Sheet7.Range("B1:B" & Sheet7.Range("B" & Sheet7.Rows.Count).End(xlUp).Row).Value2
For i = LBound(arrLists) To UBound(arrLists)
If InStr(1, arrLists(i, 1), Trim(Me.TextBox2.Value), vbTextCompare) Then
Me.ListBox2.AddItem arrLists(i, 1)
End If
Next i
End If
If Me.ListBox2.ListCount = 1 Then Me.ListBox2.Selected(0) = True
End Sub
Use ws.Cells(i, 1) instead of Cells(i, 1).
Also, should the part concerning ListBox2 be moved to TextBox2_Change() ?

How to reference Tables in VBA

I am placing a button on a sheet to allow to uppercase all items in two columns in a table.
Here is the code I have found elsewhere and adapted to try to make work:
Private Sub CommandButton1_Click()
With Range("B10", Cells(Rows.Count, "B").End(xlUp))
.Value = Evaluate("INDEX(UPPER(" & .Address(External:=True) & "),)")
End With
With Range("C10", Cells(Rows.Count, "C").End(xlUp))
.Value = Evaluate("INDEX(UPPER(" & .Address(External:=True) & "),)")
End With
End Sub
I want the Range to reference Table2, columns 1 & 2 instead of B & C.
Suggestions?
For access to all sorts of table ranges and references, you need to use a ListObject. Here's an example:
Option Explicit
Sub test()
Dim ws As Worksheet
Dim t2 As ListObject
Set ws = ActiveSheet
Set t2 = ws.ListObjects("Table2")
Debug.Print t2.ListColumns(1).Name
Dim refRange As Range
Set refRange = Union(t2.ListColumns(1).Range, t2.ListColumns(2).Range)
Debug.Print refRange.Address
End Sub

Resources