These two subs do the same thing when inside a class.
Sub DemoMe( )
Me.AboutMe ' Calls AboutMe procedure.
End Sub
Sub DemoMe( )
AboutMe ' Does the same thing.
End Sub
What is the point? Does the Me keyword do anything? What is the preferred way of an object accessing its own members?
tldr; No, although there are situations where it can be useful.
From the VBA language specification (5.3.1.5):
Each procedure that is a method has an implicit ByVal parameter called
the current object that corresponds to the target object of an
invocation of the method. The current object acts as an anonymous
local variable with procedure extent and whose declared type is the
class name of the class module containing the method declaration. For
the duration of an activation of the method the data value of the
current object variable is target object of the procedure invocation
that created that activation. The current object is accessed using the
Me keyword within the <procedure-body> of the method but cannot be
assigned to or otherwise modified.
That's all it is, just a "free" local variable that refers to the specific instance that the method is being called on. This also happens to be the default context for the procedures during their invocation, so it can be omitted if the code is intended to operate on the current instance. Although as #HansPassant points out in the comment above, it also allows the editor to bind to the interface and provide IntelliSense.
That said, there are a couple instances where you would either want to or have to use it (this is by no means an exhaustive list):
Naming collisions:
If your class has a member that "hides" a built-in VBA function, it can be used to make the scope explicit:
Public Property Get Left() As Long
'...
End Property
Public Property Get Right() As Long
'...
End Property
Public Property Get Width() As Long
Width = Me.Right - Me.Left
End Property
Equity Checks:
Public Function Equals(other As Object) As Boolean
If other Is Me Then
Equals = True
Exit Function
End If
'...
End Function
Fluent Functions:
This can be a useful pattern for compositing objects - you perform an action, then return the instance of the class so they can be "chained". Excel's Range interface does this in a lot of cases:
Public Function Add(Value As Long) As Class1
'Do whatever.
Set Add = Me
End Function
Public Sub Foo()
Dim bar As New Class1
bar.Add(1).Add(1).Add 1
End Sub
Not any more than there are reasons to use this in Java, C#, or any other language: it's a reserved identifier that represents the current instance of the class - what you do with that is up to your imagination.
What is the preferred way of an object accessing its own members?
Indeed, an object doesn't need the Me keyword to access it own public interface. Same as this in other languages, I'd even call it redundant. However it can sometimes be a good idea to explicitly qualify member calls with Me, especially when the class has a VB_PredeclaredId attribute (e.g. any UserForm): referring to UserForm1 in the code-behind of UserForm1 yields a reference to the default instance of the class, whereas qualifying member calls with Me yields a reference to the current instance of that class.
Accessing Inherited Members
VBA user code can't do class inheritance, but a lot of VBA classes do have a base class. The members of UserForm when you're in the code-behind of UserForm1, and those of Worksheet when you're in the code-behind of Sheet1, aren't necessarily easy to find. But since the inherited members show up in IntelliSense/auto-complete, you can type Me. and browse a list of members inherited from the base class, members that you would otherwise need to know about in order to invoke.
A class creating an instance of itself inside itself? That I've never seen.
You're missing out! I do this all the time, to enable referring to the object instance held by a With block, inside a Factory Method - like this GridCoord class.
Public Function Create(ByVal xPosition As Long, ByVal yPosition As Long) As IGridCoord
With New GridCoord
.X = xPosition
.Y = yPosition
Set Create = .Self
End With
End Function
Public Property Get Self() As IGridCoord
Set Self = Me
End Property
Note that while the GridCoord class exposes a getter and a setter for both X and Y properties, the IGridCoord interface only exposes the getters. As a result, code written against the IGridCoord interface is effectively working with read-only properties.
Another use is to get the name of the class module, without needing to hard-code it. This is particularly useful when raising custom errors: just use TypeName(Me) for the Source of the error.
The Builder Pattern notoriously returns Me, which enables a "fluent API" design that makes it possible to write code that incrementally builds complex objects through chained member calls, where each member returns Me (except the final Build call, which returns the type of the class being built):
Dim thing As Something
Set builder = New ThingBuilder
Set thing = builder _
.WithFoo(42) _
.WithBar("test") _
.WithSomething _
.WithSomethingElse
.Build
#PBeezy : In addition to my comment :
Me, refers to the object it's coming from so AboutMe resides in the class. If you had another instance, say this is Class1, you'd have dim c as Class1, as soon as you create an instance of Class1 in Class1, you need to tell the compiler which class you are using, the holding class or the instance created in, where, me.class1.aboutme would be logically valid. You can also create, a class for each cell in a workbook, then you could refer to A1's class from B1's class. Also, if there is a public function/sub called AboutMe, this also helps.
Class (clsPerson)
Public c1 As clsPerson
Public strPersonName As String
Public Function NAME_THIS_PERSON(strName As String)
strPersonName = strName
End Function
Public Function ADD_NEW_CHILD(strChildName As String)
Set c1 = New clsPerson
c1.strPersonName = strChildName
End Function
Normal module
Sub test()
Dim c As New clsPerson
c.NAME_THIS_PERSON "Mother"
c.ADD_NEW_CHILD "Nathan"
Debug.Print c.strPersonName
Debug.Print c.c1.strPersonName
End Sub
Gives these results
Mother
Nathan
Related
I am reading up on creating class factories here: https://rubberduckvba.wordpress.com/2018/04/24/factories-parameterized-object-initialization/ and I am confused why they are making the implemented functions private, wouldn't we want them to be public so we can access them?
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Something"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Type TSomething
Bar As Long
Ducky As String
End Type
Private this As TSomething
Implements ISomething
Public Function Create(ByVal initialBar As Long, ByVal initialDucky As String) As ISomething
With New Something
.Bar = initialBar
.Ducky = initialDucky
Set Create = .Self
End With
End Function
Public Property Get Self() As ISomething
Set Self = Me
End Property
Public Property Get Bar() As Long
Bar = this.Bar
End Property
Friend Property Let Bar(ByVal value As Long)
this.Bar = value
End Property
Public Property Get Ducky() As String
Ducky = this.Ducky
End Property
Friend Property Let Ducky(ByVal value As String)
this.Ducky = value
End Property
Private Property Get ISomething_Bar() As Long
ISomething_Bar = Bar
End Property
Private Property Get ISomething_Ducky() As String
ISomething_Ducky = Ducky
End Property
Also, why do you need to provide get and let properties for public variables in an interface?
They should be Private.
The reason is because how interfaces work in VBA: the Public members of a class module define its default interface. That means the public members of Class1 define what members Class2 must implement if it Implements Class1.
So if you make Class1_DoSomething public, then you're exposing that member on the default interface of Class2, and that's... not pretty at all.
What interface you access an object with, is determined by how you declare it.
Dim thing As Class1
Set thing = New Class1
If thing is or implements Class1, then the code after this declaration can invoke all the members exposed by the default interface of Class1 (i.e. its public members).
If Class1 implements ISomething and we declare it like this:
Dim thing As ISomething
Set thing = New Class1
Now the members we get to work with are the members defined by the public members of the ISomething class/interface.
When you implement an interface or handle events, you should never manually type the signatures; instead, pick the interface (or event provider) from the upper-left dropdown in the code pane, then pick a member from the upper-right dropdown: the VBE automatically creates the correct procedure with the correct signature, and it's always going to be a Private member - rule of thumb, anything that has an underscore in its name in VBA has no business being Public
As for why you must supply Get and Let accessors for what you defined as a public field (/variable) on an interface class... Fields are implementation details, they should never be Public in the first place. Objects expose properties, not fields - keep fields for the private internal state of the implementing class.
The reason is technical: VBA code gets compiled into a COM type library, and that library sees your public variable and says "that's going to have to be a PUT and a GET method", and the VBA code implementing that interface thus needs to implement a property for every public field, because public fields compile down to properties.
This does have interesting implications with regards to the practice of exposing a public field on a class module (breaks encapsulation vs compiles down to a property anyway!), but that is a whole other discussion.
I want to get an Object within a Collection as a String without calling a function for it.
e.G.:
In java i can
System.out.print(objVarXY)
and the compiler will automatically call the objVarXY.toString() function (if implemented)
in VBA something like this
Debug.Print parameterListe.LList.Item(1)
will cause an error.
Debug.Print parameterListe.LList.Item(1).toString
will work, if i implemented a toString Subfunction.
But what if i dont know what kind of object will be inside my LList collection?
Debug.Print will implicitly attempt coerce the given value expression into a String for output.
When you Debug.Print an object, VBA attempts to Let-coerce the object into a value - if the object doesn't have a default member that ultimately yields a value that can be implicitly converted to a String, then you get run-time error 438 "object doesn't support this property or method" if the class doesn't have a default member.
If the object is user code (i.e. your own class module), and if it makes sense to do so, you could add a default member yourself, and have the class responsible for knowing how to represent itself as a String (note that the VB attribute is hidden in the VBE code panes, and must be edited outside the VBE - unless you're using Rubberduck, in which case you can simply add a #DefaultMember annotation and synchronize annotations/attributes):
'#DefaultMember
Public Function ToString() As String
Attribute ToString.VB_UserMemId = 0
'...
End Function
But all this does is give the class the ability to be essentially treated as a String, through implicit member calls. I'd call that something like an abuse a language feature (it's through this mechanism that Debug.Print Excel.Application outputs the application's Name, or Debug.Print adoConnection outputs the connection's ConnectionString property), since as you noted, you might as well just invoke that ToString method explicitly.
If the object doesn't know how to represent itself as a String, then something, somewhere will have to. In Java (IIRC) and .NET this would essentially be the default ToString implementation:
Debug.Print TypeName(objVarXY)
...which is rather useless, but that's essentially what ToString does by default.
Whether you're writing Java, C#, or VBA, there needs to be code responsible for knowing how to represent objVarXY as a String.
Sadly VBA doesn't do fancypants pattern matching, so we can't Select Case TypeOf obj like in C# (it only recently got that ability - don't know about Java), and since Select Case TypeName(obj) wouldn't be type-safe, I'd go with If...ElseIf:
Public Function Stringify(ByVal obj As Object) As String
If TypeOf obj Is Something Then
Dim objSomething As Something
Set objSomething = obj ' cast to Something interface
Stringify = objSomething.SomeProperty
ElseIf TypeOf obj Is SomethingElse Then
Dim objSomethingElse As SomethingElse
Set objSomethingElse = obj ' cast to SomethingElse interface
Stringify = objSomethingElse.AnotherProperty & "[" & objSomethingElse.Foo & "]"
'ElseIf TypeOf obj Is ... Then
' ...
Else
' we don't know what the type is; return the type name.
Stringify = TypeName(obj)
End If
End Function
Obviously if the collection always involves classes that you own, the better solution is to have each object know how to represent itself as a String value.
But, having user classes expose a ToString method on their default interface isn't ideal: since we don't know what type we're getting from the collection, all we have is Object and a late-bound call - and no compile-time guarantee that the class implements a ToString method, and no compiler warning if we try to invoke, say, ToStrnig.
The solution is to not put ToString on the classes' default interface, and formalize the behavior with some IString class module, which might look like this:
Option Explicit
Public Function ToString() As String
End Funtion
Yup, that's the whole class.
Now the user classes that need to be representable as strings, can do this:
Option Explicit
Implements IString
Private Function IString_ToString() As String
' todo: implement the method!
End Function
And now we can have early-bound assurance that the objects have a ToString method:
Dim o As Object
For Each o In MyCollection
If TypeOf o Is IString Then
Dim s As IString
Set s = o 'cast to IString interface
Debug.Print s.ToString
Else
Debug.Print TypeName(o)
End If
Next
At the end of the day there's no magic, regardless of what language you're using.
Something like that does not exist in VBA there are only the Type conversion functions like CStr() to convert eg. an Integer into a String.
If you eg need to convert a Collection into an Array you will need to use a function for that.
But what if I don't know what kind of object will be inside my LList collection
Then you will need to determine which object it is (you would probably expect eg 5 different possible objects) and do something like a Select Case for each different object type to convert this to a String.
I have been trying to clean up my code a bit and make it more similar to the Excel object model, and I was wondering if it is possible to create a "loopable" container class in VBA, e.g. similar to how you can do:
Dim Sheet As Worksheet
For Each Sheet In ThisWorkbook.Worksheets
' ...
Next Sheet
I want this functionality for my own container.
Say I create my own class called Container which contains items of some class ItemType (this can simply be an empty class for this example):
' Class Container
' The container contains items of a class I will call ItemType
Private Type MContainer
Items As Collection ' Could also be implemented in terms of an array
End Type
Private This As MContainer
Public Property Get Item(ByVal Index As Long) As ItemType
Attribute Item.VB_UserMemId = 0 'Makes it so I can access elements like a Collection
Set Item = This.Items(Index)
End Property
Public Function Add() As ItemType
This.Items.Add
Set Add = This.Items(This.Items.Count)
End Function
Private Sub Class_Initialize()
Set This.Items = New Collection
End Sub
I then want to loop through the items in my container with the For Each..., but this doesn't work. See the following example for how I ideally want it to work:
Public Sub MyMethod()
Dim Stuff As New Container
Stuff.Add
Dim Element As ItemType
For Each Element In Stuff ' <- This will not work
' Do something
Next Element
End Sub
The final For loop is what I am looking at making work. Is this possible? Basically the issue is that I can't call For Each on my Container class similar to how you can with e.g. the Excel.Sheets class. Is this possible to achieve in VBA?
For Each iteration requires a special member attribute value to work, and a NewEnum property or function returning an IUnknown.
Every collection class that can be iterated with a For Each loop has a hidden [_NewEnum] member (the square brackets are required for accessing the hidden member, since the underscore prefix is illegal for an identifier in VBA.
Tweaking module and member attributes isn't possible to do directly in the VBE, so you need to remove/export the module, modify it in e.g. Notepad++, save the changes, then re-import it into your project.
Or, have Rubberduck (disclaimer: I contribute to this open-source project) do it for you, using annotations (aka "magic comments"):
'#Enumerator
'#Description("Gets an enumerator that iterates through the internal object collection.")
Public Property Get NewEnum() As IUnknown
Set NewEnum = this.Items.[_NewEnum]
End Function
'#DefaultMember
'#Description("Gets/sets the element at the specified index.")
Public Property Get Item(ByVal index As Long) As ItemType
Set Item = this.Items(index)
End Property
Then parse the project (Ctrl+`) and bring up the Inspection Results toolwindow (Ctrl+Shift+i) - there should be a number of "Missing Attribute" results under "Rubberduck Opportunities":
Click "Fix all occurrences in module" in the bottom pane, to synchronize the hidden attributes with the annotation comments.
If you have "Missing Annotation" results, Rubberduck has determined that a module/member has a non-default value for a given attribute, and is able to similarly add an annotation comment that surfaces/documents it with a comment.
The Code Explorer (Ctrl+R), the Rubberduck toolbar, and the VBE's own Object Browser (F2) will display the contents of the VB_Description attribute, so #Description annotations are particularly useful to have on any public procedure.
Object Browser:
Code Explorer:
Rubberduck toolbar:
Add this to your class
Public Function NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
Set NewEnum = Items .[_NewEnum]
End Function
An alternative approach to this issue is not to use a Collection but a Scripting.Dictionary. One of the advantages of a scripting dictionary is that it can return arrays of the keys and items of the dictionary. Iterating over an array in VBA is a trivial exercise.
In a VBA custom class module (and by extension VB6?), if there's a private instance variable defined as a public enumeration from within the same class, should it be qualified..? If so, how..?
I often have project references with identical types. For instance, Excel and Word both have a Range object. As such, I'm always as specific as possible when declaring variables, such as Excel.Range or Word.Range, rather than just Dim R As Range.
But what about Enumerations..? How to be specific with these..? I've tried to qualify a variable as an enumeration, but I always get an error. I tried moving the enumeration definition to another custom class, but this didn't help. I don't often create classes for VBA, so I may be barking up the wrong tree with this.
If I qualify with the Me. keyword, I get the error:
"Compile error: Expected New or type name."
If I qualify with the custom class name, I get the error:
"Compile error: User-defined type not defined"
Here's a full example:
'Custom class module.
Option Explicit
Public Enum ImageAspect
ImageAspectHorizontal
ImageAspectVertical
End Enum
' Example 1:
' Qualified to use built-in Excel enumeration. This works.
Private logoAspect1 As Excel.XlOrientation
' Example 2:
' Uses the enum defined in this class. This works, but...
' Can this be qualified..? How..? Is it even necessary?
Private logoAspect2 As ImageAspect
' Example 3:
' This does not work.
Private logoAspect3 As Me.ImageAspect
' Example 4:
' This does not work.
Private logoAspect4 As ThisClass.ImageAspect
Public Property Let Aspect(ByVal pAspect As ImageAspect)
logoAspect2 = pAspect
End Property
Public Property Get Aspect() As ImageAspect
Set Aspect = logoAspect2
End Property
They can only be classified at the project level.
Public types and enums are declared at the project level. This means they can't be classified further than the project, and you can't have multiple types or enums with the same name in a single project.
Public Enum ImageAspect
ImageAspectHorizontal
ImageAspectVertical
End Enum
Private logoAspect4 As VBAProject.ImageAspect
VBAProject is the default name of an Excel VBA Project. It can be renamed in the VBE under Tools -> VBAProject Properties
This is independent on if they're stored in a class module or normal module. They're always declared at the project level. If you want re-usable code across projects, you can store your enums and types in a different file with a distinct project name and reference that file to import the types and enums.
Our object model contains a class called Unit and a collection of these called Units (which is stored in a Dictionary). These objects have unique Names and Keys (they originally came from a SQL db that enforced this) so I have added:
Public Units(N as String) As Unit ...
Public Units(K as Integer) As Unit...
which return a Unit object from the Units collection.
In Excel VBA, one can refer to most objects using similar methods; Worksheets(1) returns the first sheet, while Worksheets("Bob") returns the named sheet. But they have one additional method, Worksheets, which returns the entire collection. It's as if they have this method...
Public Worksheets() As List(Of Worksheet)
But you can't use List in interop (right?) so it's more like...
Public Worksheets() As ArrayList
So how would I do the same basic API in .net with interop? That is, have three methods...
Public Units(N as String) As Unit ...
Public Units(K as Integer) As Unit...
Public Units() As ArrayList...
As I understand it only the first method of a given name is exported (is this correct?). So how does Excel do it, and can I fake that in .net?
VBA's Worksheets is not a method. It is a class, Worksheets, that has a default property Item that accepts a parameter of type Variant. There is no overloading (COM does not support it), it's just that Variant can hold both a number or a string.
If you want a similar structure in VB.NET, you can have a collection class that implements a default property as VB.NET understands it, and this time you can overload it.
Public Class UnitsCollection
Default Public ReadOnly Property Item(ByVal i As Integer) As Unit
Get
Return ...
End Get
End Property
Default Public ReadOnly Property Item(ByVal i As String) As Unit
Get
Return ...
End Get
End Property
End Class