Just a question that I can't seem to find an answer on.
I'm programmatically creating a userform, and I've found if I declare my object as the type "MSForms.Userform" there seems to be no way to set the height and width, as these properties don't exist, and insideheight / insidewidth are read only properties.
What I found was if I declare it as the generic type "object", I can set the height and width properties and use it exactly as I want.
So, after I initialize the object, I checked the locals window and the difference seems to be:
So my question is, what is the difference in using the different declare statments?
Thanks!
EDIT: Added some sample code so you can see how the oject act's differently when declared differently.
(I can't get this code block to display properly - even with the language declared as basic)
Sub TestUserForm()
'NOTE: You need to add a reference to Microsoft Visual Basic
' for Applications Extensibility 5.3
'Declare variables
Dim oForm As MSForms.UserForm
Dim oForm1 As Object
Dim oComp As VBComponent
Dim oComp1 As VBComponent
'Create new form objects in the VBA project programmatically
Set oComp = Application.VBE.ActiveVBProject.VBComponents.Add(ComponentType:=vbext_ct_MSForm)
Set oComp1 = Application.VBE.ActiveVBProject.VBComponents.Add(ComponentType:=vbext_ct_MSForm)
'Initailize an object of each new form
Set oForm = VBA.UserForms.Add(oComp.Name)
Set oForm1 = VBA.UserForms.Add(oComp1.Name)
'Compare what happends when trying to set the width and height properties
With oForm1 'This works
.Height = 200
.Width = 100
End With
With oForm1 'This does not work
.Properties("Width") = 100
.Properties("Height") = 200
End With
With oForm 'This does not work
.Height = 200
.Width = 100
End With
With oForm 'This does not work
.Properties("Width") = 100
.Properties("Height") = 200
End With
'Remove the forms from the project
Application.VBE.ActiveVBProject.VBComponents.Remove oComp
Application.VBE.ActiveVBProject.VBComponents.Remove oComp1
End Sub
When you import the components into the project it will name it UserForm1
and probably UserForm2
respectively.
oForm == UserForm1
oForm1 == UserForm2
Now, looking at the MSDN docs for Object we find that:
You can declare an object variable with the Object data type when the specific object type is not known until the procedure runs. Use the Object data type to create a generic reference to any object.
You've declared the variables like so:
Dim oForm As MSForms.UserForm
Dim oForm1 As Object
So what happens when you initialize the objects is oForm
gets initialized as a UserForm, while the runtime determines that the Object oForm1
is an instance of UserForm1, which is not the same thing.
Try changing the component name of oForm1 prior to initializing it and you should pretty quickly see the difference.
Now, if you want the type safety of declaring as a generic form and you want to access the Width
property, you can cast your UserForm as an Object and access it like so.
Dim FormAsForm As UserForm
Dim FormAsObject As Object
Set FormAsForm = New UserForm1
Set FormAsObject = FormAsForm
FormAsObject.Width = 200
Debug.Print TypeName(FormAsForm)
Debug.Print TypeName(FormAsObject)
This is a trick we use often when implementing multiple interfaces. The compiler will only allow you to use properties that are defined in the particular type the class object is declared as.
So what's the difference? Practically speaking, you get no intellisense when declaring things as Object. You also get no type safety. It's perfectly valid to do this (although not recommended.)
Dim foo As New Baz
Dim bar As New Qux
Dim var As Object
Set var = foo
Set var = bar
Object does come in extremely handy when you're using late binding to avoid adding references to your project though. Without the adding a reference, you're forced into using an unknown type.
Dim xl As Object
Set xl = CreateObject("Excel.Application")
The other big difference is you're leaving it up to the runtime to determine what kind of object the variable will be. As you discovered, it will sometimes (rarely, but sometimes) produce surprising results.
I think you had your answer about the difference:
But the compiler will only allow to use properties/method existing for the type the class object is declared as :
I have made some tests, and I just wanted to point at that I think the corrected procedure would rather be :
Dim FormAsForm As **UserForm1***
Dim FormAsObject As Object
Set FormAsForm = New UserForm1
Set FormAsObject = FormAsForm
FormAsObject.Width = 200
Debug.Print TypeName(FormAsForm)
Debug.Print TypeName(FormAsObject)
declaring here FormAsForm as a UserForm1, instead of UserForm which cause this error on my side :
Runtime error '438': Object doesn't support this property or method
However, using this, after FormAsForm has be loaded and displayed once with .Show, then Unloaded, FormAsForm is downcasted from Type UserForm11/UserForm11 to UserForm11/UserForm, and in cannot be shown again with .Show. Any supposed to work method will cause this error :
Error '-2147418105': Automation error
Error '-2147418105': Erreur Automation : L’appelé (serveur [pas application serveur]) n’est pas disponible et a disparu ; aucune connexion n’est valide. L’appel a peut-être été exécuté.
Of course, the variable types I just gave are read from the Locals VBE Window, since ?TypeName(FormAsForm) will only bring a :
Runtime Error '13': Type Mismatch
For the record, ?TypeName(FormAsObject) returns UserForm after downcast, and .Show method cause the same 'Automation error'.
This whole part, I can't explain...
So, unless I miss my guess here, MSFORMS.UserForm is a subclass of something else (something similar to a VB6 Form class, perhaps?), and the other properties & methods that it does handle properly, when dimensioned as an Object, (.Hide, .Show, .Visible, to name ones I know of so far) are neither a part of the Userform class interface, nor appear to be DOCUMENTED ANY-DAMN-WHERE-AT-ALL (and I wouldn't know of those listed above except from reading online questions/comments about UserForms)! ...must derive from the base class from which UserForm inherits?
Too bad we can't see what that base class is, because then we might be able to find an actual CLUE about what other capabilities are available to us (who knows, there may be .Parent, .Properties or other fun things "out there" that nobody seems to know about).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With