Referencing Control Objects Created in Different Module - vba

dr (no circumstantial details): I am trying to reference a control created programmatically in another module, but it is throwing
"Compile Error: Variable not defined."
Here's what I'm trying to do (and perhaps my approach is wrong entirely, if so, let me know): I am trying to create a userform that uses a listbox to act as a menu in which the user can select an item and see the controls (labels, textboxes) relevant to that item. For instance: item "General Information" would a submitter name, department, date, etc; item "Report request" would have report application, desired app name, etc. Selecting "General Information" on the listbox should .visible=true the frame housing the "GI" controls and hide other frames, and selecting "Report Request" item would .visible=false General Information frame but .visible=true Report Request frame, and so on.
Because I want the controls to be placed in the same place on the userform, I created them all programmatically in a Construct module. It looks like this:
Public Sub GeneralInformationCreator()
Dim i As Integer
Dim labelGeneral As MSForms.Label
Dim frameGeneral As MSForms.Frame
Dim frameRequestInformation As MSForms.Frame
Dim frameSubmitterInformation As MSForms.Frame
Dim labelAppSelect As MSForms.Label
Dim listAppSelect As MSForms.ListBox
Dim labelDateSelect As MSForms.Label
Dim comboMonthSelect, comboDaySelect, comboYearSelect As MSForms.ComboBox
Dim labelSubmitterFName, labelSubmitterLName As MSForms.Label
Dim inputSubmitterFName, inputSubmitterLName As MSForms.TextBox
Dim labelSponsorFName, labelSponsorLName As MSForms.Label
Dim inputSponsorFName, inputSponsorLName As MSForms.TextBox
Dim labelDepartmentName As MSForms.Label
Dim inputDepartmentName As MSForms.TextBox
Set labelGeneral = formRequestWizard.Controls.Add("Forms.Label.1", "labelGeneral", True)
With labelGeneral
.Font.Size = 12
.Top = 12
.Left = 192
.Height = 14.25
.Width = 42
.Caption = "General Information"
.Name = labelGeneral
End With
Set frameGeneral = formRequestWizard.Controls.Add("Forms.Frame.1", "frameGeneral", True)
With frameGeneral
.Top = 30
.Left = 192
.Height = 310
.Width = 384
.Caption = ""
.BorderColor = RGB(255, 255, 255)
Set frameRequestInformation = frameGeneral.Controls.Add("Forms.Frame.1", "frameRequestInformation", True)
With frameRequestInformation
.Top = 15
.Left = 15
.Height = 100
.Width = 350
.Caption = "Request Information"
.BorderStyle = 1
.BorderColor = RGB(0, 0, 0)
Set labelAppSelect = frameRequestInformation.Controls.Add("Forms.Label.1", "labelAppSelect", True)
With labelAppSelect
.Caption = "Select an application:"
.Top = 15
.Left = 15
.Width = 100
.Height = 20
.AutoSize = True
End With
The Construct methods are called at runtime using UserForm_Initialize() like this:
Private Sub UserForm_Initialize()
Call Construct.GeneralInformationCreator
Call Construct.ApplicationDetailsCreator
With formRequestWizard.listMenu
.AddItem ("General Information")
.AddItem ("Application Details")
'.Selected(0) = True
End With
End Sub
Here's the issue: in the userform code, I have a listMenu_Change() that looks like this:
Private Sub listMenu_Change()
If (listMenu.Selected(0) = True) Then
labelGeneral.Visible = True
frameGeneral.Visible = True
Else
labelGeneral.Visible = False
frameGeneral.Visible = False
End If
If (listMenu.Selected(1) = True) Then
labelAppDetails.Visible = True
frameAppDetails.Visible = True
Else
labelAppDetails.Visible = False
frameAppDetails.Visible = False
End If
End Sub
Selecting an item from the listbox throws the error from the tl;dr: "Compile Error: Variable not defined" on the first variable "labelGeneral".
I have tried putting all of the script into the userform code window, and putting the control Dims outside of Subs. The research I've done has mostly led to discussions about how to create event handlers for controls created at runtime, but I've decided to place the listMenu object via the object view so I'm not sure that's applicable.

GeneralInformationCreator should be a class in its own right - even the name says so (it's a name like a class/type that is something, not a verb like a procedure that does something).
See all these declarations you have at procedure scope, live and die at procedure scope - they're local variables that nobody else can see:
Dim labelGeneral As MSForms.Label
Dim frameGeneral As MSForms.Frame
Dim frameRequestInformation As MSForms.Frame
Dim frameSubmitterInformation As MSForms.Frame
Dim labelAppSelect As MSForms.Label
Dim listAppSelect As MSForms.ListBox
Dim labelDateSelect As MSForms.Label
Dim comboMonthSelect [As Variant], comboDaySelect [As Variant], comboYearSelect As MSForms.ComboBox
Dim labelSubmitterFName [As Variant], labelSubmitterLName As MSForms.Label
Dim inputSubmitterFName [As Variant], inputSubmitterLName As MSForms.TextBox
Dim labelSponsorFName [As Variant], labelSponsorLName As MSForms.Label
Dim inputSponsorFName [As Variant], inputSponsorLName As MSForms.TextBox
Dim labelDepartmentName As MSForms.Label
Dim inputDepartmentName As MSForms.TextBox
By moving them up to instance level fields (i.e. module-scope variables) in a class module, you can expose a Public Property Get accessor that makes anyone with an instance of that class able to access these objects.
So instead of this:
Private Sub UserForm_Initialize()
Call Construct.GeneralInformationCreator
Call Construct.ApplicationDetailsCreator
With formRequestWizard.listMenu
.AddItem ("General Information")
.AddItem ("Application Details")
'.Selected(0) = True
End With
End Sub
You will have that:
Private generalInfoControls As New GeneralInformationCreator
Private appDetailsControls As New ApplicationDetailsCreator
Private Sub UserForm_Initialize()
generalInfoControls.Create Me.Controls
appDetailsControls.Create Me.Controls
With Me.listMenu 'don't refer to the default instance in the form's code-behind... EVER!
.AddItem "General Information"
.AddItem "Application Details"
'.Selected(0) = True
End With
End Sub
Where Create is a procedure that takes a Controls parameter - by giving it Me.Controls we are passing the collection of controls for the current instance of the user form, so the method can look like this:
Public Sub Create(ByVal parent As Controls)
Set labelGeneral = parent.Add("Forms.Label.1", "labelGeneral", True)
With labelGeneral
.Font.Size = 12
.Top = 12
.Left = 192
.Height = 14.25
.Width = 42
.Caption = "General Information"
.Name = labelGeneral
End With
'...
Notice it's no longer coupled with the default instance of your formRequestWizard form, and will work with whatever form instance's Controls collection it's given.
I'm not sure if you're still following my train of thought here, but this means your handler can now do this:
Private Sub listMenu_Change()
generalInfoControls.SetVisibility listMenu.Selected(0)
appDetailsControls.SetVisibility listMenu.Selected(1)
End Sub
That implies that your XxxxCreator classes have a SetVisibility method that looks something like this:
Public Sub SetVisibility(ByVal isVisible As Boolean)
labelGeneral.Visible = isVisible
frameGeneral.Visible = isVisible
'...

Define all your variables like this:
Dim comboMonthSelect As MSForms.Combobox
Dim comboDaySelect As MSForms.Combobox
Dim comboYearSelect As MSForms.ComboBox
With
Dim comboMonthSelect, comboDaySelect, comboYearSelect As MSForms.ComboBox,
only comboYearSelect is declared as a MSForms.ComboBox and the other two as Variant.
Then write Option Explicit and make sure that every variable is defined in every Sub/Function. In your case is should be:
Dim labelGeneral As MSForms.Label
in the Sub listMenu_Change, where the error is happening. MSDN Option Explicit reference.
As a quick and dirty way, you can do something like this:
If (listMenu.Selected(0) = True) Then
Controls("labelGeneral").Visible = True
Controls("someOtherName").Visible = True
End If
However, this breaks completely any MVC pattern and may cause any non-VBA people to feel dizzy and to publish things like this:
If you want to do it the "correct way", read this article form the ex-StackOverflow Documentation, it is quite nice:
http://www.riptutorial.com/vba/example/19036/best-practices.

Related

How to hide an ActiveX Command Button when printing a word document?

I have an ActiveX Command Button that when pressed opens a userform to allow data entry into the word document. This button needs to remain visible when working on the document but not visible when printing.
How can I hide/make invisible only when printing?
Unlike in Excel VBA where the properties include an option to 'PrintObject', word VBA does not have this functionality. The best I have been able to do is delete the button after being clicked but this is not really what I want.
'Needs to hide button only on printing, not delete it
UserForm2.Show
CommandButton1.Select
Selection.Delete
I assume you are having ActiveX Command Button in word and using user form entered data gets feed in corresponding fields and you are closing user form and then trying to print document and printed file should not have ActiveX Command Button in it
Paste the following code into CommandButton_Click event
Private Sub CommandButton1_Click()
With ActiveDocument
.Shapes(1).Visible = msoFalse
.PrintOut Background:=False
.Shapes(1).Visible = msoTrue
End With
End Sub
#ille P. - You should post a new question, perhaps with a link to this one.
Try the Shapes collection as well as inlineshape collection.
The following is suggested by Ibby on the Microsoft Word MVP VBA FAQ pages:
Private Sub CommandButton1_Click()
With ActiveDocument
.Shapes(1).Visible = msoFalse
.PrintOut Background:=False
.Shapes(1).Visible = msoTrue
End With
End Sub
So translating to the collection:
Dim oShape as Shape
For Each oShape in ActiveDocument.Shapes
oShape.Visible = False
Next oShape
That would, though hide all shapes, not just your buttons.
You could add bookmarks to your buttons and use them as a filtering Range.
After investigating what happens with the CommandButtons, I detected that in the document they are included (if it has not been edited to be a Shape) in the InLineShape collection by being encapsulated in an InLineShape object.
It would be great if we could directly typecast from CommandButton to InShapeLine but I think Microsoft doesn't allow it, too bad.
The idea is to create two class modules:
One will be an event manager to capture the 'before printing' event and another.
The other will be an encapsulation of an InLineShape object that will have the methods to make the object visible, taking advantage of the Brightness property of the PictureFormat object.
CLASS clsButton
Option Explicit
Private M_ishpButton As InlineShape
Public Property Get button() As InlineShape
Set button = M_ishpButton
End Property
Public Property Set button(oObj As InlineShape)
Set M_ishpButton = oObj
End Property
Public Property Get Visible() As Boolean
Visible = Not bIsHidden
End Property
Public Property Let Visible(bValue As Boolean)
Dim oPictureFormat As PictureFormat
Set oPictureFormat = M_ishpButton.PictureFormat
If bValue Then
Call show
Else
Call hide
End If
End Property
Private Function bIsHidden() As Boolean
Dim oPictureFormat As PictureFormat
Set oPictureFormat = M_ishpButton.PictureFormat
If oPictureFormat.Brightness = 1 Then bIsHidden = True: Exit Function
bIsHidden = False
End Function
Private Sub hide()
Dim oPictureFormat As PictureFormat
Set oPictureFormat = M_ishpButton.PictureFormat
oPictureFormat.Brightness = 1
End Sub
Private Sub show()
Dim oPictureFormat As PictureFormat
Set oPictureFormat = M_ishpButton.PictureFormat
With oPictureFormat
.Brightness = 0.5
.Contrast = 0.5
End With
End Sub
CLASS clsEvents
Option Explicit
Public WithEvents appWord As Word.Application
Public WithEvents docWord As Word.Document
Private m_button As New clsButton
Private Sub appWord_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
'If process is not cancel and button is not visible
With m_button
If Cancel = False And .Visible = True Then
.Visible = False
End If
End With
End Sub
Private Sub appWord_WindowDeactivate(ByVal Doc As Document, ByVal Wn As Window)
'If button is not visible then set to true
With m_button
If .Visible = False Then
.Visible = True
End If
End With
End Sub
Public Property Set button(oObj As clsButton)
Set m_button = oObj
End Property
CLASS ThisDocument
Now, in ThisDocument class should create objects and linking
Dim oEventsManager As New clsEvents
Dim oEditedButton As New clsButton
Const BUTTON_LINKS As String = "cmdUpdateLinks" 'For example
Dim oInShpDoc As Word.InlineShape, oOleDoc As Word.OLEFormat, oInShapesDoc As Word.InlineShapes
Public Sub Set_Manager_Events()
Set oEventsManager.appWord = Word.Application 'ThisDocument.Application
Set oEventsManager.docWord = ThisDocument
Set oInShpDoc = FNC_oGet_Button_Variable(BUTTON_LINKS)
If Not oInShpDoc Is Nothing Then
Set oEditedButton.button = oInShpDoc
Set oEventsManager.button = oEditedButton
End If
End Sub
'###### EVENTOS OF BUTTON
Private Sub cmdUpdateLinks_Click()
If oEventsManager.appWord Is Nothing Then Call Set_Manager_Events
Call UpdateLinks ' Is one example
End Sub
Public Function FNC_oGet_Button_Variable(sCodeName As String) As InlineShape
Dim oForm As InlineShape, oFormsInLine As InlineShapes, oOLEFormat As OLEFormat
Set oFormsInLine = ThisDocument.InlineShapes
If oFormsInLine .Count < 1 Then GoTo bye
i = 0
For Each oForm In oFormsInLine
With oForm
Set oOLEFormat = .OLEFormat
If Not oOLEFormat Is Nothing Then
If InStr(1, oOLEFormat.ClassType, "CommandButton") > 0 Then
If .OLEFormat.Object.Name = sCodeName Then
Set FNC_oGet_Button_Variable= oForm
Exit Function
End If
End If
End If
End With
Next
bye:
Set FNC_oGet_Button_Variable = Nothing
End Function
With this you will can hide button for printing.

Excel VBA Userform - Execute Sub when something changes on dynamic comboBox

I created a userform where is a comboBox
Depending which result user chooses, new comboBoxes will appear with new choices to choose from.
Below is the latest test I tried.
How do I make it so that when user changes the value in the new comboBox it will execute a premade function/sub
Code in Form
Dim WB As Workbook
Dim structSheet As Worksheet
Dim tbCollection As Collection
Private Sub UserForm_Activate()
Dim ignoreList(3) As String
ignoreList(0) = "main"
ignoreList(1) = "configurator"
ignoreList(2) = "create structure"
Set WB = Excel.ActiveWorkbook
For Each sheet In WB.Worksheets
If Not isInTable(ignoreList, sheet.Name) Then
supercode_box.AddItem sheet.Name
End If
Next
End Sub
Private Sub supercode_box_Change()
If Not sheetExists(supercode_box.text) Then Exit Sub
Set structSheet = WB.Worksheets(supercode_box.text)
'Dim obj As clsControlBox
topPos = 10
leftPos = 54
ID = 1
' For ID = 1 To 2
Set ComboBox = createProductForm.Controls.add("Forms.ComboBox.1")
With ComboBox
.Name = "comboBoxName"
.Height = 16
.Width = 100
.Left = leftPos
.Top = topPos + ID * 18
.AddItem "test"
.Object.Style = 2
End With
Set tbCollection = New Collection
tbCollection.add ComboBox
'Next ID
End Sub
code in class1 module
Private WithEvents MyTextBox As MSForms.controlBox
Public Property Set Control(tb As MSForms.controlBox)
Set MyTextBox = tb
MsgBox ("did it get here?")
End Property
Public Sub comboBoxName_Change()
MsgBox ("start working ffs")
End Sub
Public Sub comboBoxName()
MsgBox ("?? maybe this?")
End Sub
Judging on your code, the easiest way is to write the value you need in a separate worksheet.
Then make a check, whether it is changed and if it is changed, write the new value and fire the procedure that you want.
In short, something like this:
Sub TestMe()
If Worksheets("Special").Cells(1, 1) = WB.Worksheets(supercode_box.Text) Then
Call TheSpecificSub
End If
Worksheets("Special").Cells(1, 1) = WB.Worksheets(supercode_box.Text)
End Sub

Dynamic created User-Form, with 2 dependent Combo-Boxes

I am trying to create a dynamic User_form, where all the Controls are created at Run-Time.
I have 2 array of Combo-Boxes, first array of Combo-Boxes is "Catgeory" (CatCBArr) , and the second array of Combo-Boxes is "Item" (ItemCBArr).
I want, that once I select a value from the first Combo-Box of "Category", let's say CatCBArr(0), that only the related Items in ItemCBArr(0) will be displayed.
Issue: I can't figure out how to modify the second Combo-box (ItemCBArr(0)) according to the value selected in the first Combo-box (CatCBArr(0))
User_Form Code (relevant section)
Option Explicit
Dim ItemsNumofRows As Long
Dim QtyTB As MSForms.TextBox
Dim CatCB As MSForms.ComboBox
Dim ItemCB As MSForms.ComboBox
Dim Key As Variant
' dynamic Form controls (related to new Classes)
Dim CatCBArr() As New cComboBox
Dim ItemCBArr() As New cComboBox
Dim QtyTBArr() As New cTextBox
Private Sub UserForm_Initialize()
' reset flags
ItemsNumofRows = 5
TasksNamesUpd = False
TasksColUpd = False
ItemsRows_ControlsInit '<-- upload all Controls at run-time
Check_FormHeight
End Sub
'======================================================
Private Sub ItemsRows_ControlsInit()
For ItemRow = 0 To ItemsNumofRows
' add Category Combo-boxes
Set CatCB = Me.Controls.Add("Forms.ComboBox.1", "Cb" & ItemRow, True)
With CatCB
' loop through Dictionay items (view category)
For Each Key In Dict.Keys
.AddItem Key
Next Key
.SpecialEffect = fmSpecialEffectSunken
.Left = 40
.Width = 100
.Height = 18
.Top = 54 + 20 * ItemRow
ReDim Preserve CatCBArr(0 To ItemRow)
Set CatCBArr(ItemRow).ComboBoxEvents = CatCB
End With
' add Item Combo-boxes
Set ItemCB = Me.Controls.Add("Forms.ComboBox.1", "Cb_" & ItemRow, True)
With ItemCB
.SpecialEffect = fmSpecialEffectSunken
.Left = 160
.Width = 100
.Height = 18
.Top = 54 + 20 * ItemRow
ReDim Preserve ItemCBArr(0 To ItemRow)
Set ItemCBArr(ItemRow).ComboBoxEvents = ItemCB
End With
Next ItemRow
End Sub
cComboBox Class Code
Public WithEvents ComboBoxEvents As MSForms.ComboBox
Private Sub ComboBoxEvents_Change()
Dim CBIndex As Long
' get for ID number (row number), from third character in String Name.
' e.g "Cb1" will result 1)
CBIndex = CInt(Mid(ComboBoxEvents.Name, 3))
' ??? How do I get the Value, and update the second combo-box Items
Select Case ComboBoxEvents.Value
End Select
End Sub
GUI User_Form screen-shot
Alright, here's the basics.
Your class cCombobox I replicated as follows:
Private WithEvents ComboBoxEvents As MsForms.ComboBox
Private Sub ComboBoxEvents_Change()
Select Case ComboBoxEvents.value
Case "1":
UserForm1.DependentBox.Clear
UserForm1.DependentBox.AddItem "3"
UserForm1.DependentBox.AddItem "4"
Case "2":
UserForm1.DependentBox.Clear
UserForm1.DependentBox.AddItem "5"
UserForm1.DependentBox.AddItem "6"
Case Default:
'Do Nothing
End Select
End Sub
Public Property Let box(value As MsForms.ComboBox)
Set ComboBoxEvents = value
End Property
Public Property Get box() As MsForms.ComboBox
Set box = ComboBoxEvents
End Property
Next, I created a UserForm1 that adds 2 comboboxes, one of which I add to a local variable of type cComboBox.
Public DependentBox As MsForms.ComboBox
Private InitialBox As cComboBox
Private Sub UserForm_Initialize()
Dim cBox As MsForms.ComboBox
Set InitialBox = New cComboBox
Set cBox = Me.Controls.Add("Forms.ComboBox.1", "initial", True)
With cBox
.Left = 6
.Width = 100
.Height = 25
.Top = 6
.AddItem "1"
.AddItem "2"
End With
InitialBox.box = cBox
Set DependentBox = Me.Controls.Add("Forms.ComboBox.1", "dependent", True)
With DependentBox
.Top = 6
.Left = 126
.Height = 25
.Width = 100
End With
End Sub
Even though this works, the above approach is not very clean, since your class is not self-contained - It has to be aware of the UserForm. A better way would be to link the boxes in the class already and then just pass them from the Userform when you initialize your arrays of controls.
Then it would be:
cComboBox class:
Private WithEvents p_ComboBoxEvents As MSForms.ComboBox
Private p_DependBox As MSForms.ComboBox
Private Sub p_ComboBoxEvents_Change()
Select Case p_ComboBoxEvents.value
Case "1":
p_DependBox.Clear
p_DependBox.AddItem "3"
p_DependBox.AddItem "4"
Case "2":
p_DependBox.Clear
p_DependBox.AddItem "5"
p_DependBox.AddItem "6"
Case Default:
'Do Nothing
End Select
End Sub
Public Property Let TriggerBox(value As MSForms.ComboBox)
Set p_ComboBoxEvents = value
End Property
Public Property Get TriggerBox() As MSForms.ComboBox
Set TriggerBox = p_ComboBoxEvents
End Property
Public Property Let DependBox(value As MSForms.ComboBox)
Set p_DependBox = value
End Property
Public Property Get DependBox() As MSForms.ComboBox
Set DependBox = p_DependBox
End Property
Here you see you already link the boxes in a self-contained class.
In the event handler you could create a lookup for the values, etc.
Then in the UserForm1 code you initialize them as follows:
Option Explicit
Private LinkedComboBox As cComboBox
Private Sub UserForm_Initialize()
Dim cBox As MSForms.ComboBox
Set LinkedComboBox = New cComboBox
Set cBox = Me.Controls.Add("Forms.ComboBox.1", "initial", True)
With cBox
.Left = 6
.Width = 100
.Height = 25
.Top = 6
.AddItem "1"
.AddItem "2"
End With
LinkedComboBox.TriggerBox = cBox
Set cBox = Me.Controls.Add("Forms.ComboBox.1", "dependent", True)
With cBox
.Top = 6
.Left = 126
.Height = 25
.Width = 100
End With
LinkedComboBox.DependBox = cBox
End Sub
EDIT:
Based on the fact that it needs to be an array, you can amend the userform as follows:
Option Explicit
Private LinkedComboBox(0 To 4) As cComboBOx
Private Sub UserForm_Initialize()
Dim cBox As MSForms.ComboBox
Dim i As Integer
For i = 0 To 4
Set LinkedComboBox(i) = New cComboBOx
Set cBox = Me.Controls.Add("Forms.ComboBox.1", "initial", True)
With cBox
.Left = 6
.Width = 100
.Height = 25
.Top = 6 + (i * 25)
.AddItem "1"
.AddItem "2"
End With
LinkedComboBox(i).TriggerBox = cBox
Set cBox = Me.Controls.Add("Forms.ComboBox.1", "dependent", True)
With cBox
.Top = 6 + (i * 25)
.Left = 126
.Height = 25
.Width = 100
End With
LinkedComboBox(i).DependBox = cBox
Next i
End Sub
In the array you can access each box as LinkedComboBox(i).DependBox and LinkedComboBox(i).TriggerBox. You won't need the two seperate arrays anymore, since everything is already contained in this LinkedComboBox array

Creating and populating combobox from vba module

In Excel 2010, I am trying to create a userform dynamically in my vba code. However, after running the code, the list does not show when opening the dropdown. If I put a breakpoint before ".show", I can inspect the form in the design window and I see that the list is populated. Does the .show method clear the list or what is happening? The solutions I have found focus on populating from cell ranges or placing the ".additem"s in the initialization code of the userform. I don't want to do anything that requires me to create a second file. Everything should be in this vba code if possible. Any help appreciated. My code follows.
Sub make_combobox_form()
' Makes a form with only a simple combobox
Dim myForm As Object
Dim cb As MSForms.ComboBox
'Create the User Form
Set myForm = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
With myForm
.Properties("Width") = 400
.Properties("Height") = 300
End With
Set cb = myForm.Designer.Controls.Add("Forms.ComboBox.1")
With cb
.Top = 100
.Left = 100
.Height = 20
.Width = 200
.AddItem "Item_1"
.AddItem "Item_2"
.AddItem "Item_3"
.value = "Item_1"
End With
VBA.UserForms.Add(myForm.name).Show
ThisWorkbook.VBProject.VBComponents.Remove myForm
End Sub
Sub make_combobox_form()
'Create the User Form as component first
Dim myFormComponent As VBComponent
Set myFormComponent = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
' Get reference to user form from the UserForms collection then
Dim cb As MSForms.ComboBox
Dim myForm As Variant
Set myForm = VBA.UserForms.Add(myFormComponent.Name)
With myForm
.Width = 400
.Height = 300
End With
Set cb = myForm.Controls.Add("Forms.ComboBox.1")
With cb
.Top = 100
.Left = 100
.Height = 20
.Width = 200
.AddItem "Item_1"
.AddItem "Item_2"
.AddItem "Item_3"
.Value = "Item_1"
End With
myForm.Show
ThisWorkbook.VBProject.VBComponents.Remove myFormComponent
End Sub

Excel VBA ComboBox Identification

I have 4+ ComboBoxes on a user form. When they fire, they fire the same event. What I am trying to do is find out which ComboBox triggered the event. The ComboBoxes are created depending on how many components there are. The code generating the ComboBoxes is shown below:
For j = 0 To UBound(ComponentList) - 1
'Set Label
num = j + 1
Set control = UserForm1.Controls.Add("Forms.Label.1", "ComponentLabel" & CStr(num) & ":", True)
With control
.Caption = "Component " & CStr(num)
.Left = 30
.Top = Height
.Height = 20
.Width = 100
.Visible = True
End With
'set ComboBox
Set combo = UserForm1.Controls.Add("Forms.ComboBox.1", "Component" & num & ":", True)
With combo
.List = ComponentList()
.Left = 150
.Top = Height
.Height = 20
.Width = 50
.Visible = True
Set cButton = New clsButton
Set cButton.combobox = combo
coll.Add cButton
End With
Height = Height + 30
Next j
This works well and I can get the value the user selected, BUT I can not find which ComboBox has been used. This code below is the event that it fires (clsButton):
Public WithEvents btn As MSForms.CommandButton
Public WithEvents combobox As MSForms.combobox
Private combolist() As String
Private Sub btn_Click()
If btn.Caption = "Cancel" Then
MsgBox "Cancel"
Unload UserForm1
Variables.ComponentSelectionError = False
ElseIf btn.Caption = "Enter" Then
MsgBox "enter"
Unload UserForm1
Variables.ComponentSelectionError = True
End If
End Sub
Private Sub combobox_Click()
MsgBox combobox.Value
End Sub
This bit of code above was kindly worked on by Doug Glancy to get the events working with the code generated ComboBoxes.
How do I get the ComboBox that triggered the event? i.e. the name or some other form of identification.
I have managed to finally answer my own question after searching over 500 webpages (took a long time)
this is what i used and it works and fires when the certain comboboxes are clicked:
Private Sub combobox_Click()
MsgBox combobox.Value
If combobox = UserForm1.Controls("Component0") Then
MsgBox "Success1"
End If
If combobox = UserForm1.Controls("Component1") Then
MsgBox "Success2"
End If
End Sub
hopefully this can be used for other people who need it.
Within the class .Name will not appear in the intellisense list for the combobox as MSForms.ComboBox does not actually have a name property itself (take a look at it in the F2 object browser), rather that property is provided by the Control base class:
Private Sub combobox_Click()
MsgBox combobox.Value
MsgBox combobox.Name '// no hint but still works
'//cast to a Control to get the formal control interface with .Name
Dim ctrl As Control: Set ctrl = combobox
MsgBox ctrl.Name
End Sub
Maybe reference back to btn.Combobox again? Similar to how you assigned the combobox to the button in the first place, but then in reverse:
set combobox = btn.Combobox
Is there a reason you don't just add a property to your custom class and set that property when you register in the Collection?
For j = 0 To UBound(ComponentList) - 1
'Set Label
num = j + 1
Set control = UserForm1.Controls.Add("Forms.Label.1", "ComponentLabel" & CStr(num) & ":", True)
With control
.Caption = "Component " & CStr(num)
.Left = 30
.Top = Height
.Height = 20
.Width = 100
.Visible = True
End With
'set ComboBox
Set combo = UserForm1.Controls.Add("Forms.ComboBox.1", "Component" & num & ":", True)
With combo
.List = ComponentList()
.Left = 150
.Top = Height
.Height = 20
.Width = 50
.Visible = True
Set cButton = New clsButton
'*******EDIT********
with cButton
.combobox = combo
.Indx = j
end With 'cButton
'*******************
coll.Add cButton
End With
Height = Height + 30
Next j
Class Module
Public WithEvents btn As MSForms.CommandButton
Dim WithEvents mCombobox As MSForms.comboBox
Private combolist() As String
'*******EDIT********
Public Indx As Long
Property Let comboBox(cb As MSForms.comboBox)
Set mCombobox = cb
End Property
'*******************
Private Sub btn_Click()
If btn.Caption = "Cancel" Then
MsgBox "Cancel"
Unload UserForm1
Variables.ComponentSelectionError = False
ElseIf btn.Caption = "Enter" Then
MsgBox "enter"
Unload UserForm1
Variables.ComponentSelectionError = True
End If
End Sub
Private Sub mCombobox_Click()
'*******EDIT********
MsgBox "Combobox " & Indx & Chr(9) & mComboBox.Value
'*******************
End Sub
Since you require many to one mapping of the events, I assume you have a common call-back in your actual code, so you could also do this...
In a Standard Module
Public Sub cbCallBack(ocb As clsButton)
MsgBox ocb.Indx
End Sub
In clsButton (replacing the event handler)
Private Sub mCombobox_Click()
cbCallBack Me
End Sub