Declare variable to be initialized in Module1 and used in UserForm module - vba

I have a project with one module (main) and one userform module (myUserForm).
I declare the variable as global on top of the module main:
Dim myGlobal As MyType
... then I set my variable inside module main:
Public Sub mySubInMain()
Set myGlobal = New MyType
End Sub
... but when I try to use it in the module myUserForm:
Private Sub oneSubOfTheForm()
myGlobal.Name = "something"
End Sub
... I get Object required exception. How should I declare my variable myGlobal to live in the other stack?

If the variable is in a module called main, and you declare it Public, i.e.
Public myGlobal As MyType
you should be able to refer to it in your UserForm as main.myGlobal (or simply as myGlobal, but it is usually better to qualify it so that it is obvious where it resides).

You can use a Public property in a standard module to get the object so you can control its state.
Option Explicit
Private type_ As CustomType
Public Property Get MyType() As CustomType
If type_ Is Nothing Then Set type_ = New CustomType
Set MyType = type_
End Property

You have to assign to myGlobal something, as exception states. So, you have to first call mySubInMain, then you can use myGlobal object.

Related

Is it possible to change the appearance of a custom class's object in the VBA editor's locals and watch windows? [duplicate]

Although an experienced VBA programmer it is the first time that I make my own classes (objects). I am surprised to see that all properties are 'duplicated' in the Locals Window. A small example (break at 'End Sub'):
' Class module:
Private pName As String
Public Property Let Name(inValue As String)
pName = inValue
End Property
Public Property Get Name() As String
Name = pName
End Property
' Normal module:
Sub Test()
Dim objTest As cTest
Set objTest = New cTest
objTest.Name = "John Doe"
End Sub
Why are both Name and pName shown in the Locals Window? Can I in some way get rid of pName?
As comments & answers already said, that's just the VBE being helpful.
However if you find it noisy to have the private fields and public members listed in the locals toolwindow, there's a way to nicely clean it up - here I put the Test procedure inside ThisWorkbook, and left the class named Class1:
So what's going on here? What's this?
Here's Class1:
Option Explicit
Private Type TClass1
Name As String
'...other members...
End Type
Private this As TClass1
Public Property Get Name() As String
Name = this.Name
End Property
Public Property Let Name(ByVal value As String)
this.Name = value
End Property
The class only has 1 private field, a user-defined type value named this, which holds all the encapsulated data members.
As a result, the properties' underlying fields are effectively hidden, or rather, they're all regrouped under this, so you won't see the underlying field values unless you want to see them:
And as an additional benefit, you don't need any pseudo-Hungarian prefixes anymore, the properties' implementations are crystal-clear, and best of all the properties have the exact same identifier name as their backing field.
All the Inspection windows not only show the public interface of the objects to you, but also their private members. AFAIK there is nothing you can do about it.
Consider it a nice feature to get even more insights while debugging.
In my experience this is less of an issue in real world objects as they tend to have more fields and properties. Assuming a consistent naming (as your example shows), fields and properties are nicely grouped together.
If you really dont want to see even Mathieu's This you could wrap it into a function. This is a bit more involved, and can be achieved using
a second class that stores the data in public variables. This will be marginally slower then Mattieu's implementation
a collection object that accesses the data using keys. This does not require additional clutter in the project exporer's 'class module' list but will be a little slower if you call the This repeatedly in fast sucession
An example for each is given below. If you break in the Class's Initialisation function, you can add me to the watch window and only the Name property will be listed
Using 2 Objects example
insert a classmodule and name it: InvisibleObjData
Option Explicit
Public Name As String
Public plop
Private Sub Class_Initialize()
Name = "new"
plop = 0
End Sub
insert a classmodule and name it: InvisibleObj
Option Explicit
Private Function This() As InvisibleObjData
Static p As New InvisibleObjData 'static ensures the data object persists at successive calls
Set This = p
End Function
Private Sub Class_Initialize()
This.Name = "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
This.plop = 111: Debug.Print This.plop
End Sub
Property Let Name(aname As String): This.Name = aname: End Property
Property Get Name() As String: Name = This.Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleObj
If you dont like splitting the class over two objects, a similar behaviour can be generated using a 'wrapped' collection object:
insert a classmodule and name it: InvisibleCol
Option Explicit
Private Function This() As Collection
Static p As New Collection
'static ensures the collection object persists at successive calls
'different instances will have different collections
'note a better dictionary object may help
Set This = p
End Function
Private Function add2this(s, v)
'a better dictionary object instead of the collection would help...
On Error Resume Next
This.Remove s
This.Add v, s
End Function
Private Sub Class_Initialize()
add2this "name", "invisible man": Debug.Print Name
Me.Name = "test": Debug.Print Name
add2this "plop", 111
Debug.Print This("plop") ' use the key to access your data
Debug.Print This!plop * 2 ' use of the BANG operator to reduce the number of dbl quotes
' Note: This!plop is the same as This("plop")
End Sub
Property Let Name(aname As String): add2this "name", aname: End Property
Property Get Name() As String: Name = This!Name: End Property
'_______________________________________________________________________________________
' in the immediate window type
'
' set x=new invisibleCol

VBA global class variable

My obstacle is trying to get multiple subs to recognize class variables. When I try to declare them globally, I get a compile error: "Invalid outside procedure". Then, when I run a public function or sub to declare the variables, they remain undefined in the other subs. I want multiple subs to recognize the variables because their values are supposed to be altered via UserForm, and then utilized in a different sub.
If it could work in this manner, great, but I understand that my design could fundamentally be flawed. Please advise!
This is my Class definition, inserted as a Class module named "cRSM":
Option Explicit
Private pName As String
Private pDesiredGrowth As Double
'Name of RSM
Public Property Get Name() As String
Name = pName
End Property
Public Property Let Name(Value As String)
pName = Value
End Property
'Growth property
Public Property Get DesiredGrowth() As Double
DesiredGrowth = pDesiredGrowth
End Property
Public Property Let DesiredGrowth(Value As Double)
If Value > 0 And Value < 1 Then
pDesiredGrowth = Value
End If
End Property
This is invalid procedure error (which I put in the Global Declarations section):
'Bedoya
Dim Bedoya As cRSM
Set Bedoya = New cRSM
Bedoya.Name = "Bedoya"
And this is the "variable not defined error" (within a private sub):
Private Sub Add_Click()
**Bedoya.DesiredGrowth** = Txt2.Value
Thank you for your time
In a standard module (I name mine MGlobals), put
Public Bedoya As cRSM
Then in another standard module (I name mine MOpenClose), put
Sub Initialize()
If Not Bedoya Is Nothing Then
Set Bedoya = New cRSM
End If
End Sub
Any default properties you want set should be set in the Class_Initialize procedure. In any procedure that you want to use Bedoya, use
Initialize
and it will instantiate the global variable if necessary. The only difference between this and the New keyword is that you can't accidentally instantiate the variable with this method. You either call Initialize or you don't. A lot of VBA developers use New, but almost never do for that reason.
If I understood well You want a global object.
You can put the declaration in module like
public Bedoya As cRSM
then you have create the object ... you can use a global event inside the Workbook like
Private Sub Workbook_Open()
Set Bedoya = New cRSM
Bedoya.initialize("Bedoya") 'a method to initialize private variables
End Sub
Now you can use the global object. You have to restart the excel file or run this method manually.
Is not good style to use global variables, but sometimes is the more easy to do :P
What you want to do nowadays is done using singleton Software Pattern, but this is for other day hehehe

"Class.Property" is not accessible in this context because it is 'Private'

I'm looking access private member of a class in myproject.vb file. My function header in myproject.vb looks like this,
Public Function MyVbFunction(ByVal objRequest As BookRequest) As Integer
Try
For Each book As Book In objRequest.m_Books
If Myvbfucntion2(book) = 1 Then
Return True
End If
Next
End Try
End Function
Book Request Class has property m_Books as Private of type BookCollection class
Public Class BookRequest
Private m_Books As ExamCollection
'
'
'
End Function
It is not allowing me to access 'book', showing as m_Books as private member. How can i access m_Books to pass to Myvbfucntion2.
The compiler tells you the answer. It can't access m_exams because it is private. So make it public, that will fix the problem:)
Private means it is only accessible from the same class.
Public means it is accessible from anywhere.
You could make a public property and use the get and set functions to tie your property to a private member if you wish.
Public Property Books() As BookCollection
Get
Return m_Books
End Get
Set(ByVal Value As BooksCollection)
m_Books = Value
End Set
End Property
Apologies for any code formatting issues. I wrote this on my mobile phone

How do I avoid down-casting to the interface class?

I'm using Excel VBA (Excel 2010) and I've run into a problem when attempting to use inheritance. Basically, I have an interface MyInterface and an implementing class MyImplementation. In the VBA code, when I refer to a Dim of type MyInterface I can only access members defined on that interface - this is expected. When I refer to a Dim of type MyImplementation I cannot access members defined on the interface it implements - not expected.
Why can I not call the interface property directly on the implementation class?
MyInterface
Option Explicit
Public Property Get Text() As String
End Property
MyImplementation
Option Explicit
Implements MyInterface
'The implementation of the interface method'
Private Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
Public Property Get MoreText() As String
MoreText = "Yes, some more text!"
End Property
MainModule - usage examples
Function Stuff()
Dim impl As New MyImplementation
Dim myInt As MyInterface: Set myInt = impl
'The following line is fine - displays "Yes, some more text!"
MsgBox impl.MoreText
'This is also fine - displays "Some text"
MsgBox DownCast(impl).Text
'This is also fine - displays "Some text"
MsgBox myInt.Text
'This is *not* fine - why??
MsgBox impl.Text
End Function
Function DownCast(ByRef interface As MyInterface) As MyInterface
Set DownCast = interface
End Function
The main question is how can I avoid down-casting?
Note - the example above is intentionally contrived. I realize it's generally bad practice to refer directly to implementation classes.
When I refer to a Dim of type MyImplementation, I cannot access members defined on the interface it implements - not expected.
The solution is to change your expectation. That's the way things work in VBA: VBA classes implement COM interfaces (such as IUnknown) without exposing them publicly.
If you want to expose your interface's members from the class, you have to do so explicitly:
Option Explicit
Implements MyInterface
'The implementation of the interface method'
Private Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
Public Property Get MoreText() As String
MoreText = "Yes, some more text!"
End Property
Public Property Get Text() As String
Text = MyInterface_Text
End Property
Simply declare the implementing methods as Public instead of Private will do:
Option Explicit
' Class MyImpl
Implements MyInterface
'The implementation of the interface method'
'Notice the Public here instead of private'
Public Property Get MyInterface_Text() As String
MyInterface_Text = "Some Text"
End Property
The only thing to keep in mind is that to invoke the methods on the implementation you'll need to use a longer name:
Dim instance as MyImpl
' initialize your instance
instance.MyInterface_Text
' instead of instance.Text
That's all.

vba/catvba class instantiation

i want to declare class globaly
here is my example:
i want to use class clsIEError that looks exactly like this:
Option Explicit
Public Sub m(msg As String, Optional title As String = "Title:")
'Attribute Value.VB_UserMemId = 0
'this method will be used as defualt method
'and here are attributes msg and title used to create some inteface
End Sub
and this is how it works example1:
Sub CATMain()
Dim ie As clsIEError
Set ie = New clsIEError
ie "test", "title"
Set ie = Nothing
End Sub
but my problem is that i want to have it globally example2:
Option Explicit
Public ie As clsIEError
Private Function Init()
Set ie = New clsIEError
End Function
Sub CATMain()
Call Init
' and to use it same as in example 1
ie "test", "title"
' but i am able to use it only like:
' ie.m "test", "title" 'works as expected
Set ie = Nothing
End Sub
why with public default method doesnt work?
I can confirm that this doesn't work just as you describe. I get "Expected procedure, not variable" at run-time, but no compile errors. It must be a bug in the VB parser, but that's the only explanation I can come up with.
I always thought that the attribute had to match the element name. Where you have
Attribute Value.VB_UserMemId = 0
I thought you should have
Attribute m.VB_UserMemId = 0
But it seems to work (with the locally declared variable) either way. It's a terrible answer, but the answer is to explicitly call the method. Sorry.
I am not sure that it will answer your question explicitly, but, as for me I wanted to create an instance of a class that would be accessible throughout my project. Basically I needed to create Static Class or Singleton. I found this post that was quite useful.