I just found out that the Me-Object and UserFormName is not the same Object. Here my example:
I have two custom classes FilterLine and FilterModel. All you need to know is that FitlerModel has a property N which is set to = if newed up. There is also a UserForm called frmFilter.
Sub testFilter()
Dim Filterm As FilterModel
Set Filterm = New FilterModel
With New frmFilter
Set .Model = Filterm
.ExampleSub ' This is the interesting part
.Show
End With
End Sub
Here the ExampleSub of the Userform frmFilter:
Public Sub ExampleSub()
Debug.Print Me.Model.N ' gives a 0
Debug.Print frmFilter.Model.N ' gives an error "Object not Found"
End Sub
I find this rather interesting what is going on here? Why are they different and how are they different?
They refer to different objects. A UserForm is just a Class that has a Default Instance, a free object created when you call the class by name.
Me.Model.N
References the object you instanced.
frmFilter.Model.N
Refers to the Default Instance of the Userform, which has no model associated with it.
Good write-up on this issue here: https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/
You can use UserForm.Object only when the UserForm is explicitly created (Using Insert -> User Form) and shown in the project explorer tree.
If the userform is not shown in the project explorer tree, then you will get an error "Object not Found" if you use UserForm.Object
Related
I have two class modules in my project, "ClassAlfa" and "ClassBeta". I have separated the code between the two classes so that if an end-user does not have ClassBeta included in their project, then any of ClassAlfa's late bound references to it gracefully fail. Where I'm having trouble is making the script work if the end-user DOES have ClassBeta available.
Within ClassAlfa, I have the following code:
Option Explicit
Public myClass as Object
Private Sub Class_Initialize()
On Error Resume Next
Set myClass = Application.Run("ClassBeta")
If Err = 0 Then 'End user has this class available, go ahead and use it.
'Do code with ClassBeta
Else
'Do code without ClassBeta
End If
On Error GoTo 0
End Sub
This throws the following error on the Set line:
Run-time error '1004':
Cannot run the macro 'ClassBeta'. The macro may not be available in this workbook or all macros may be disabled.
I have also tried replacing the Set line with this:
Set myClass = CreateObject("ClassBeta")
which instead throws the error
Run-time error '429':
ActiveX component can't create object
Also does not work:
Set myClass = CreateObject("'" & ThisWorkbook.Name & "'!" & "ClassBeta")
What is the proper way to late bind a custom class from my own project?
There is no mechanism in VBA that would allow you to check if a class exists by it's name and to create a new instance if it is.
However, it is possible to achieve. Let's dissect the problems and see how we can work around them.
Problem 1
You need to create an instance of a class that you are not sure it exists in the current project. How?
As you already tried, Application.Run and CreateObject do not work.
Application.Run is only capable of running methods in standard .bas modules (not class modules). It does not create instances of classes.
CreateObject does a few things behind the scenes. First, it calls CLSIDFromProgIDEx using the ProgID (the string) you are passing. Then, it calls CoCreateInstance. The problem is that VBA classes do not have their ProgIDs in the registry so CreateObject simply doesn't work. You would need to have your class in a registered .dll file of ActiveX.exe to make this work.
That leaves us with the New keyword to instantiate a new ClassBeta. That obviously works when the class is present but gives a 'User-defined type not defined' compiler error when it's not. There are only 2 ways to supress this compiler error:
Have a compiler directive
Not have the New ClassBeta at all
A compiler directive would look like this in ClassAlfa:
Option Explicit
#Const BETA_EXISTS = False
Sub Test()
Dim myBeta As Object
#If BETA_EXISTS Then
Set myBeta = New ClassAlpha
#End If
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
This compiles fine without having the ClassBeta in the project but you could never make the BETA_EXISTS conditional compiler constant to turn True because:
Only conditional compiler constants and literals can be used in expression
So, the last option is not to have the New ClassBeta anywhere in the project. Except, we do. We can put it in the ClassBeta itself as a factory:
Option Explicit
Public Function Factory() As ClassBeta
Set Factory = New ClassBeta
End Function
When the class is missing, the factory is missing and the New ClassBeta will not throw a compiler error.
Problem 2
How do we call the .Factory method on the ClassBeta?
Well, we obviously cannot create a new instance of ClassBeta because that is our first problem.
What we can do is to make sure that ClassBeta always has a default global instance (like userforms have). To do that we need to export the class to a .cls text file and edit it with a text editor (like Notepad). The text should look like this:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ClassBeta"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Public Function Factory() As ClassBeta
Set Factory = New ClassBeta
End Function
Notice that I've changed the VB_PredeclaredId attribute to True (manually, in the text editor). Now we can import the class back. We can check if it worked by typing ?Typename(ClassBeta.Factory) in the Immediate window and then pressing Enter. We should see:
The code in ClassAlfa can now be written as:
Option Explicit
Sub Test()
Dim myBeta As Object
On Error Resume Next
Set myBeta = ClassBeta.Factory
On Error GoTo 0
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
Problem 3
If we remove ClassBeta from the project, the following line does not compile:
Set myBeta = ClassBeta.Factory
But, compared to problem 1, this time the compiler error is 'Variable not defined'.
The only way that I could think of, to get rid of this new compiler error, is to turn off Option Explicit. Yeah! That bad!
ClassAlfa:
'Option Explicit 'needs to be off to be able to compile without the ClassBeta class
Sub Test()
Dim myBeta As Object
On Error Resume Next
Set myBeta = ClassBeta.Factory
On Error GoTo 0
Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub
Final Thoughts
You could do the development while Option Explicit is on and turn it off for your users.
If your users will use the ClassAlfa with Option Explicit off, the the code should work fine. But if they want ClassBeta as well, then the only way they could get it would be via importing the .cls file. Copy-pasting code won't work because they would lose the global instance of ClassBeta (the one we set in the VB_PredeclaredId hidden attribute).
I must say that I do not recommend removing Option Explicit because that could lead to other issues. I do not think there is a way to achieve what you want in a 'clean' way.
Trying to get values from userform in module but it always shows blank.
EmailIdForm:
Private Sub CLRBTN_Click()
Call UserForm_Initialize
End Sub
Private Sub OKBTN_Click()
ToInput = EmailIdForm.ToInput.Value
CCInput = EmailIdForm.CCInput.Value
Unload EmailIdForm
End Sub
Private Sub UserForm_Initialize()
ToInput.Value = ""
CCInput.Value = ""
ToInput.SetFocus
End Sub
Module:
Public ToInput As String
Public CCInput As String
Sub EmailInput()
EmailIdForm.Show
MsgBox ToInput
MsgBox CCInput
End Sub
I think the problem is the visibility of the modul level variables ToInput and CCInput. If you name the modul MyModul just add the scope to the variables variables. See the modified code here:
Private Sub OKBTN_Click()
MyModul.ToInput = EmailIdForm.ToInput.Value
MyModul.CCInput = EmailIdForm.CCInput.Value
Unload EmailIdForm
End Sub
To elaborate on the comments;
10000% read UserForm1.Show. It might take some time to understand it (and a lot of googling things) but if you use UserForms it's worth the read to better understand how they work and how to work with them.
If you have a control on your userform with the same name as your public variable, the control will take precedence over the public variable.
So it is assumed that your TextBox names are ToInput and CCInput respecitvely.
When writing code in the UserForm code module, you could access these controls in 3 different ways (let's use the ToInput textbox in our example):
EmailIdForm.ToInput 'Explicitly declare which UserForm we are accessing the control on.
Me.ToInput 'The 'Me' keyword refers to that instance of the class (in the case you could have more than 1 instance of the class loaded).
ToInput 'The UserForm object is implied by it's own class.
The Me Keyword
Avoiding implicit qualifiers
To fix your problem, you will need to remove your naming conflicts. Either rename your variables or rename your textbox controls - *or both!
Consider doing a bit of research about good practices when it comes to naming variables, controls, objects, data types etc. I like to use descriptive names so someone who doesn't already know what the codes is doing can read it and understand - but this can come at the cost of making the code quite verbose - or - dense.
Consider chaning the names like this: (Note: It is entirely up to you how you name things in your code, this is just a suggestion.)
Module:
----------------------------------------
Public PublicToInputString As String
Public PublicCCInputString AS String
Sub EmailInput()
EmailIdForm.Show
MsgBox PublicToInputString
MsgBox PublicCCInputString
End Sub
-----------------------------------
Userform:
-----------------------------------
Private Sub OKBTN_Click()
PublicToInputString = Me.ToInputTextBox.Value
PublicCCInputString = Me.CCInputTextBox.Value
Unload Me 'Consider chaning to Me.Hide
End Sub
Now the code is clear and telling us what is what - Anyone could understand you are assigning a textbox value to a public variable string and following a naming convention like this also means you are far less likely to have naming conflicts - Again - Note: this is not a naming convention you must follow, merely a suggestion. Everyone has their own way that works for them.
The following code works fine on latest Excel Windows and also on Excel 16.28 on Mac. But on the latest Excel for Mac (16.29 and 16.30) it generates this error: "Compile error: Method or data member not found" on the code line MyShape.Select.
I assume that there is an alternative way to do what I want that the compiler will approve of, but I don't know what it would be. As an alternative, I tried not selecting the shape and just referring to it, but then I get the same error but on the With MyShape.ShapeRange.Fill line.
Dim MyShape As Shape
'Other stuff
Set MyShape = ActiveSheet.Shapes.AddShape(msoShapeRectangle, 400, 400, DistanceBetweenCells, LineWidth)
MyShape.Select
With Selection.ShapeRange.Fill
'stuff here
End With
I'm hoping that a newer version of Mac Excel, when released, will revert to the older version in allowing the above, but assuming that's not the case, any workarounds?
I like that you're explicitly referring to ActiveSheet, kudos!
The problem is that ActiveSheet is an Object, wich means the compiler is helpless: ActiveSheet.Shapes compiles, but so will ActiveSheet.Shapess - even with Option Explicit specified. The entire expression is evaluated at run-time.
Let's fix that first:
Dim sheet As Worksheet
Set sheet = ActiveSheet
Now sheet.Shapes gets intellisense and compile-time validation, along with subsequent the .AddShape member call. You even get parameter tooltips as you type up the argument list!
What happens next is interesting: you declared MyShape as a Shape, but it's not a Shape you're looking at - the Shape class doesn't have a ShapeRange property, so... where does MyShape.ShapeRange come from then?
If you break execution (F9 to set a breakpoint) after the MyShape.Select call, and then bring up the immediate pane (Ctrl+G), the answer appears:
?typename(selection)
Rectangle
If you press Shift+F2 on the word Rectangle...
Dim myRectangle As Excel.Rectangle '<~ here
...the VBE doesn't seem to figure it out ("identifier under cursor is not recognized"). So we press F2, then right-click somewhere and tick the "Show hidden members" option - and sure enough, there it is:
So your code says "let's use the Shape interface", but works with a Rectangle object. And since that works, it means a Rectangle "is a" Shape: the two interfaces simply describe the same object through different lens, so either works... but then Shape.ShapeRange doesn't look quite right, since the Shape class doesn't define that member and that's the interface we explicitly said we were going to be working with.
If we want to invoke the members of Rectangle, we can - and since we're now showing hidden members in the object browser, intellisense displays the hidden types and members too. If the entire With block is early-bound, everything makes much more sense:
With myRectangle.ShapeRange.Fill
...and explains how the late-bound code off ActiveSheet would work at run-time to resolve the member call, and now the compiler needs a completely other strategy to compile the VBA code: maybe that could shake things up enough to get it to work, maybe it won't. At least the type ambiguities and ignored-by-compiler statements are all gone :)
The thing that's surprising here, is that you can't do that with VBA user code. If you made a MyShape class with a DoSomething method:
'#ModuleDescription "A metaphorical Shape"
Option Explicit
Public Sub DoSomething()
MsgBox TypeName(Me)
End Sub
And then a MyRectangle class that implements MyShape and exposes a member on its own public interface, that yields a MyShape object reference:
'#ModuleDescription "A metaphorical Rectangle"
Option Explicit
Private sh As MyShape
Implements MyShape
Public Property Get Thing() As Object
Set Thing = sh
End Property
Private Sub Class_Initialize()
Set sh = New MyShape
End Sub
Private Sub MyShape_DoSomething()
MsgBox TypeName(Me)
End Sub
And now in any standard module, we can test this - first, all early-bound, and we'll have a factory method that returns a MyShape, to mimick Shapes.CreateShape:
Public Sub WorksMaybe()
Dim r As MyShape
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
So we run this (on Windows), and I expected, the code doesn't compile:
Late binding however...
Public Sub WorksMaybe()
Dim r As Object
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
...works? Nope:
Are we not looking at a MyRectangle object? No: we're looking at the limits of late-binding polymorphism in VBA - we created a New MyRectangle, but to the compiler CreateRect returns a MyShape object reference. If we place a breakpoint on End Function, run it, and then type ?TypeName(CreateRect) in the immediate pane (Ctrl+G) when the breakpoint is hit, then despite the declared type being MyShape, the runtime type is clearly MyRectangle.
And it should work - but it doesn't. Error 438, member not found: the late-bound/run-time equivalent of the "method or data member not found" compile error.
And if we use the interface we really mean to work with...
Public Sub WorksMaybe()
Dim r As MyRectangle
Set r = CreateRect
r.Thing.DoSomething
End Sub
Private Function CreateRect() As MyShape
Set CreateRect = New MyRectangle
End Function
...then everything "just works":
Now, I'm not running this on a Mac, but this code compiles for me...
Option Explicit
Const DistanceBetweenCells As Long = 50
Const LineWidth As Long = 2
Public Sub WorksMaybe()
Dim r As Excel.Rectangle
Set r = CreateRect
r.ShapeRange.Fill.BackColor.RGB = vbRed
End Sub
Private Function CreateRect() As Excel.Shape
Set CreateRect = Shapes.AddShape(msoShapeRectangle, 40, 40, DistanceBetweenCells, LineWidth)
End Function
...and systematically raises run-time error 13 as soon as CreateRect returns and the Shape reference gets assigned to a Rectangle - error 13 being "type mismatch". In other words, a Rectangle is not a Shape (!!?!??). Proof, if we make CreateRect return a Excel.Rectangle, we now get the type mismatch error as soon as we try to assign the function's return value, and nothing makes sense anymore: there's something weird going on, and, well, I'm out of ideas - there doesn't appear to be any way to work early-bound with a Rectangle, despite what TypeName(Selection) claims the type is (the class is hidden/undocumented for a reason after all!), which... pretty much destroys all hope, especially if neither With Selection.Fill nor With MyShape.Fill work (it does work perfectly fine here on my Windows box though).
Sending a frown with some repro code through the user feedback feature should get you heard from the product team at Microsoft. I doubt they removed anything from anywhere - but it's not impossible something broke how interfaces are resolved, somewhere deep down in some seemingly unrelated piece of internal API :)
Ok, I think it is figured out. In Mac Excel version 16.29 Microsoft has deleted certain class members for, at least, Shapes. For instance "Fill" and "Select" are no longer available. So any code that refers to them will generate an error.
I'm not sure how extensive this is or any other ramifications, but I do know that the code works fine in version 16.28, and also that the member list in 16.28 shows both "fill" and "select" but not in 16.29. Thanks to Mathieu Guindon above for the input and also to a poster who deleted his thread - both of these people really helped. I've reported the issue to Microsoft.
Answer from Steve Rindsberg (MVP):
Iterate through the .ShapeRange collection:
For x = 1 To .ShapeRange.Count
With .ShapeRange(x)
'...stuff....
End With
Next
I've had a few similar weird ones on Mac where perfectly good code (on
Windows) errors or sometimes make the Mac app go POOF! And disappear.
Iterating the collections this way has been the fix.
I am attempting to create a class module that builds on top of a checkbox control. Within the Class Module I want to point at the checkbox in the userform. Although, when I try to fill the CheckBox object with one of the checkboxes in the userform I get a type-mismatch since calling on the checkbox gives back it's state instead of the entire object. Is there a way to get the entire object?
I have tried
set myCheckBox = makeMyCheckBox(Me.CheckBox1)
and
set myCheckBox = makeMyCheckBox(Me.CheckBox1.Object)
where makeMyCheckBox is a function that takes in a CheckBox object and creates a new MyCheckBox object.
'Within my userform's code
Dim myCheckBoxes(1 to 2) As MyCheckBox 'MyCheckBox is my class module
Private Sub UserForm_Initialize()
set myCheckBoxes(1) = makeMyCheckBox(me.CheckBox1)'<--Error Type Mismatch
End Sub
Private Function makeMyCheckBox(c As CheckBox) As MyCheckBox
Dim myChck As MyCheckBox
Set myChck = New MyCheckBox
myChck.init c 'takes in a CheckBox and fills its internal CheckBox object
Set makeMyCheckBox= myChck
End Function
I expect Me.CheckBox1 to be a CheckBox object.
Me.CheckBox1 outputs the checkbox's state when I look in debug (true/false)
I get--
Run-time error '13':
Type Mismatch
I get a type-mismatch since calling on the checkbox gives back it's state instead of the entire object. Is there a way to get the entire object?
Wrong assumption, you're getting the "entire object", but the object you're getting isn't implementing the interface you're expecting, hence the type mismatch.
You need to qualify your MSForms types explicitly with the MSForms library, like this:
Private Function makeMyCheckBox(ByVal c As MSForms.CheckBox) As MyCheckBox
Otherwise the unqualified CheckBox identifier / type name is referring to Excel.CheckBox, because the host application's object model (the Excel library) always has a higher priority than the referenced MSForms library, in the project references dialog:
This is excruciatingly hard to discover in a vanilla VBE. With Rubberduck you just place the caret on CheckBox and it tells you where it's coming from:
Without any add-ins, you kind of have to guess what the actual type is, because Shift+F2 (which normally takes you to the definition in the Object Browser) is useless for this - all you get is a message saying "the identifier under the cursor is not recognized".
Disclaimer: I manage the Rubberduck open-source project.
This seems like it should be an easy one but I'm stuck.
I'm running a VBA script in Access that creates a 40+ page report in Excel.
I am creating an Excel Application Object using Early Binding:
Public obj_xl As New Excel.Application
Here is an example of how I am referencing the object:
With obj_xl
.Workbooks.Add
.Visible = True
.Sheets.Add
.blahblahblah
End With
The problem is that the procedure has become too large and I need to break the code up into separate modules.
If I try to reference the Excel Application Object from a different module than it was created in, it throws an error ("Ambiguous Name").
I'm sure I could do something with Win API but that seems like it would be overkill.
Any thoughts? Thanks
this is the type of situation that can cause the error "Ambiguous Name"
Function Split(s As String)
MsgBox s
End Function
Function Split(s As String)
MsgBox s
End Function
I know the example is trivial, but what you are looking for is a function , an object and/or a form control with the same names.
If you convert your declaration to Global, you can reference it in all your modules. For example, in one module, put this at the top:
Global obj_xl As Excel.Application
Then in an another module,
Sub xx()
Set obj_xl = New Excel.Application
Debug.Print obj_xl.Name
End Sub