Adding OnChange event to dynamically created vba form controls - vba

I have a form in excel, where I need to have dynamically created Comboboxes and Listboxes. So, the idea is, each listbox is linked to combobox. The first ones are set up by default, and user can press the "Add" button, if he needs to add another combo + list boxes. So the code for "Add" button is following:
Private Sub AddCountry_Click()
aaa = "a"
Set comb = Controls.Add("Forms.Combobox.1", "CountryList" & Val(CountryLabel.Caption) + 1)
With comb
.Top = CountryList1.Top
.Width = CountryList1.Width
.Height = CountryList1.Height
.Left = (CountryList1.Width + 3) * Val(CountryLabel.Caption) + CountryList1.Left
.AddItem ("--Choose country--")
For i = 3 To 20
.AddItem Worksheets("Countries").Range("B" & i).Value
Next i
.Text = "--Choose country--"
End With
Set listb = Controls.Add("Forms.Listbox.1", "Countries" & Val(CountryLabel.Caption) + 1)
With listb
.Top = Countries1.Top
.Width = Countries1.Width
.Height = Countries1.Height
.Left = (Countries1.Width + 3) * Val(CountryLabel.Caption) + Countries1.Left
.ColumnCount = 2
.MultiSelect = 1
End With
CountryLabel.Caption = Val(CountryLabel.Caption) + 1
End Sub
The idea is, that Comboboxes must will have names "CountryList" and a number, that is stored in invisible label (to which is added +1 every time the button is bressed), so it will be CountryList1, CountryList2, etc. Same for listboxes.
So the thing is, that comboboxes are made and values (country names) are added correctly. But I did not get, how to use them after it? The thing, that I need is to - when a combobox is changed (user selects different country), the list box below must be filled with certain values (different for each country).
I assume, the problem might be in defining the name for combo/list box. So is it possible to add dynamical names (CountryList1, CountryList2, etc) and then somehow add OnChange Events? Thanks in advance.

Here's an example for the ComboBox. You can base the listbox of of it, since the principle is the exact same.
First create a class named cComboBox and put this code in there:
Private WithEvents p_ComboBoxEvents As MSForms.ComboBox
Private Sub p_ComboBoxEvents_Change()
'Here you can handle the events.
End Sub
Public Property Let Box(value As MSForms.ComboBox)
Set p_ComboBoxEvents = value
End Property
Public Property Get Box() As MSForms.ComboBox
Set Box= p_ComboBoxEvents
End Property
Next, in your existing code, you can add this cComboBox and just put the Combobox you're adding already:
'Add the custom box!
Private customBox as cComboBox
Private Sub AddCountry_Click()
aaa = "a"
Set comb = Controls.Add("Forms.Combobox.1", "CountryList" & Val(CountryLabel.Caption) + 1)
With comb
.Top = CountryList1.Top
.Width = CountryList1.Width
.Height = CountryList1.Height
.Left = (CountryList1.Width + 3) * Val(CountryLabel.Caption) + CountryList1.Left
.AddItem ("--Choose country--")
For i = 3 To 20
.AddItem Worksheets("Countries").Range("B" & i).Value
Next i
.Text = "--Choose country--"
End With
Set customBox = New cComboBox
customBox.Box = comb
End Sub
Of course you might want to create an array of these so you can have as many as you want, regardless of how they're named. However: you'll see that if you change the added ComboBox value the p_ComboBoxEvents_Change will trigger.

Related

Microsoft ActiveX Controls

Is there a way to change the index value of a ActiveX Button that inserted onto a spreadsheet. I currently have four buttons and two are hidden and two are visible. I would like to re-order the them to not have a large gap between objects. I have some VBA code that runs when the document is opened to ensure that they are the right size and location. Because it loops through the OLEObjects Collection; it will not matter what order they are in on the spreadsheet they will always appear with a gap because of the index value in the OLE Object collection. Below is the code:
Private Sub Workbook_Open()
Application.ErrorCheckingOptions.EvaluateToError = False
ActiveWorkbook.Worksheets("SITE").Activate
Dim button As OLEObject
Dim name As String, top As Integer
top = 15
For Each button In ActiveWorkbook.Worksheets("SITE").OLEObjects
Debug.Print button.name & " " & button.ZOrder
name = button.name
If button.OLEType = xlButtonOnly And InStr(name, "btn") = 1 Then
With button
.Height = 21.75
.Width = 174.75
.Left = 1114.5
.top = top
End With
top = top + 30
End If
Next button
End Sub
If you give them proper names with an integer code in it reflecting their intended position (e.g.: "btn...01", "btn...02",...) then you could try this code (sorry for not being able to format it as code by now):
Private Sub Workbook_Open()
Application.ErrorCheckingOptions.EvaluateToError = False
ActiveWorkbook.Worksheets("SITE").Activate
Dim button As OLEObject
Dim name As String
Dim btnRnk As Long
For Each button In ActiveWorkbook.Worksheets("SITE").OLEObjects
name = button.name
If button.OLEType = xlButtonOnly And InStr(name, "btn") = 1 Then
btnRnk = CLng(Right(name,2))
With button
.Height = 21.75
.Width = 174.75
.Left = 1114.5
.top = 15 + (btnRank - 1) * 30
End With
End If
Next button
End Sub

RE: Getting the name of the object onFocus & Adding Caption to a Button dynamically Created

I have written VBA code for Dynamically creating textboxes and buttons. The following is the code when I hit an "Add" button on the userform.
Dim oTxtBox As Control
Dim oBrwsBtn As Control
Dim oCaption As Control
Dim oTxtLen As Integer
oTxtLen1 = TextBox1.Width
oTxtBrth1 = TextBox1.Height
oTxtPos1 = TextBox1.Left
oButLen = CommandButton1.Width
oButBrth = CommandButton1.Height
oTxtLen2 = TextBox2.Width
oTxtBrth2 = TextBox2.Height
If i = Empty Then
i = 1
End If
Set oTxtBox = Me.Controls.Add("Forms.TextBox.1")
Set oBrwsBtn = Me.Controls.Add("Forms.CommandButton.1")
Set oCaption = Me.Controls.Add("Forms.TextBox.1")
With oTxtBox
.Left = oTxtPos1
.Top = oTxtBrth1 + 18 + (oTxtBrth + 18) * (i - 1)
.Width = oTxtLen1
.Height = oTxtBrth1
End With
With oBrwsBtn
.Left = oTxtPos1 + oTxtLen1 + 18
.Top = oTxtBrth1 + 18 + (oTxtBrth + 18) * (i - 1)
.Width = oButLen
.Height = oButBrth
End With
With oCaption
.Left = oTxtPos1 + oTxtLen1 + 18 + oButLen + 18
.Top = oTxtBrth1 + 18 + (oTxtBrth + 18) * (i - 1)
.Width = oTxtLen2
.Height = oTxtBrth2
End With
i = i + 1
Q1 Now How to Edit the caption of the browse Button which I create dynamically No method .Caption with oBrWsBtn
And Q2: How to get the value when the focus is changed
For example When I click on 'TextBox1' Object. A variable should assign itself with the name (i. e. var(str) = focus object name)
Thanks in advance
Q1: Why don't you try defining the control type more specifically and see if that permits you to use the .Caption method. Don't understand why it's not working now, but maybe the Control object type doesn't have the .Caption method because not all controls have captions.
Try:
'at the top of the sub:
Dim oBrwsBtn as CommandButton
'then later
Set oBrwsBtn = Me.Controls.Add("Forms.CommandButton.1")
Another option to consider would be to create these buttons and just have them be invisible until needed. But that assumes you don't actually have a good reason to create them dynamically, which you may have.
Q2: Use a control_AfterUpdate sub:
'Dim this wherever appropriate for scope. Could be in the afterupdate sub if that works.
dim TextBox1_Value as string
Private Sub TextBox1_AfterUpdate()
TextBox1_Value = TextBox1.value
End Sub

Why can't I get textbox.text values from code generated textboxes

I have a project where I have a form "userform1" which only has a "GO" button and an "EXIT" button on it to begin with. I solicit user input via an inputbox for a number. I use code to then populate the form with that number of labels and textboxes and display the form to allow editting of the created textboxes. Everything works fine up to this point.
I then want the "GO" button to populate an array with the textbox.text values for use elsewhere. This is where I am having a problem.
I tried retrieving the text like I normally do (something like; Xstring = UserForm1.Box1.Text), that didn't work. So then I cycled thru the form controls; UserForm1.Controls.index(), looking for my Textbox .names to confirm they existed. I found them, so I singled one out, index(3), .name = "Box1". See below:
Dim tText As String
tText = UserForm1.Box1.Text
MsgBox tText
returns an ERROR, 'method or data member not found'
But if I change it to this:
Dim x As Object
Dim tText As String
Set x = UserForm1.Controls.Item(3)
tText = x.Text
MsgBox tText
The msgbox returns the .text value
So, QUESTION is, why can't I simply address it normally? I don't want to have to go through all the extra steps to figure out index numbers vs names to populate my array.
for reference, below is partial code of my sub for creating labels/textboxes:
For i = Data(x, 1) To z
Lab = "forms.label.1"
Box = "forms.textbox.1"
Set newlabel = UserForm1.Controls.Add(Lab)
Set newtextbox = UserForm1.Controls.Add(Box)
lbl1 = ("Label" + CStr(i))
With newlabel
.Name = lbl1
.Caption = "No. " + CStr(i)
.Left = Data(x, 3)
.top = top
.Height = 20
.Width = 30
End With
lbl2 = ("Box" + CStr(i))
With newtextbox
.Name = lbl2
.Text = CStr(i)
.Left = (Data(x, 3) + 35)
.top = top
.Height = 20
.Width = 36
End With
top = top + 25
Next i
UserForm1 has no method or data member of that name before run-time. Therefore the VBA interpreter cannot interpret what is meant.
You can nevertheless access the item via through its collection of child controls indexed on either index number or control name, like:
Dim myTxtBox As MSForms.TextBox
Me.Controls.Add bstrProgId:="Forms.Textbox.1", Name:="My Runtime Textbox"
Set myTxtBox = Me.Controls("My Runtime Textbox")
myTxtBox.Text = "Hello"
After reading the comments from Cor_Blimey, this is what I now have working in the code for retrieving my program generated textbox.text data:
Dim i As Integer, num As Integer, ctr As Integer
Dim oCntl As Object
Dim tText() as String
num = UserForm1.Controls.Count
ReDim tText(num)
For Each Control In UserForm1.Controls 'loops thru each control
If TypeOf Control Is TextBox Then
ctr = ctr + 1
tText(ctr) = Control.Text
End If
Next Control
ReDim Preserve tText(ctr)
The .text is being written to the array sequentially (by index#, the order textboxes were created)
In the future, I might consider 2 values in the array, 1 for .name and 1 for .text, just to be sure.

'Object Required' error when referencing dynamically created controls, stored in a collection, in the class of another dynamically created control

I am using a spin button to cycle through dates of a phase. When I call an item from a collection called customtextboxcollection with its index value, I get an "Object Required" error. Both the spin button and the text box whose value changes are dynamically created controls displayed on a UserForm called UserForm1.
The sub to create the items in customtextbox collection run before the spin button is clicked:
Dim customtextboxcollection As Collection
Dim spinbuttoncollection As Collection
Public Sub ComboBox1_Click() 'When a person is selected to enter hours for an employee from a combobox, it triggers the creation of the controls
Sheet1.Activate
CommandButton1.Enabled = True 'Enable the OK and Apply buttons when personnel title is selected.
UserForm1.Label2.Visible = True
UserForm1.ratebox.Visible = True
QuantityLabel.Visible = True
quantitybox.Visible = True
'The variables below are to access the table where I store saved information regarding the project phases I will add hours to.
Dim counter As Integer
counter = 6 'The index of the first row for phases
Dim phasecolumn As Integer
phasecolumn = 3 'The index of the column containing the phases
Dim checkboxnumber As Integer
checkboxnumber = 1 'This is the number needed to distinguish between the checkboxes that appear/disappear.
phasestartcolumn = 4
phaseendcolumn = 5
Dim customtextboxHandler As cCustomTextBoxHandler
Set customtextboxcollection = New Collection 'Sets the previously created collection
Dim spinbuttonHandler As cSpinButtonHandler 'This is my spin button handler class
Set spinbuttoncollection = New Collection 'Sets the previously created collection
'This Do-Loop locates a row on the table with saved information
Do
If (Sheet3.Cells(savedpersonnelrow, savedpersonnelcolumn) = ComboBox1.Value) Then
storagerow = savedpersonnelrow
lastcomboboxvalue = ComboBox1.Value
Exit Do
End If
savedpersonnelrow = savedpersonnelrow + 1
Loop Until (savedpersonnelrow = 82)
Sheet1.Activate
'These sections create the controls depending on the number of phases saved.
Set spin = UserForm1.Controls.Add("Forms.SpinButton.1")
With spin
.name = "SpinButton" & checkboxnumber
.Left = 365
.Top = topvalue + 6
.Height = 15
.Width = 40
'.Value = Sheet3.Cells(storagerow, savedphasecolumn + checkboxnumber)
'Sheet1.Activate
Dim phasestart As Date
phasestart = Sheet1.Cells(counter, phasestartcolumn).Value
Dim phaseend As Date
phaseend = Sheet1.Cells(counter, phaseendcolumn).Value
spin.Min = phasestart
spin.Max = phaseend
spin.Orientation = fmOrientationVertical
'Do
'.AddItem Format(phasestart, "mmm-yy")
'phasestart = DateAdd("m", 1, phasestart)
'Loop Until (Month(phaseend) = Month(phasestart) And Year(phaseend) = Year(phasestart))
Set spinbuttonHandler = New cSpinButtonHandler
Set spinbuttonHandler.spin = spin
spinbuttoncollection.Add spinbuttonHandler
End With
Set ctext = UserForm1.Controls.Add("Forms.TextBox.1")
With ctext
.name = "CustomTextbox" & checkboxnumber
.Left = 470
.Top = topvalue + 6
.Height = 15
.Width = 40
.Value = phasestart
Set customtextboxHandler = New cCustomTextBoxHandler
Set customtextboxHandler.ctext = ctext
customtextboxcollection.Add customtextboxHandler
End With
topvalue = topvalue + 15
counter = counter + 1
checkboxnumber = checkboxnumber + 1
Loop Until counter = 14
End Sub
In my class called cSpinButtonHandler, I reference these customtextboxcollection object associated with it's corresponding spin button:
Public WithEvents spin As MSForms.SpinButton
Private Sub spin_Click()
UserForm1.CommandButton3.Enabled = True
End Sub
Private Sub spin_SpinDown()
x = 0
Do
x = x + 1
Loop Until spin.name = "SpinButton" & x
Dim spindate As Date
spindate = customtextboxcollection.Item(x).ctext.Value 'The error occurs here.
customtextboxcollection.Item(x).ctext.Value = DateAdd("m", -1, spindate)
End Sub
Why is this reference generating an error? What is the correct way to reference it?
This is not an answer to your real question, but a suggestion for an alternate approach which might be easier to manage.
Instead of using two separate collections and two different classes, you could create a single class which would handle each pair of controls (one spin and one text box). That would be easier to handle in terms of hooking events between each pair.
clsSpinText:
Option Explicit
Public WithEvents txtbox As MSForms.TextBox
Public WithEvents spinbutn As MSForms.SpinButton
Private Sub spinbutn_Change()
'here you can refer directly to "txtbox"
End Sub
Private Sub txtbox_Change()
'here you can refer directly to "spinbutn"
End Sub
When adding your controls create one instance of clsSpinText per pair, and hold those instances in a single collection.

Excel-VBA How to know which dynamically created UserForm TextBox has changed?

I have an excel UserForm that creates textboxes during execution time. code as follows;
Dim CompHandler() As New CCompHandler
Dim tb As MSForms.TextBox
Dim count As Integer
For i in Range(something)
If i = anotherthing Then
Set tb = UserForm1.Controls.Add("Forms.TextBox.1", "tb" & count)
With tb
.Width = iTbWidth
.Top = count * distance
.Left = iTbLeft
.Height = iTbHeight
.Value = Cells(row, column)
ReDim Preserve CompHandler(0 To count)
Set CompHandler(count).TextBoxGroup = tb
End With
count = count + 1
End If
Next i
I want to write back the changed value to the corresponding cell.
I'm already able to get when the box has changed and the new value with this code on a class called CCompHandler:
Option Explicit
Public WithEvents TextBoxGroup As MSForms.TextBox
Private Sub TextBoxGroup_Change()
MsgBox TextBoxGroup
End Sub
So.. any ideas on how can I get which textbox has changed?
Or maybe is there a better way of doing that?
Thanks in advance
The Tag property is typically used for something like this. On creation add something like:
With tb
...
.Tag = i.Address
...
End With
You can then access the Tag property later, with something like:
Debug.Print tbWhoseValueHasChanged.Tag
Your code snippet has a lot of undefined/unclear variables, including i. I assumed it was a range variable in the above.