Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between declaring a userform as Object vs MSForms.Userform?

Tags:

vba

vb6

userform

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:

  • When declared as type "object" it will initialize as an instance of type "UserForm1"
  • When declared as type "MSForms.Userform" it will initialize as an instance of type "UserForm"

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  
like image 725
CBRF23 Avatar asked Oct 16 '14 15:10

CBRF23


3 Answers

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.

like image 134
RubberDuck Avatar answered Oct 26 '22 23:10

RubberDuck


I think you had your answer about the difference:

  • one is a UserForm object of class UserForm1, wrapped in an Object object (Type Object/UserForm1) that can be set to anything else later in proc,
  • and the other is a UserForm object of class UserForm2.

But the compiler will only allow to use properties/method existing for the type the class object is declared as :

  • Objects have .Width and .Height properties, so oForm1 will allow you to access these properties of the instantiated form of the VBE component UserForm2 (take a look HERE for further understanding on this), but oForm won't.
  • UserForms have a .Show/.Hide method, and so will have oForm to display the instantiated/loaded form of the VBE component UserForm1, but oForm1 won't.

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

empty

This whole part, I can't explain...

like image 26
hymced Avatar answered Oct 26 '22 23:10

hymced


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

like image 40
Mark Burns Avatar answered Oct 27 '22 00:10

Mark Burns