Excel-VBA: Set Userfom Control.Name at runtime (runtime error 382) - vba

I'm working on a little Excel-VBA GUI/Form for the user to read and write data from/to an .ini file. One of the UserForms has a MultiPage item for which the user creates pages at runtime and after help from stackoverflow, they can also move the pages around. Because the control properties are used to write data to the .ini file, the controls must be named correctly, that is for my purpose, among other things, in line with the relevant MultiPage.Value. I added the following to the MoveLeft/Right methods and I get an error saying I cannot change the Control.Name property at runtime (382).
For i = 1 To UFmodproject.MultiPage1.Pages.Count - 1
For Each Ctrl In UFmodproject.MultiPage1.Pages(i).Controls
If TypeOf Ctrl Is MSForms.TextBox Then
Ctrl.Name = Left(Ctrl.Name, Len(Ctrl.Name) - 1) & i
End If
Next
Next
The strange thing is, I'm doing pretty much the same thing when copying a page, and that doesn't produce any errors:
For Each newCtrl In UFmodproject.MultiPage1.Pages(pCount).Controls
For Each Ctrl In UFmodproject.MultiPage1.Pages(UFmodproject.MultiPage1.Value).Controls
If (Ctrl.Left = newCtrl.Left And Ctrl.Top = newCtrl.Top) Then
newCtrl.Name = Left(Ctrl.Name, Len(Ctrl.Name) - 1) & pCount
Exit For
End If
Next
'[bunch of other code...]
Next
What could be the error here? For what it's worth, I tried Dim Ctrl as Object and Dim Ctrl as Control, both work when copying but none of them when I only try to set the Control.Name property. Furthermore, the code to copy a page is in a standard module and called from a class module. The new code to rename the controls is in the class module. I tried moving it to a standard module like the other, but to no avail.
EDIT:
I thought I had figured it out and that the error wasn't that it couldn't change the property at runtime, but it seemed that an ambiguous name is detected when the Control.Name property is set to its own value. But I was still getting errors when I tried to move a page to Value 1. Since I still seemed to have ambiguous names I ended up with the following to test:
Sub SetNames()
Dim Ctrl As Object
Dim Ctrl2 As Object
Dim i As Integer
Dim Name As String
For i = 1 To UFmodproject.MultiPage1.Pages.Count - 1
For Each Ctrl In UFmodproject.MultiPage1.Pages(i).Controls
If Int(Right(Ctrl.Name, 1)) <> i Then
For Each Ctrl2 In UFmodproject.Controls
If Ctrl2.Name = Left(Ctrl.Name, Len(Ctrl.Name) - 1) & i Then
Ctrl2.Name = Left(Ctrl.Name, Len(Ctrl.Name) - 1) & i + 4
Exit For
End If
Next
Ctrl.Name = Left(Ctrl.Name, Len(Ctrl.Name) - 1) & i
End If
Next Ctrl
Next i
End Sub
but I still kept getting the same error, but when I checked for the object with the supposedly used name in the immediate window it confirms the object doesn't exist. What am I doing wrong?

Related

Dynamically Duplicating a page in a Multipage

Good afternoon, So I have been assigned a task where I'm supposed to take a Calendar Userform I made and implement the ability to duplicate as many calendars as needed as tabs on a multipage Userform.
My question is : Is it possible to do so dynamically? I copied over all the controls over to another tab to test it, but all the control buttons get renamed and I'm not able to name them the the same name as the buttons on the 'master page' because of the ambiguity error when naming 2 buttons the same on one userform.
I could write new code using the new button names, but this would not be able to be done dynamically since for ever new tab added, I'd have to have code ready for it prior to creation.
Any help would greatly be appreciated.
Demo Workbook
The key to handling this type of problem is to create a class to hold references to the newly created controls. Using WithEvents will allow you to handle the events of the referenced controls.
Read about WithEvents here: Events And Event Procedures In VBA
In order to make this work you'll have to Set references between the newly created Page controls and their doppelgangers in your class. You'll also need to keep the class references alive by adding them to a global collection, dictionary or array.
In my example I created a subroutine that will iterate over the template page controls, creating and copy the code necessary to declare the variable and set the references into the Windows ClipBoard.
The final step is to copy the event code from the Userform into the class module.
Download Demo Workbook for the code example.
Alternatively, you could replace the Multipage control with a TabStrip control. The difference is that you can have the controls on a TabStrip span across all the Tabs. You could than use the TabStrip1_Change() to update the controls based on the selected tab (TabStrip1.SelectedItem).
First of all I would recommend looking at Thomas Inzina's answer.
However, in one of my worksheets I did the same without relying on WithEvents, so I wanted to show the alternative method.
This method sets names of the controls along a predetermined format (e.g. "Input1_" (counter + 1)) so the controls are easily referenced from other operations.
The controls themselves are within Frames with set Captions, so I can still reference them after they've been copied.
I've edited the code somewhat since I stripped it from a longer procedure, but hopefully it's still intact.
Dim Ctrl As msforms.Control
Dim Mpage As msforms.Control
Dim Ctrl2 As msforms.Control
Dim pge As msforms.Page
Dim L As Double, R As Double
Dim PageName As String, PageTitle As String
Dim counter As Long
counter = 0
Set Mpage = Me.Controls("Multipage_1") 'set Multipage
'count current number of tabs within MPage
For Each pge In Mpage.Pages
counter = counter + 1
Next pge
'set name/title for new page
PageName = "Tab_" & (counter + 1)
PageTitle = "Tab " & (counter + 1)
With Mpage 'add tab
.Pages.Add PageName, PageTitle
.Pages(0).Controls.Copy
.Pages(counter).Paste
End With
'get position of original frame (controls are within this frame)
For Each Ctrl In Mpage.Pages(0).Controls
If TypeOf Ctrl Is msforms.Frame Then
L = Ctrl.Left
R = Ctrl.Top
Exit For
End If
Next
'apply position to new frame
For Each Ctrl In Mpage.Pages(counter).Controls
If TypeOf Ctrl Is msforms.Frame Then
Ctrl.Left = L
Ctrl.Top = R
Exit For
End If
Next
'renames input-controls and removes copied values by looping through frames
'that contain the controls, since frame-captions can be duplicates
For Each Ctrl In Mpage.Pages(counter).Controls
If TypeOf Ctrl Is msforms.Frame Then
Select Case Ctrl.Caption
Case "Input1"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input1_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
Case "Input2"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input2_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
Case "Input3"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input3_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
End Select
End If
Next Ctrl
use a prefix for the control names. eg. tab1_button1, tab2_button1, tab33_button1. then have only one one event handler that services all the events (button presses, checkbox clicks)
here is some info using one sub for multiple buttons in excel vba

check if textbox exists vba (using name)

I am using Ms-Access and I created a userform which has a number of Textboxes on it. The boxes are named: Box1, Box2, Box3 ...
I need to loop through all boxes, but I don't know which is the last one. To avoid looping through all userform controls I thought of trying the following:
For i =1 To 20
If Me.Controls("Box" & i).value = MyCondition Then
'do stuff
End If
Next i
This errors at Box6, which is the first box not found. Is there a way to capture this error and exit the loop when it happens.
I know I could use On Error but I 'd rather capture this specific instance with code instead.
Thanks,
George
A Controls collection is a simplified collection of controls (obviously) and share a same order as a placement order of controls.
First of all, even a creatable collection object lacks methods such as Exists or Contains , hence you need a function with error handling to checking/pulling widget from a collection.
Public Function ExistsWidget(ByVal Name As String) As Boolean
On Error Resume Next
ExistsWidget = Not Me.Controls(Name) Is Nothing
On Error GoTo 0
End Function
If you really doesnt like "ask forgiveness not permission" option you can pull entire ordered collection of your textboxes (and/or check existance by name in another loop with similar logic).
Public Function PullBoxes() As Collection
Dim Control As MSForms.Control
Set PullBoxes = New Collection
For Each Control In Me.Controls
If TypeOf Control Is MSForms.TextBox And _
Left(Control.Name, 3) = "Box" Then
Call PullBoxes.Add(Control)
End If
Next
End Function
Since names of widgets are unique - you can return a Dictionary from that function with (Control.Name, Control) pairs inside and able to check existance of widget by name properly w/o an error suppression.
There's a good guide to Dictionary if it's a new information for you.
Anyway, no matter what object you choose, if user (or code) is unable to create more of thoose textboxes - you can convert this Function above to a Static Property Get or just to a Property Get with Static collection inside, so you iterate over all controls only once (e.g. on UserForm_Initialize event)!
Public Property Get Boxes() As Collection
Static PreservedBoxes As Collection
'There's no loop, but call to PullBoxes to reduce duplicate code in answer
If PreservedBoxes Is Nothing Then _
Set PreservedBoxes = PullBoxes
Set Boxes = PreservedBoxes
End Property
After all, the last created TextBox with name Box* will be:
Public Function LastCreatedBox() As MSForms.TextBox
Dim Boxes As Collection
Set Boxes = PullBoxes
With Boxes
If .Count <> 0 Then _
Set LastCreatedBox = Boxes(.Count)
End With
End Function
I think that now things are clearer to you! Cheers!
Note: All code are definitely a bunch of methods/properties of your form, hence all stuff should be placed inside of form module.
Long story short - you cannot do what you want with VBA.
However, there is a good way to go around it - make a boolean formula, that checks whether the object exists, using the On Error. Thus, your code will not be spoiled with it.
Function ControlExists(ControlName As String, FormCheck As Form) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
ControlExists = (Err.Number = 0)
End Function
Taken from here:http://www.tek-tips.com/viewthread.cfm?qid=1029435
To see the whole code working, check it like this:
Option Explicit
Sub TestMe()
Dim i As Long
For i = 1 To 20
If fnBlnExists("Label" & i, UserForm1) Then
Debug.Print UserForm1.Controls(CStr("Label" & i)).Name & " EXISTS"
Else
Debug.Print "Does Not exist!"
End If
Next i
End Sub
Public Function fnBlnExists(ControlName As String, ByRef FormCheck As UserForm) As Boolean
Dim strTest As String
On Error Resume Next
strTest = FormCheck(ControlName).Name
fnBlnExists = (Err.Number = 0)
End Function
I would suggest testing the existence in another procedure per below: -
Private Sub Command1_Click()
Dim i As Long
i = 1
Do Until Not BoxExists(i)
If Me.Conrtols("Box" & i).Value = MyCondition Then
'Do stuff
End If
i = i + 1
Next
End Sub
Private Function BoxExists(ByVal LngID As Long) As Boolean
Dim Ctrl As Control
On Error GoTo ErrorHandle
Set Ctrl = Me.Controls("BoX" & LngID)
Set Ctrl = Nothing
BoxExists = True
Exit Function
ErrorHandle:
Err.Clear
End Function
In the above, BoxExists only returns true if the box does exists.
You have taken an incorrect approach here.
If you want to limit the loop, you can loop only in the section your controls reside e.g. Detail. You can use the ControlType property to limit controls to TextBox.
Dim ctl As Control
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Then
If ctl.Value = MyCondition Then
'do stuff
End If
End If
Next ctl
I believe the loop will be faster than checking if the control name exists through a helper function and an On Error Resume Next.
But this only a personal opinion.

VBA - Error While Programming a Class to Operate all Checkboxes on Userform

Here is a bit of background on what I'm trying to do: I'm creating a userform to track Inventory items and prices, using checkboxes in a multipage object. The clerk checks off everything put into an order and uses a submit button, which will take some actions.
In order for the project not to require a coding person every time Inventory items change, the checkboxes are being dynamically generated when the userform is activated, from cell values on an Inventory sheet. The clerks just adjust the Inventory sheet and the form automatically adjusts for them.
This is my code to dynamically create all the checkboxes (currently this form can accommodate up to 160 possible checkboxes), in case this is effecting my issue (side note, each tab on the multipage has a frame on it, and all checkboxes are within the frame, so I could change background colors, the frame in this example being titled "frmreg"):
Sub StoreFrmRegCheckboxGenerator()
'Works with the store userform
Dim curColumn As Long
Dim LastRow As Long
Dim i As Long
Dim chkBox As msforms.CheckBox
'This sub dynamically creates checkboxes on the Regular Items tab based
'on values in Column A of the Inventory sheet
curColumn = 1 'Set your column index here
LastRow = Worksheets("Inventory").Cells(Rows.Count, curColumn).End(xlUp).Row
For i = 2 To 9
If Worksheets("Inventory").Cells(i, curColumn).Value <> "" Then
Set chkBox = store.frmreg.Controls.Add("Forms.CheckBox.1", "CheckBox_" & i)
chkBox.Caption = Worksheets("Inventory").Cells(i, curColumn).Value & " - $" & Worksheets("Inventory").Cells(i, curColumn).Offset(0, 1).Value
chkBox.AutoSize = True
chkBox.WordWrap = True
chkBox.Left = 5
chkBox.Top = 1 + ((i - 1) * 25)
End If
Next i
'Cut some code out here identical to this previous section, but for the rest of the cells in column A up to Row 33, in blocks of 8
End Sub
The above code is in the Userform_Initialize sub, and it works perfectly.
However, since the number of checkboxes is not static, and can be as many as 160, I'm trying to write one sub to take the same set of actions any time any checkbox is clicked.
The closest solution I've found is from this question: Excel Macro Userform - single code handling multiple checkboxes, from sous2817.
Here is his code that I'm trying to use:
In a new class module:
Option Explicit
Public WithEvents aCheckBox As msforms.CheckBox
Private Sub aCheckBox_Click()
MsgBox aCheckBox.Name & " was clicked" & vbCrLf & vbCrLf & _
"Its Checked State is currently " & aCheckBox.Value, vbInformation + vbOKOnly, _
"Check Box # & State"
End Sub
The "store" userform, at the top, right under Option Explicit:
Dim myCheckBoxes() As clsUFCheckBox
At the bottom of the Userform_Initialize sub, AFTER I call the all the subs that dynamically create all the checkboxes:
Dim ctl As Object, pointer As Long
ReDim myCheckBoxes(1 To Me.Controls.Count)
For Each ctl In Me.Controls
If TypeName(ctl) = "CheckBox" Then
pointer = pointer + 1
Set myCheckBoxes(pointer) = New clsUFCheckBox
Set myCheckBoxes(pointer).aCheckBox = ctl
End If
Next ctl
ReDim Preserve myCheckBoxes(1 To pointer)
When I try to open the userform I get this error:
"Compile Error: User-defined type not defined"
Pointing to this line:
Dim myCheckBoxes() As clsUFCheckBox
Am I missing a library reference? I haven't been able to figure this out.

error Creating a new page in a userform in VBA

I have a Userform with several pages, one of which is called FXForward1 and if a certain condition is met I want to create a new page in the userform called FXForward2 that has all the same controls as FXForward1. Does anyone know how I can go about doing this? The following is the relevant part of the code I currently have and it's giving me the following error:
Run-Time Error '-2147319767(80028029)':
Could not paste the control. Invalid forward reference, or reference to uncompiled type
Sub UpdateForm()
Dim vbObject As Object
Dim objControl As Control
Dim OptionNumber As Integer
Set vbObject = ThisWorkbook.VBProject.VBComponents("UserForm1")
Set objControl = vbObject.Designer.Controls("MultiPage1")
' Other code here
If OptionNumber > 1 Then
objControl.Pages.Add ("FXForward" & OptionNumber)
objControl.Pages(FXForward1).Controls.Copy
objControl.Pages("FXForward" & OptionNumber).Paste ' This line is where I get the error
End If
UserForm1.Show
End sub

Access VBA Userform Controls without Form instance

I have a friend with a VBA project in Excel. This project has a lot of Forms that pop up and perform various functionality while the spreadsheet is being used. Some of them have complex Form_Initialize methods that rely on other things already existing (this is not a problem when the project is used as expected).
We are trying to print out the names of every control on every form within the application. Our problem is that the VBA.UserForms collection only contains forms that have already been instantiated, and we can't instantiate all the forms without their Form_Initialize methods executing.
For example:
For Each f In VBA.UserForms
Debug.Print f.Name
Debug.Print "----------------------"
For Each c In f.Controls
Debug.Print c.Name
Next c
Next f
does nothing if no forms have been used/loaded. This code:
For Each c in frmConfig.Controls
Debug.Print c.Name
Next c
First executes frmConfig.Form_Initialize(), then loops through the controls on the form printing their names. This crashes, as things that need to happen before this form is available have not happened.
Is it possible to get the names of the controls on a form WITHOUT instantiating the form (avoiding execution of frmConfig.Form_Initialize())?
Any help much appreciated!
Is this what you are trying?
Option Explicit
Sub FindObjects()
Dim vbc As VBIDE.VBComponent
Dim frm As Object
Dim Ctrl As MSForms.Control
For Each vbc In ThisWorkbook.VBProject.VBComponents
If vbc.Type = vbext_ct_MSForm Then
With VBA.UserForms
On Error Resume Next
Set frm = .Add(vbc.Name)
Debug.Print "Found userform :" & vbc.Name
If Err.Number = 0 Then
For Each Ctrl In frm.Controls
Debug.Print "Controls in Userform " & vbc.Name & _
" - " & Ctrl.Name
Next Ctrl
End If
On Error Go To 0
End With
End If
Next vbc
End Sub
IMP:
Set reference to Microsoft Visual Basic For Applications Extensibility
In Excel options, set "Trust Access To the VBA project Object Model"
Screen Shot
FOLLOWUP
Since this is a one time thing, do this
Open VBA Project
Press CTRL + F
Do as shown in the screenshot below and then run the code.
Close the file without saving once you have got what you need