Word VBA Userform, Getting Control Object By Name fails sometimes - vba

I have a userform “myUserForm” with dozens of controls whose TypeName() is “CheckBox”. I just hate having dozens of _Click() routines named like “Private Sub Chk1_Click()”, so in order to manage the quantity of _Click() routines, I simplified and made them nearly identical:
Private Sub Chk1_Click()
ProcessClickFor ("Chk1")
End Sub
Private Sub Chk2_A_Click()
ProcessClickFor ("Chk2_A")
End Sub
Private Sub Chk3_Z_Click()
ProcessClickFor ("Chk3_Z")
End Sub
ProcessClickFor() does most of the work.
Sub ProcessClickFor(anyCheckBox As String)
Dim cbControl As Object
Set cbControl = ControlByName(anyCheckBox)
If cbControl.Value Then
cbControl.Value = True
End If
End Sub
Later, when I want to work with any control, I can get the Control object by name, like:
Dim aControl As Object
Set aControl = ControlByName(“Chk3”)
MsgBox “The control named “ & cbControl.Name & “ is “ & cbControl.Visible
Function ControlByName(sName) As Object
Dim objectified As Object
For Each objectified In myUserForm.Controls
If objectified.Name = sName Then
Set ControlByName = objectified
Exit Function
End If
Next objectified
End Function
This works fine, almost, but it FAILS on the same four controls on myUserForm every time.
The failure “mode” is that ControlByName() seems to return successfully, but the first use of the returned control (such as my MsgBox) gives the error:
"Run-time error '91': Object variable or With block variable not set".
I verified that the spelling of the defined control names matches the names in my _Click() routines. Dozens of similarly designed CheckBox controls work perfectly. Could it have to do with the length of the CheckBox names or the number of “_” characters in the CheckBox names? Could there be a corrupt character in a CheckBox name? Can you think of other things for me to try?

Related

Trying to use a variable in a Set statement

I have an array oNam of TextBox names, I want to call a sub and pass an index number pointing to the specific TextBox I am working on to the sub. In the sub I use the following code, Dim MyControl As Control, and Set MyControl = Me![oNam(n)]. I get the following
Run-time error 2147024809: Could not find the specified object.
However, if I replace oNam(n) with the actual TextBox name, Me![tbxBuyNowPrice], I don't get the error. I included a Debug.Print oNam(n) and it holds the correct name.
I've replaced the variable with the actual name and it works.
Dim oNam() As Variant
....Call TextBoxControl(34)
Sub TextBoxControl(n As Integer)
Debug.Print oNam(n) <====Shows correct name
Set MyControl = Me![oNam(n)] <====GET THE ERROR HERE
If DVAL(n) = "Yes" Then
Call TextBoxSettings(MyControl, "", vbYellow)
MyControl.SetFocus
GoTo EndOfTextBoxControl
Else
Call TextBoxSettings(MyControl, 0, vbCyan)
End If
EndOfTextBoxControl:
End Sub

Dim Form as as specific Form with if-statement

I have a Sub, which works for two different Forms. When I call the Sub from one of those forms I give the Form to the Sub as a parameter.
How can I set the correct for as a variable in the Sub?
Here are two ways I have tried this, but they do not work.
Shared Sub SetDataGridViewg(ByRef Form As Form, AusAuftrag As Boolean)
' ERROR: A common type can not be derived, and Option Strict On does not allow the assumption of the Object type.
Dim AdressenÜbersicht = If(AusAuftrag, CType(Form, AdressenÜbersichtAusAuftrag), CType(Form, Startseite))
' ERROR: The variable "AdressenÜbersicht" hides a variable in an enclosing block.
If AusAuftrag Then
Dim AdressenÜbersicht As AdressenÜbersichtAusAuftrag = CType(Form, AdressenÜbersichtAusAuftrag)
Else
Dim AdressenÜbersicht As Startseite = CType(Form, Startseite)
End If
' ...
End Sub

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.

Why is Object type instead of Sheet type used for variable declaration in the following VBA code?

The following code gets user back to the old sheet if a Chart is activated, and it shows how many data points are included in the Chart before getting back. And I wonder why the variable Sh is defined as Object rather than Sheet in the two event-handler procedures. Same for the variable OldSheet.
Dim OldSheet As Object
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Set OldSheet = Sh
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Dim Msg As String
If TypeName(Sh) = "Chart" Then
Msg = "This chart contains "
Msg = Msg & ActiveChart.SeriesCollection(1).Points.Count
Msg = Msg & " data points." & vbNewLine
Msg = Msg & "Click OK to return to " & OldSheet.Name
MsgBox Msg
OldSheet.Activate
End If
End Sub
Because there is no such thing as a 'Sheet' in Excel.
Notice the event is SheetActivate, not WorksheetActivate - the concept of a "sheet" encompasses several types that have nothing in common, other than the ability to be "activated". There is no Sheet type in the Excel object model - the Workbook.Sheets collection contains various types of objects, including Chart and Worksheet objects.
The Sh parameter in the SheetActivate event has to be an Object, because there is no common interface between a Chart and a Worksheet.
So you need do what you did: verify the type of the object instead of assuming you're dealing with a Chart or a Worksheet object.
Instead of using the TypeName function and thus stringly-typed type checks, you should use the TypeOf operator instead:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If TypeOf Sh Is Excel.Worksheet Then
Debug.Print "Worksheet!"
ElseIf TypeOf Sh Is Excel.Chart Then
Debug.Print "Chart!"
Else
Debug.Print "Something else!"
End If
End Sub
The parameter being Object allows future versions to activate a "sheet" of a type that couldn't be dreamed of at the time the event declaration was written in the IWorkbookEvents hidden interface that every Workbook object implements.
The short version of the answer is that it has to be declared as Object. The events are being "fired" through a COM source sink, and that returns an IDispatch pointer (known in VBA as Object) to anything that has a subscribed callback function. The ByVal Sh As Object parameter is passed to the callback function so that the event handler can determine which object was responsible for raising the event. It's declared in the Excel type library on dispinterface WorkbookEvents like this:
[id(0x00000619), helpcontext(0x0007ad30)]
void SheetActivate([in] IDispatch* Sh);
Even without considering the COM plumbing of its implementation, it has to be declared as Object because the Sheets collection holds both Worksheet and Chart objects, and the event will fire if either type of tab is activated. The two types don't share a common interface, but they do both source the same event. That means in order to pass the source object to the event handler, it has to be passed as late bound (IDispatch). The assumption is that the handler will determine what type of object it was passed and take the appropriate action based on the type of the sender.

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