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

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.

Related

Setting a VBA form object using a string

Hello,
I am trying to set up a form which is a calendar from which the user can select a date (by default the current month appears). The form consists of 42 command buttons (I have left the default name ie. CommandButton1) which I am setting the day number.
At the moment I have a long-winded section of code for each button (I used Excel to generate this rather than type it all out) which locks and hides the button if it is outside of the month in question which looks like this:
NewDate.CommandButton1.Caption = Format(DATlngFirstMonth - DATintDayNumFirst + DATintX, "dd")
If DATintX < DATintDayNumFirst Then
With NewDate.CommandButton1
.Locked = True
.Visible = DATbooShowExtraDays
.ForeColor = RGB(150, 150, 150)
End With
Else
With NewDate.CommandButton1
.Locked = False
.Visible = True
.ForeColor = RGB(0, 0, 0)
End With
End If
I know that I can refer to a command button by:
Dim objCommandButton As Object
Set objCommandButton = NewDate.CommandButton1
..which neatens the code up somewhat. But what I would like to do is refer to the command button as a string so I can loop through all 42, ie.
Dim n as integer
n = 1
Do Until n > 42
Set objCommandButton = NewDate.CommandButton & n
'Some operations
n = n + 1
Loop
Many thanks in advance for assistance.
You can loop through all controls of the form. Try
Sub LoopButtons()
Dim it As Object
For Each it In NewDate.Controls
Debug.Print it.Name
Next it
End Sub
Then you can put conditional expression (if ... then) in place of Debug.Print or whatever. For example
If Instr(it.Name, "CommandButton") Then
'do your code
end if
Here's code which iterates over ActiveX controls on active sheet:
Sub IterateOverActiveXControlsByName()
Dim x As Integer
Dim oleObjs As OLEObjects
Dim ctrl As MSForms.CommandButton
Set oleObjs = ActiveSheet.OLEObjects
For x = 1 To 10
Set ctrl = oleObjs("CommandButton" & x).Object
Next
End Sub

Adding OnChange event to dynamically created vba form controls

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.

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

Reference an object using string (the object is under the tab control)

I have a working code here that can put test to all textboxes from 1 to 10. The name of the textboxes are Tb_Hour_1, Tb_Hour_2, Tb_Hour_3, Tb_Hour_4, etc. up to 10.
Dim textboxes As TextBox
For i As Integer = 1 To 10
textboxes = Me.Controls("TB_Hour_" & i)
textboxes.Text = "Test" & i
Next
But the problem comes in when I put my text boxes under the tab control, by searching I've found out that by adding in I can specify the place of the object that I want to call. Here's the working code.
For Each c As TextBox In TabPage1.Controls.OfType(Of TextBox)()
If c.Text = String.Empty Then c.Text = "a"
Next
My question is how can I integrate the 2 codes so I can have something like this
Dim textboxes As TextBox In TabPage1.Controls.OfType(Of TextBox)()
For i As Integer = 1 To 10
textboxes = Me.Controls("TB_Hour_" & i)
textboxes.Text = "Test" & i
Next
My VB is a little rusty, but it should be something like this. You can loop through all of the controls on the tab, look for ones that starts with "TB_Hour_", cast it to a textbox and do whatever it is you want to do.
For Each c As Control In TabPage1.Controls
If c.Name.StartsWith("TB_Hour_") Then
' it's a textbox
Dim tb as Textbox = DirectCast(c, Textbox)
' do whatever you're going to do
End If
Next
Now that you tucked the TextBoxes into a TabContol, don't use Me.Controls(). Instead try:
Dim textboxes As TextBox
For i As Integer = 1 To 10
textboxes = TabPage1.Controls("TB_Hour_" & i)
textboxes.Text = "Test" & i
Next
Otherwise, you can do something like:
Dim textboxes As TextBox
For i As Integer = 1 To 10
textboxes = Me.TabControl1.Controls("TabPage1").Controls("TextBox" & i)
textboxes.Text = "Test" & i
Next

'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.