VBA - Class property with reserved keyword as the name - vba

In VBA, is there any known mechanism to fool the compiler into allowing the use of reserved keywords as names for class properties? For example, I would like to create a property called Select in one of my class modules. However, the compiler flags my declaration as an error. Below is the syntax I used:
Public Property Get Select() As Database.SQLStatements
End Property
Database is my VBA project name and SQLStatements is one of the class modules I created. Also, I'm running the code in MS Access 2010.

You can do that and use any keyword/reserved word in your VBA. But then it will make your code a lot messy and very hard to read/debug/maintain.
If you have a bool property named If in your class you will end up with something like this If .If  Then, well, good luck reading that. Also code maintenance like Find/Replace/rename etc. will need extra caution and more work.
Anyhow, if you are willing to go through all this pain, here is how you do it.
After the keywords/reserved words add a invisible blank space using ALT+0160 and that's it. VBA will consider it a perfectly legal name. e.g. If .
Also, you will have to either use intellisense for using these propertynames or manually type the altcode everywhere. That's extra typing.
clsTest
Option Explicit
Private m_sSelect As String
Private m_bIF As Boolean
Public Property Get Select () As String '~~> Select () is actually typed as SelectALT+0160()
Select  = m_sSelect
End Property
Public Property Let Select (ByVal sNewValue As String)
m_sSelect = sNewValue
End Property
Public Property Get If () As Boolean
If  = m_bIF
End Property
Public Property Let If (ByVal bNewValue As Boolean)
m_bIF = bNewValue
End Property
Test Module
Option Explicit
Sub demo()
Dim objTestClass As clsTest
Set objTestClass = New clsTest
With objTestClass
.Select  = "It works. But it will, for sure, create readibility/maintenance issues."
.If  = False
End With
MsgBox objTestClass.Select 
'/ See how hard it will to read/debug this sort of code
With objTestClass
If .If  Then '~~> This line here :)
MsgBox "If prop value is TRUE"
Else
MsgBox "If prop value is FALSE"
End If
End With
End Sub
ALT+0160 <> Space

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

Set a Property Value From Database of Properties

My database has the formname, control, and control property type value stored.
I would like to have a line of code like this.
Forms(i%).Controls(ControlName$)).controlpropertytype$ = NewValue
I am currently using a select case structure to handle the various property types. It would be much simpler to have a single statement take care of it.
Using a helper function, you can achieve this with one line of code. Here's an example of setting a TextBox on Form1 to the value 'aaa':
Option Explicit
Private Sub Test()
CallByName FindForm("Form1").Controls("Text1"), "Text", VbLet, "aaa"
End Sub
Public Function FindForm(ByVal Name As String) As Form
Dim f As Form
For Each f In Forms
If UCase(f.Name) = UCase(Name) Then
Set FindForm = f
Exit Function
End If
Next
End Function
While this is an interesting exercise, I would not recommend this approach. It assumes the form and the control can both be found, but if they can't be found this one-liner will crash your app.
Here's documentation for CallByName.

VBA: Use of Public Property Get in place of Const (for Non-Unicode Characters)

I have a VBA code where I need to define a constant string containing Non-Unicode characters (£). As some might know, VBA Editor doesn't support non-unicode and uses windows "System Locale" setting in regional and language setting to parse/map these characters. The machine I develop the code is set to English system Locale but some of the users have that setting as other languages, e.g. Chinese which turns the string constant to question mark (£ --> ?).
Now, £ = chr(163), however, you cannot use chr as part of defining a constant in VBA. So while this is allowed:
public const mystring = "reference constant string with £"
this is not allowed in VBA"
public const mystring = "reference constant string with " & chr(163).
One way around is to define mystring as a public/global variable:
Constants.bas
public mystring as string
and then set the public variable on start of running code or Excel opening.
ThisWorkbook
Private Sub Workbook_Open()
mystring = "reference constant string with " & chr(163).
End Sub
One issue with this process is the public variables get cleared when error happens or the code stops. To keep the value an alternate I came across was to avoid public variable and instead use a public property get. Note that I have to include this a part of a class.
**.cls
Public Property Get mystring () As String
mystring = "\R;;" & Chr(163)
End Property
So, now I am wondering if there will be any issue with this approach? or perhaps there is a better approach to address the constant variable with non-unicode character.
The main issue is the name of the module, Constants - it's misleading, since neither a public/global variable nor a public get-only property are constants.
Side node, constants not being assignable to non-constant expressions isn't a limitation that's specific to VBA.
Properties are perfectly legal in standard modules, and public get-only properties are a perfectly fine way to expose a read-only value that needs to be constructed at run-time.
the public variables get cleared when error happens or the code stops
Assuming "when error happens" involves clicking End and effectively ending the execution context, that is true of everything exposed anywhere, whether it's a global variable, a public property, an object, or anything that exists in-memory at run-time... and that's just normal stuff - the value is simply available on-demand any time.
Again there's no need to have a class module for this, this is perfectly legal in a standard module:
Option Explicit
Public Property Get MyString() As String
MyString = "\R;;" & Chr(163)
End Property
If re-creating the string every time the property getter is accessed is a problem, then you need a way to persist its value in a backing field - but then, this backing field (whether it's in a class or standard module) only has a value when the execution context exists, meaning it has exactly the same problem a global variable would have: execution stops, it's gone.
One work-around could be to use a class with the VB_PredeclaredId attribute set to True (default is False).
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Class1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Option Explicit
Private internalString As String
Private Sub Class_Initialize()
internalString = "\R;;" & Chr(163)
End Sub
Public Property Get MyString() As String
MyString = internalString
End Property
And now VBA will automatically create an instance of Class1 whenever it's referenced, as soon as it's referenced, and that instance remains "alive" until an End statement is explicitly executed, or the execution context is otherwise terminated. Exactly like, say, a UserForm class, you access this default instance using the class name as an identifier:
Debug.Print Class1.MyString
If the default instance existed when Class1 was referenced, the internalString is returned. If it didn't, then Class_Initialize executes, then the internalString is returned.

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

VBA - Passing variable between modules

I'm new to VB6, and trying to write some macro in use for CorelDraw.
I have a variable that need to be passed from Class module to Standard module, in my Class Module "SaveOptClass" I have a public variable called IsSaved and it's set on the class module:
Public IsSaved As Boolean
Public Sub SaveFile()
If <some triggers> Then
IsSaved = True
End If
In Standard module:
Sub DoSave()
Dim SaveClass As SaveOptClass
Set SaveClass = New SaveOptClass
If SaveClass.IsSaved = True Then
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
Basically I'm trying to pass "IsSaved" boolean value from class module to standard. (If IsSaved is true, save the document or else display a form.)
I have tested that the boolean is True when I executed the code, but I can't get the state to pass to the other module.
Is there something I miss here? Thanks in advance.
As already answered by #shahkalpesh the problem is that you're not using a meaningful instance of SaveOptClass.
In my opinion the best way to design this kind of dependency is by mean of a parameter in the routine is using it and avoid as much as possible the use of global variables.
In your case this brings to this rewriting:
' in someOtherModule
Public Sub DoSave(saveOptObj as SaveOptClass)
If saveOptObj.IsSaved Then ' = True is unnecessary
ActiveDocument.Save
Else
Form1.Show
End If
End Sub
The client code could be:
private saveOptObj as SaveOptClass
Public Sub SaveFile()
If <some triggers> Then
saveOptObj.IsSaved = True
End If
' ....
someOtherModule.DoSave(saveOptObj)
' ...
Consider also, at this point, a renaming of DoSave, given that the actions taken suggest different semantics. In similar cases is preferable moving the If Else logic in the caller. Anyway, if you prefer to group actions with different semantics in the same routine you'd better use namings like DoSaveOr<SomethingElse>.