VBA Excel - add header to combobox when using AddItem - excel

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

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

How to sort Columns based on Headers using a list of all headers that are available?

I'm receiving several different excel spreadsheets with up to 30 different column headers. I really only need about 8 or 10 of the columns from each spreadsheet. I'm sick of scrolling left and right finding the columns I need. I'd like to have a macro that pops up a dialog box with all the headers available. I'd like to select the headers that I want and have it cut and paste them from left to right so they are all next to each other.
I'm new to VBA and trying to learn it but this is a little over my head. Help anyone??
I've found ways to organize my columns the same way every time but each spreadsheet has different columns and orders so I need to be able to select them.
I suggest using userform with a listbox where you can select the header.
You can try the code below.
Requirement:
Userform
ListBox > Multiselect Property should be = 1
Button > to load the selected data to a new worksheet
Dim mySH As Worksheet
Dim oSH As Worksheet 'Output Worksheet
Private Sub cmd_load_Click()
Dim i As Integer
Dim col_count As Integer
col_count = 1
Dim col_header As String
Dim ns_srow As Integer
ns_srow = 1
'LOOP THRU ALL ITEMS IN LISTBOX AND GET ALL SELECTED
For i = 0 To lst_header.ListCount - 1
If lst_header.Selected(i) Then
col_header = lst_header.List(i)
'FIND THE COLUMN HEADER POSITION AND TRANSFER TO NEWSHEET THE DATA
For a = 1 To mySH.Cells(1, Columns.Count).End(xlToLeft).Column
If mySH.Cells(1, a).Value = col_header Then
For b = 1 To mySH.Cells(Rows.Count, a).End(xlUp).Row
oSH.Cells(ns_srow, col_count).Value = mySH.Cells(b, a).Value
ns_srow = ns_srow + 1
Next b
col_count = col_count + 1
ns_srow = 1
Exit For
End If
Next a
End If
Next i
MsgBox "Data Completed"
End
End Sub
Private Sub UserForm_Initialize()
Set mySH = ThisWorkbook.Sheets("Data") 'name of your raw data worksheet
Set oSH = ThisWorkbook.Sheets("Output") 'output worksheet
'Assuming that column header is at row 1
For a = 1 To mySH.Cells(1, Columns.Count).End(xlToLeft).Column
lst_header.AddItem mySH.Cells(1, a).Value
Next a
End Sub

How to collaborate excel listbox as data-grid using search button and shown in the textbox?

I have this code to show all my data in the sheets and load them into the listbox. How can I display and at the same time modify the data into my textbox when I clicking the column inside the listbox?
Private Sub Dloadbtn_Click()
'Load Diret Colo data into Direct Colo Listbox data grid.
Dim ws As Worksheet
Dim rng As Range
Dim hd As Range
Dim i As Long, j As Long, rw As Long
Dim Myarray() As String
'~~> Change your sheetname here
Set ws = Sheets("Colodbs")
'~~> Set you relevant range here
Set rng = ws.Range("A1:N10000")
With Me.Dlistbox
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.count
ReDim Myarray(rng.Rows.count, rng.Columns.count)
rw = 0
For i = 1 To rng.Rows.count
For j = 0 To rng.Columns.count
Myarray(rw, j) = rng.Cells(i, j + 1)
Next
rw = rw + 1
Next
.List = Myarray
'~~> Set the widths of the column here. Ex: For 5 Columns
'~~> Change as Applicable
.ColumnWidths = "50;70;30;50;30;120;120;30;150;30;50;50;70;200"
.TopIndex = 0
End With
End Sub
Private Sub DSearchbtn_Click()
Dim i As Long
Dim rno As Integer
i = 0
Do While Colodbs.Cells(i + 1, 1).Value <> ""
If Colodbs.Cells(i + 1, 1).Value = FbSNtxt.Text Then
rno = Colodbs.Cells(i + 1, 1).Row
GoTo Condition
Else
rno = 0
End If
i = i + 1
Loop
Condition:
If rno <> 0 Then
Colodbs.Cells(rno, 2).Value = FbSNtxt.Text
Else
MsgBox ("No Such number is found")
End If
End Sub
Get clicked value in listbox and write to textbox
Your OP states: "How can I display and at the same time modify the data into my TextBox when I click the column inside the listbox?"
Actually your question is asking how to get the clicked column index in a listbox. Using the helper function getColIndex() you receive this essential information and get easily the clicked data value via the list property for the current listindex.
In the code example below I use the listbox MouseUp event to [1] get the clicked column index via the x argument showing the position in points within the listbox based on a cumulated column widths array created when loading data to the listbox and [2] to modify the textbox FbSNtxt with the clicked data value dListBox.List(Me.dListBox.ListIndex, ColIndex).
Amplifying remark to a tricky method
In order to get the correct column index via the listbox x position, it's necessary to display the ►entire listbox width and to wrap it into a scrolling Frame (cf. listbox and frame settings in the loading procedure Dloadbtn_Click())
Declaration head of UserForm
Option Explicit
Dim cumulWidths() As Double ' cumulated column widths
Dim ColIndex As Integer ' current column index
A. Solution via Listbox MouseUp event
IMO it's the best to use the ► MouseUp event to get the horizontal position (in points) within the listbox via ist x argument as you get the actual listindex only there (and not already in the MouseDown event). Furthermore it gets triggered in any case, whereas the click event only once within the same row.
Private Sub dListBox_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
' Purpose: find search value in first column and write it to corresponding row in range (column B)
' Escape if no data
If dListBox.ListIndex < 0 Then Exit Sub
' [1] get the column index via helper function getColIndex
ColIndex = getColIndex(X)
' [2] get clicked data in column ColIndex and write it into textbox (i.e. modify latest value)
FbSNtxt.Text = Me.dListBox.List(Me.dListBox.ListIndex, ColIndex)
End Sub
►Helper function to get the current column index
Private Function getColIndex(ByVal X) As Integer
' Purpose: get column number analyzing your click position x
' Method: uses array cumulWidths (created by Dloadbtn_Click() calling initCumulWidths)
' Note: called by dListBox_MouseUp event
Dim i As Integer
For i = 0 To UBound(cumulWidths)
If X < cumulWidths(i) Then
getColIndex = i ' return value (listbox index is zero based)
Exit For
End If
Next i
End Function
B. Improved search procedures
Private Sub DSearchbtn_Click()
' Purpose: find search value in first column and write it to corresponding row in range (column B)
' Method: calls procedure dSearch
dSearch
End Sub
Private Sub dSearch()
' Purpose: find value in first column and write it to corresponding row in range (column B)
' Note: called a) by dListBox_DblClick or b) DSearchbtn_Click()
If Me.dListBox.ListCount = 0 Then Exit Sub
Dim i As Long, bFound As Boolean
'Dim Colodbs As Worksheet
'Set Colodbs = ThisWorkbook.Worksheets("Colodbs")
For i = 0 To Me.dListBox.ListCount - 1
If Me.dListBox.List(i, 0) = FbSNtxt.Text Then
' write to cell in found row (i+1, Column B)
Colodbs.Cells(i + 1, 2).Value = FbSNtxt.Text
' correct ListBox value in Column B
Me.dListBox.List(i, 1) = FbSNtxt.Text
bFound = True: Exit For
End If
Next i
If Not bFound Then
MsgBox FbSNtxt.Text & " ColIndex " & ColIndex & " not found.", vbInformation
End If
End Sub
C. Loading procedures
Improved CommandButton code to speed up the loading process by assigning the range in one statement and using subprocedure initCumulWidths.
Private Sub Dloadbtn_Click()
'Purpose: Load Diret Colo data into Direct Colo Listbox data grid.
Dim ws As Worksheet
Dim rng As Range
Dim hd As Range
Dim Myarray As Variant
'~~> Change your sheetname here
Set ws = ThisWorkbook.Worksheets("Colodbs")
'~~> Set yourrelevant range here
Set rng = ws.Range("A1:N10000")
With Me.dListBox
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
' -----------------------------------------------------
' Variant a): assign data to array and array to listbox
' -----------------------------------------------------
' Myarray = rng
' .List = Myarray
' -----------------------------------------------------
' Variant b): assign data in one step to listbox
' -----------------------------------------------------
.List = rng.Value2
'~~> Set the column widths of the column here.
.ColumnWidths = "50;70;30;50;30;120;120;30;150;30;50;50;70;200"
.TopIndex = 0
' =====================================================
' calls subprocedure to cumulate column widths
' -----------------------------------------------------
initCumulWidths
' set total width of listbox to max as it gets shown within a frame
.Width = cumulWidths(UBound(cumulWidths)) + 16
' set scroll width within frame
Frame1.ScrollWidth = .Width ' Frame1.InsideWidth * 4
End With
End Sub
►Subprocedure called by above CommandButton Click event to get cumulated widths
This subprocedure reads the columnwidths described in Points and assigns cumulated values to an array defined in the Userform module's declaration head.
Private Sub initCumulWidths()
' Purpose: calculate cumulated widths and assign values to common userform variable cumulWidths
' Method: splits ColumnWidths property of listbox and sums converted Point values
' Note: called by above Dloadbtn_Click() event
Dim a: a = Split(Me.dListBox.ColumnWidths, ";")
Dim previous As Double, i As Integer
ReDim cumulWidths(UBound(a))
For i = 0 To UBound(a)
cumulWidths(i) = val(Replace(a(i), " Pt", "")) + previous
previous = cumulWidths(i)
Next i
If UBound(a) > 0 Then
Debug.Print cumulWidths(UBound(a))
End If
End Sub
Additional note
As you didn't declare Colodbs as Worksheet in your search procedures, I assume you are using the CodeName property instead of an object.

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

Populate Listbox from 2 combobox selections

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

Resources