Populate Listbox from 2 combobox selections - excel

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

Related

Could an Excel dropdown box behave as a ListBox with checkboxes for Multi Selection?

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

VBA Excel - add header to combobox when using AddItem

I have a list with values I like to add to combobox in my userform.
The values I want are in Column A and Column Z (so values from 2 columns). I manage to add the values with the AddItem function but struggling to add a header to the dropdown (a few posts said this is not possible).
As alternative I saw ListFillRange but I cannot figure out if this can be used for two columns which are not next to each other.
Appreciate the help.
a few posts said this is not possible
I usually do not reply to questions which do not show any efforts but this is an interesting one. I tend to agree with you that lot of people think that you cannot show headers in a ComboBox.
But it is possible to show headers in a Combobox. Here is a demonstration. You will of course have to take help of a helper sheet for this if you do not want to change the original sheet.
TEST CASE
For our demonstration, we will take 2 non-contigous range A1-A5 and D1-A5
LOGIC
You will copy the relevant data to a new sheet.
Convert the range to a table
Set columnheads to true of combobox
Set rowsource to the relevant table range from helper sheet.
CODE
Option Explicit
Dim ws As Worksheet
Private Sub UserForm_Initialize()
Dim wsInput As Worksheet
'~~> Input sheet. Change as applicable
Set wsInput = Sheet1
'~~> Add a new sheet. Hide it (Optional)
Set ws = ThisWorkbook.Sheets.Add
ws.Visible = xlSheetHidden
'~~> Copy the non-contigous range to the new sheet
wsInput.Range("A1:A5").Copy ws.Range("A1")
wsInput.Range("D1:D5").Copy ws.Range("B1")
Dim rng As Range
'~~> Get your range
Set rng = ws.Range("A1:B5")
'~~> Convert range to table
ws.ListObjects.Add(xlSrcRange, rng, , xlYes).Name = "MyTable"
'~~> Few combobox settings and we are done
With ComboBox1
.ColumnCount = 2
.ColumnHeads = True
.RowSource = "MyTable"
End With
End Sub
'~~> Delete the temp sheet we created
Private Sub UserForm_Terminate()
Application.DisplayAlerts = False
If Not ws Is Nothing Then ws.Delete
Application.DisplayAlerts = True
End Sub
OUTPUT
ALTERNATIVE
If you are not ok with the idea of helper sheet and are ok to sacrifice on the header part then you can populate a combobox using non contigous ranges. See Excel VBA Multicolumn Listbox add non contiguous range. You will of course have to edit the code to suit your needs. Since there are two columns only, your final array would look like Dim Ar(1 To LastRow, 1 To 2). This array will hold values from both columns.
I use the following code to add headers above listboxes and comboboxes. It seems a bit like a sledgehammer to crack a nut but sometimes the nut has to be cracked, and all the other methods and tools that I have seen also fall into the category of sledgehammer.
To make this as simple as possible for myself I have defined a class called clsListBoxHeaders and I include that code below. Then suppose you have a ListBox with 3 columns you need to
Tell the class which ListBox it is to work on
Tell it what the headers are
Tell it the column widths
To do this insert the following code in your user form
Dim lbHeaders As New clsListBoxHeaders
Set lbHeaders.ListBox = ListBox1
lbHeaders.Headers = "First Header;Second Header;Third Header"
lbHeaders.ColumnWidths = "40;50;60"
Note that the number of headers and the number of columnwidths must match exactly the number of columns in your listbox/combobox
To clear the header data use:
lbHeaders.Clear
If you want to format the labels (e.g. font) then you can access the labels as a variant array
lbHeaders.Labels
The class module code is as follows:
Option Explicit
' clsListBoxHeaders - Display header info above a ListBox or ComboBox
' To use this class in your project:
' Add a class module called clsListBoxHeaders and paste this code into it
' For each ListBox or ComboBox for which you wish to display column headers insert the following code in your userform:
' Dim lbHeaders As New clsListBoxHeaders
' Set lbHeaders.ListBox = ListBox1
' lbHeaders.Headers = "First Header;Second Header;Third Header"
' lbHeaders.ColumnWidths = "40;50;60"
'Note that the number of headers and the number of columnwidths must match exactly the number of columns in your listbox/combobox
' To clear the header data use:
' lbHeaders.Clear
Const LabelHeight As Integer = 10 ' Height of the header labels.
Const LabelOffset As Integer = 10 ' Offset to get the header to align correctly to first column in listbox
Private myListBox As Object
Private myParent As Object
Private lblHeaders() As MSForms.Label
Private sColumnWidths() As Single
Public Property Set ListBox(ListBox As Object)
Set myListBox = ListBox
Set myParent = ListBox.Parent
End Property
Public Property Let Headers(sHeaders As String)
Dim lLeft As Long, vHeaders As Variant
Dim iCol As Integer
With myListBox
vHeaders = Split(sHeaders, ";")
ReDim lblHeaders(.ColumnCount)
If UBound(sColumnWidths) = 0 Then
ReDim sColumnWidths(.ColumnCount)
For iCol = 1 To .ColumnCount
sColumnWidths(iCol) = .Width / .ColumnCount
Next
End If
lLeft = LabelOffset
For iCol = 1 To .ColumnCount
Set lblHeaders(iCol) = myParent.Controls.Add("Forms.Label.1")
With lblHeaders(iCol)
.Top = myListBox.Top - LabelHeight
.Left = lLeft + myListBox.Left
.Width = sColumnWidths(iCol)
.Height = LabelHeight
lLeft = lLeft + sColumnWidths(iCol)
.Visible = True
.Caption = vHeaders(iCol - 1)
.ZOrder fmZOrderFront
End With
Next
End With
End Property
Public Property Let ColumnWidths(ColumnWidths As String)
Dim vSplit As Variant
Dim lLeft As Long
Dim iCol As Integer
With myListBox
vSplit = Split(ColumnWidths, ";")
ReDim sColumnWidths(.ColumnCount)
For iCol = 1 To .ColumnCount
sColumnWidths(iCol) = vSplit(iCol - 1)
Next
lLeft = LabelOffset
If UBound(lblHeaders) > 0 Then
For iCol = 1 To .ColumnCount
With lblHeaders(iCol)
.Left = myListBox.Left + lLeft
.Width = sColumnWidths(iCol)
lLeft = lLeft + sColumnWidths(iCol) ' + LabelOffset
End With
Next
End If
End With
End Property
Public Property Get Labels() As Variant
Dim iCol As Integer
Dim vLabels As Variant
With myListBox
ReDim vLabels(.ColumnCount - 1)
For iCol = 1 To .ColumnCount
Set vLabels(iCol - 1) = lblHeaders(iCol)
Next
End With
Labels = vLabels
End Property
Public Sub Clear()
Dim i As Integer
For i = 1 To UBound(lblHeaders)
myParent.Controls.Remove lblHeaders(i).Name
Next
Class_Initialize
End Sub
Private Sub Class_Initialize()
ReDim lblHeaders(0)
ReDim sColumnWidths(0)
End Sub

how extract some informations from range of sheet1 and put it on a combobox

I am facing a little problem, I have a userform which contains two comboboxes, a combobox1 for "company name" and another combobox2 for "specialty"
I really want that when I choose for example like in the photo, I choose in combobox1 "teter", I want to display in combobox2 a list which contains only MP and PDP
and if I choose teterss in combobox1, I would like to display in combobox2 only PDP, I tried this in combobox2 which allows you to search only in column H compared to the choice I chose in combobox1 but it does not work
Dim i As Long
Dim isearch As Long
isearch = Worksheets("FRS").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To isearch
If Trim(sheets7.Cells(i, 1)) = Trim(Combobox1.Value) Then
Combobox2.Value = sheets7.Cells(i, 8).Value
Exit For
End If
Next i
Thanks
Change sheets7 to Sheet7 That way intellisense will pick up the range object.
Dim i As Long
Dim isearch As Long
isearch = Worksheets("FRS").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To isearch
If Trim(Sheet7.Cells(i, 1)) = Trim(Combobox1.Value) Then
Combobox2.Value = Sheet7.Cells(i, 8).Value
Exit For
End If
Next i
The combo box value can be set in that way but it is not wise. It is good to check if the string of that cell exists between that combo items collection. I try this answer supposing that you know some VBA and your code wants to do something. Otherwise, your explanation does not match the code you presented...
Dim i As Long, isearch As Long, cbIt as Long
isearch = Worksheets("FRS").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To isearch
If Trim(sheets7.Cells(i, 1)) = Trim(Combobox1.Value) Then
cbIt = ComboIt(Me.ComboBox2, sheets7.Cells(i, 8).Value)
If cbIt = -1 Then
Me.ComboBox2.AddItem sheets7.Cells(i, 8).Value
Me.ComboBox2.Value = sheets7.Cells(i, 8).Value
Else
Me.ComboBox2.ListIndex = cbIt
End If
Exit For
End If
Next i
You need a way to find the combo listIndex for that specific string. Which MUST BE PART OF THE COMBO ITEMS COLLECTION.
I just suppose that sheets7 has been previously correctly set...
Function ComboIt(cb As ComboBox, strIt As String) As Long
Dim i As Long
If cb.ListCount > 0 Then
For i = 0 To cb.ListCount - 1
If cb.List(i) = strIt Then ComboIt = i: Exit Function
Next i
End If
ComboIt = -1
End Function
If your code does not count, based on your explanation, you should clear the combo and add one or two items according to the combobox1 value, not with the checked range...
Something like that:
Private Sub ComboBox1_Change()
If Me.ComboBox1.value = "teter" Then
Me.ComboBox2.Clear
Me.ComboBox2.AddItem "MP"
Me.ComboBox2.AddItem "PDP"
ElseIf Me.ComboBox1.value = "teterss" Then
Me.ComboBox2.Clear
Me.ComboBox2.AddItem "MP"
End If
End Sub
EDIT: I am unable to comment as I am not logged in. Please see edits below.
Here's a suggestion of a smarter way to fill out your comboboxes. You may use Dictionary object (Scripting.Dictionary) to avoid repeated values on your dropdown.
Dictionary is similar to Collection which has a set of Keys (unique) with its corresponding values. You may read on this link to learn more about Dictionary.
Variables used:
companyNamesRange = range of your Company Names
dict = Scripting.Dictionary object
cboCompanyName = ComboBox1
specialty = value of specialty
cboSpecialty = ComboBox2
And now for the code...
Add this to your userform's code -- UserForm_Initialize() event:
Private Sub UserForm_Initialize()
totalRows = Sheet1.Range("A" & Rows.Count).End(xlUp).Row 'get total rows
Set companyNamesRange = Sheet1.Range("A2:A" & totalRows) 'range of company values
Call GetCompanyNameList 'this method will fill add unique items on your Combobox1 (Company Name)
End Sub
For Combobox1 (Company Name), you may use this edit and use the code below to get unique values
Private Sub GetCompanyNameList()
'create dictionary object
Set dict = CreateObject("Scripting.Dictionary")
'loop through each Company Name
For Each cellRange In companyNamesRange
'test if Company Name exists on the dictionary Keys
If Not dict.Exists(cellRange.Value) Then
'since we confirmed that the value is unique, add the value as a dictionary key so that we can collect unique values for testing later on
dict.Add cellRange.Value, ""
'add value to cboCompanyName(Combobox1)
cboCompanyName.AddItem cellRange.Value
End If
Next
'dispose object
Set dict = Nothing
End Sub
Added method here for cboCompany_Change event
Private Sub cboCompanyName_Change()
cboSpecialty.Value = ""
cboSpecialty.Clear
Call GetSpecialtyList
End Sub
For Combobox2 (Specialty), you may edit and use this as well to get unique values of specialty based on the Company Name selected**
Private Sub GetSpecialtyList()
Set dict = CreateObject("Scripting.Dictionary")
For Each companyRange In companyNamesRange
If cboCompanyName.Value = companyRange.Value Then
specialty = Sheet1.Range("B" & companyRange.Row).Value
If Not dict.Exists(specialty) Then
dict.Add specialty, ""
cboSpecialty.AddItem specialty
End If
End If
Next
Set dict = Nothing
End Sub
So, here is my whole [UserForm(Code)] module. I hope I understood everything correct.
Read comments, ask if you have questions.
Option Explicit
Dim dataSheet As Worksheet
Private Sub UserForm_Initialize() ' update UserForm name if needed
Dim companyRange As Range, cr As Range
Dim startRow As Long, columnNo As Long
Set dataSheet = ActiveSheet ' replace ActiveSheet with your data sheet name, don't use ActiveSheet
startRow = 2 ' as I suppose - values start from 2d row, 1st one - is the header, update if needed
columnNo = 1 ' as I suppose - starting column is 1, update if needed
With dataSheet
Set companyRange = Range(.Cells(startRow, columnNo), .Cells(Rows.Count, columnNo).End(xlUp)) ' assign companies range
End With
With ComboBox1 ' this is a combobox with company's names
For Each cr In companyRange
.AddItem cr.Value ' add companies to combobox one by one
' so if the cell row is 2, it will have a 0 ListIndex in combobox
Next
End With
End Sub
'-------------------------------------------------------------------------------------------------------
Private Sub ComboBox1_Change()
Dim item1 As Range, item2 As Range
Set item1 = dataSheet.Cells(ComboBox1.ListIndex + 2, 8) ' see the explanation below this part of code
Set item1 = dataSheet.Cells(ComboBox1.ListIndex + 3, 8)
'when we were adding items to combobox1, the cell on the sartRow=2 has row number 2 and a 0 ListIndex in combobox1 (cell on the row 3 has ListIndex 1, and so on)
'so in order to get the row of selected item - we need to add 2 to item's ListIndex
With ComboBox2
.Clear ' clear the combobox2 upon each change, consider it if you have something in your ComboBox2_Change() event
' 'cos if there is some selected value the .Clear statement will trigger that event
' This point is still not clear to me, so there are 2 options based on original post
' uncomment if needed
' Option 1
If Not item1 = item2 Then ' as I understood, if ites are different we add two of them
.AddItem item1.Value
.AddItem item2.Value
Else
.AddItem item1.Value ' if they are the same - we add only one
End If
' Option 2
' If ComboBox1.Value = "teter" Then
' .AddItem "MP"
' .AddItem "PDP"
' ElseIf ComboBox1.Value = "teterss" Then
' .AddItem "MP"
' End If
End With
End Sub
Adding items to a second combobox
Assuming you want rebuild your list by entering one or several "row" items each time you choose another value in ComboBox1 and assuming your sheet's CodeName actually is sheets7, I'd suggest the following steps:
just clear contents in combobox2,
use the .AddItem method to add comboitems in a list,
omit the Exit For and
set the .ListIndex to the first item in the list if only one item to choose
Private Sub ComboBox1_Change()
Dim isearch As Long
isearch = Worksheets("FRS").Range("A" & Rows.Count).End(xlUp).Row
With Me.ComboBox2
.Clear
Dim i As Long
For i = 2 To isearch
If Trim(sheets7.Cells(i, 1)) = Trim(ComboBox1.Value) Then
.AddItem sheets7.Cells(i, 8).Value
End If
Next i
If .ListCount > 1 Then ' several items
.ListIndex = -1 ' no concrete display, let open for choice
Else
.ListIndex = 0 ' display single item
End If
End With
End Sub
Further hint: instead of using AddItem you could also populate an array and assign it to the .List property via one code line, but the above approach should show you the way :-)
Further assumption: "specialty" items seem to be unique per chosen identifying name.
Edit due to comment
Your issue seems to be caused by adding the ".RowSource` property to you comboboxes.
I added a simple UserForm_Initialize event procedure removing this property by code as first step, so you can control it.
Remove or outcomment a Combobox2_Change (Combospe_Change) event procedure overwriting prior results!
Seems that you named the data worksheet "FRS" sheets7 equalling the sheets Codename feuil7 (Excel worksheet name "FRS") - change that to your needs. Furthermore I sticked to ComboBox1 (Combofrs) and ComboBox2 (Combospe) ... Bonne chance :-)
Private Sub UserForm_Initialize()
With Me.ComboBox1 ' << (Me.Combofrs)
.RowSource = "" ' << remove existing row source !
Dim frs As Collection
Set frs = New Collection
' get unique elements of suppliers/fournisseurs (e.g. teter, teterss, test)
Dim fr
On Error Resume Next
For Each fr In sheets7.Range("listefrs") ' << feuil7.
frs.Add fr, fr
If Trim(fr) = vbNullString Then Exit For
Next
' populate supplier/fournisseurs combo with unique elements
Dim i As Long
For i = 1 To frs.Count
.AddItem frs(i) & ""
Next
End With
'(Combospe)
ComboBox2.RowSource = "" ' << remove existing row source !
End Sub

Combo boxes where available choices are unique and dependent on the choice in previous combo box

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

Excel VBA - Search Userform Listbox with Multiple Columns via textbox

I'm trying to work out the code needed to filter or search a listbox included on a userform that contains multiple columns and multiple rows. On the userform, I have a textbox that allows user input that would ideally filter out the non-matching entries from the Listbox.
I've found a few solutions online, but nothing that I could get to work on a listbox with multiple columns in a userform. The way it's coded from the example is trying to transpose a single column of data, and I'm guessing I need to alter the code to use an array. I'm just not strong enough with VBA to know exactly how to change that piece.
I'm also receiving an error on the GoToRow() function, but I believe it's tied into the single vs multiple column listbox issue.
I've included a link to a basic mockup of my project below since I'm using a userform with a listbox and textbox that are named.
https://www.dropbox.com/s/diu05ncwbltepqp/BasicListboxExample.xlsm?dl=0
The listbox on my userform has five columns and is named ProjectList, and the textbox is named SearchTextBox.
Option Explicit
Const ProjectNameCol = "B"
Dim PS As Worksheet
Private loActive As Excel.ListObject
Private Sub UserForm_Activate() ' Main code on Userform Activation, calls support subs
Set PS = Sheets("ProjectSheet") 'stores value for Project Sheet Worksheet as PS
Set loActive = ActiveSheet.ListObjects(1)
'populates listbox with data from ProjectSheet Worksheet named table
ProjectList.RowSource = "AllData"
'# of Columns for listbox
ProjectList.ColumnCount = 5
'Column Width for listbox
ProjectList.ColumnWidths = "140; 100; 100; 100; 100"
Me.ProjectList.TextColumn = 1
Me.ProjectList.MatchEntry = fmMatchEntryComplete
ResetFilter
End Sub
Private Sub SearchTextBox_Change()
'Can't get anything to work here
ResetFilter
End Sub
Sub ResetFilter()
Dim rngTableCol As Excel.Range
Dim varTableCol As Variant
Dim RowCount As Long
Dim FilteredRows() As String
Dim i As Long
Dim ArrCount As Long
Dim FilterPattern As String
'the asterisks make it match anywhere within the string
If Not ValidLikePattern(Me.SearchTextBox.Text) Then
Exit Sub
End If
FilterPattern = "*" & Me.SearchTextBox.Text & "*"
Set rngTableCol = loActive.ListColumns(1).DataBodyRange
'note that Transpose won't work with > 65536 rows
varTableCol = Application.WorksheetFunction.Transpose(rngTableCol.value)
RowCount = UBound(varTableCol)
ReDim FilteredRows(1 To 2, 1 To RowCount)
For i = 1 To RowCount
'Like operator is case sensitive,
'so need to use LCase if not CaseSensitive
If (LCase(varTableCol(i)) Like LCase(FilterPattern)) Then
'add to array if ListBox item matches filter
ArrCount = ArrCount + 1
'there's a hidden ListBox column that stores the record num
FilteredRows(1, ArrCount) = i
FilteredRows(2, ArrCount) = varTableCol(i)
End If
Next i
If ArrCount > 0 Then
'delete empty array items
'a ListBox cannot contain more than 65536 items
ReDim Preserve FilteredRows(1 To 2, 1 To Application.WorksheetFunction.Min(ArrCount, 65536))
Else
're-initialize the array
Erase FilteredRows
End If
If ArrCount > 1 Then
Me.ProjectList.List = Application.WorksheetFunction.Transpose(FilteredRows)
Else
Me.ProjectList.Clear
'have to add separately if just one match
'or we get two rows, not two columns, in ListBox
If ArrCount = 1 Then
Me.ProjectList.AddItem FilteredRows(1, 1)
Me.ProjectList.List(0, 1) = FilteredRows(2, 1)
End If
End If
End Sub
Private Sub ProjectList_Change()
GoToRow
End Sub
Sub GoToRow()
If Me.ProjectList.ListCount > 0 Then
Application.Goto loActive.ListRows(Me.ProjectList.value).Range.Cells(1),True
End If
End Sub
Over in my modules I have:
Function ValidLikePattern(LikePattern As String) As Boolean
Dim temp As Boolean
On Error Resume Next
temp = ("A" Like "*" & LikePattern & "*")
If Err.Number = 0 Then
ValidLikePattern = True
End If
On Error GoTo 0
End Function

Resources