Did you know that there’s code in your VBA projects that you can’t see in your editor? Want to see it? (Of course you do.)
So, just right click on any module and click Export.
Save the file some where convenient and open it up in any text editor, like Notepad.
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "Class1" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = False Option Explicit '@TestClass 'TestMethod Public Sub TestMethod1() 'hello there End Sub
And here we see the hidden header that is in all of your VBA classes and modules. However, only three of these do anything in VBA, to the best of my knowledge.
- VB_Name
- VB_PredeclaredId
- VB_Exposed
VB_Name is pretty self explanatory, it stores the name you gave it in the properties window. VB_PredeclaredId is much more interesting. If you change the value of VB_PredeclaredId to true in a class file, that class will be given a global default instance when your code starts running. Now, globals are bad, we all know that, so use this with care, but this feature allows us to simulate static class methods in our VBA code. What does that all really mean? It means that we can access class methods without first creating a new instance of the class. We can access the methods via our global default instance.
So, instead of
Dim math As New clsMath math.Add 1, 2
We can do this
Math.Add 1, 2
It’s a poor example, I know. We could accomplish the piece of code above with a regular module, but it really does allow us to do some pretty cool things.
I won’t touch much on the remaining attribute, VB_Exposed, other than to say that it holds the value of the class instancing property. 1 == false. 2 == true. None of the other values are available in VBA. They weren’t brought over from VB6.
But wait, there’s more…
That’s the just the header, the module level attributes. There are also a number of attributes that can be applied to module variables (fields), properties, and procedures. There’s a slightly different syntax for variables, so let’s start there.
- VB_VarUserMemId: Determines the order of the variables in the Object Broswer. A value of 0 (zero) declares the variable to be the default member of the class.
- VB_VarDescription: The value of this attribute will be displayed in the Object Broswer.
So, placing this code into our module, and re-importing it back into our project, produces this in the Object Browser.
Public FooCount As Integer Attribute FooCount.VB_VarDescription = "Specifies the number of Foos"
This allows us to put our documentation into a highly visible location and the same works for Properties and Procedures, only we drop the “Var” from the attributes. So, change our public field into a read-only default property.
private pFooCount As Integer Property Get FooCount() As Integer Attribute FooCount.VB_Description = "Returns the number of Foos" Attribute FooCount.VB_UserMemId = 0 FooCount = pFooCount End Property
Now, when we have a Class1 object, it will return the FooCount by default.
Dim cls As New Class1 Dim i As Integer i = cls ' is equivalent to i = cls.FooCount
When might you want to do this? Well, for one, when you want to create a strong typed collection. Which brings us to one last detail. There is one more special value for VB_UserMemId and that value is -4. Negative 4 always indicates that the function being marked should return a [_NewEnum] enumerator. This sample is from the stack overflow link above (corrected to make Item the default member of the class).
Public Property Get Item(index As Long) As cShape Attribute Item.VB_UserMemId = 0 Set Item = myCustomCollection.Item(index) End Property Public Property Get NewEnum() As IUnknown Attribute NewEnum.VB_UserMemId = -4 Attribute NewEnum.VB_MemberFlags = "40" Set NewEnum = myCustomCollection.[_NewEnum] End Property
Below is a summary of the syntax and values.
' Header Attribute VB_Name = "ClassOrModuleName" Attribute VB_GlobalNameSpace = False ' ignored Attribute VB_Creatable = False ' ignored Attribute VB_PredeclaredId = False ' a Value of True creates a default global instance Attribute VB_Exposed = True ' Controls how the class can be instanced. 'Module Scoped Variables Attribute variableName.VB_VarUserMemId = 0 ' Zero indicates that this is the default member of the class. Attribute variableName.VB_VarDescription = "some string" ' Adds the text to the Object Browser information for this variable. 'Procedures Attribute procName.VB_Description = "some string" ' Adds the text to the Object Browser information for the procedure. Attribute procName.VB_UserMemId = someInteger ' 0: Makes the function the default member of the class. ' -4: Specifies that the function returns an Enumerator.
Note that there can be only one default member in any class. You can not have both a default member variable and a default procedure.
#1 by Johny Why on July 12, 2016 - 9:14 pm
hi, great site! i noticed that If you change the value of VB_PredeclaredId to true in a class, you do NOT need to even declare a variable for the class. You can reference the class by name directly.
LikeLike
#2 by Christopher J. McClellan on July 13, 2016 - 2:20 am
Thanks! That’s correct. Setting PredeclaredId to true creates a default global instance. It’s the same trick UserForms use. Cheers.
LikeLike
#3 by Johny Why on July 16, 2016 - 12:43 am
new question: can VB_PredeclaredId be used with class arrays? This is used to share event-code between multiple controls:
Example:
in a class called cControlEvents, in declarations, i can say:
Public WithEvents lstBox As MSForms.ComboBox
Private Sub lstSearch_Change()
Debug.Print lstBox
End Sub
then, in a module, i can say:
Dim oControlEvents() As New cControlEvents
Sub WireUpControls
ReDim Preserve oControlEvents(1)
Set oControlEvents(0).lstBox = MyListBox
Set oControlEvents(1).lstBox = MyOtherListBox
End Sub
——
I’m concerned that we are creating a new class as soon as the Workbook is opened (due to New keyword). You cannot exclude the New keyword in the module declaration. You cannot do this in WireUpControls:
Set oControlEvents = New cControlEvents
I’m wondering if VB_PredeclaredId can improve this code.
thx!
LikeLike
#4 by Rubberduck VBA on July 19, 2017 - 2:45 pm
The latest build of Rubberduck now allows you to modify attributes using magic comments / “annotations”:
Module level:
‘@PredeclaredId
‘@Exposed
Member level:
‘@Description(“this will be the attribute’s value”)
‘@DefaultMember
‘@Enumerator
An inspection compares annotations with the actual attributes, and a quickfix symchronizes them for you!
https://github.com/rubberduck-vba/Rubberduck/releases
LikeLike