Class Property Fixed Values
In my previous post on a Custom Collection Class, I used an example of a People collection with Person items.
As well as First Name, Last Name, and City, I had originally included the Gender property (Female, Male, Unknown), but removed it just prior to posting.
I purposely left it out because I didn’t want to distract from the core topic (collection classes), and because I have a special way of dealing with properties of this type.
I could have just defined Gender as a String datatype.
That would work.
We could be a little more rigid with our variable, and define an Enum.
Unknown
Female
Male
End Enum
…
Public Gender as GenderEnum
It’s an OK approach, though it has a couple of small issues.
- I can set Gender to numbers other than the Enums 0, 1, and 2. MyPerson.Gender = 7 will not error.
- The required helper functions GenderEnumToString, and the StringToGenderEnum are stored in a separate place to the Gender variable, probably buried in a giant module. While it’s improbable that there will be more than 2 genders, your property might grow over time. It’s nice to have related code all in the same place.
What follows is a different approach that mixes Classes with Enums. You get the validation capability of a Class property, the Intellisense of an Enum, and related code living in the same place.
You get code that reads like
per.Gender.FromString “Woman”
Debug.Print per.Gender.ToString()
This example starts where the Custom Collection Class post ends. It is assumed you have the same example ready to use.
As in the first post, we’ll persevere with the VBA Attribute workaround.
Copy the following code to Notepad, save as Gender.cls, then use VBA to Import the file. Did you know that instead of clicking File > Import, you can drag’n'drop the file from Windows Explorer to the Project window?
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “Gender”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Public Enum GenderEnum
Unknown
Female
Male
End Enum
Private Const LoLimit = GenderEnum.Unknown
Private Const HiLimit = GenderEnum.Male
Private gen As GenderEnum
Private Sub Class_Initialize()
gen = GenderEnum.Unknown
End Sub
Public Property Get Value() As GenderEnum
Attribute Item.VB_UserMemId = 0
Value = gen
End Property
Public Property Let Value(val As GenderEnum)
If val >= LoLimit And val < = HiLimit Then
gen = val
Else
Err.Raise Number:=vbObjectError + 513, Description:=“Invalid value for Gender”
End If
End Property
Public Sub FromString(str As String)
Select Case Trim(LCase(str))
Case “f”, “female”, “w”, “woman”: Me.Value = GenderEnum.Female
Case “m”, “male”, “man”: Me.Value = GenderEnum.Male
Case Else: Me.Value = GenderEnum.Unknown
End Select
End Sub
Public Function ToString() As String
Dim str As String
Select Case gen
Case GenderEnum.Female: str = “Female”
Case GenderEnum.Male: str = “Male”
Case Else: str = “Unknown”
End Select
ToString = str
End Function
Notice in the code above the line that reads: Attribute Value.VB_UserMemId = 0
That line is the one thing that makes us perform the Notepad workaround, but it is pretty important that we do it.
The Attribute tells VBA the Value property is the Default property for the Class.
For further reading, take a look at Chip Pearson’s explainer of the feature.
There’s also a couple of Consts defining the valid range of Gender values allowed: LoLimit and HiLimit. As a Gender value is assigned, validation is performed against these Consts through Property Let Value.
Now we can add our Gender property to the Person class.
The Person class should now look as follows:
Public LastName As String
Public City As String
Public Gender As Gender
Private Sub Class_Initialize()
Set Me.Gender = New Gender
End Sub
Great! Now we’re ready to test it.
Dim per As Person
Set per = New Person
‘Test 1: set the gender to a value
per.Gender = Male
Debug.Print per.Gender.ToString()
‘Test 2: Take a string, and store the Gender
per.Gender.FromString “Woman”
Debug.Print per.Gender.ToString()
‘Test 3: supply an invalid value – get an error
On Error Resume Next
per.Gender = 7
Debug.Print Err.Description
End Sub
I’ve put together a workbook containing the code of this post, and also the code of my previous post.
You can download Gender_Class.zip
Great series of posts Rob.
Here is an alternative to the LoLimit and HiLimit consts.
[_First]
Unknown
Female
Male
[_Last]
End Enum
Public Property Let Value(val As GenderEnum)
If val > GenderEnum.[_First] And val < GenderEnum.[_Last] Then
gen = val
Else
Err.Raise Number:=vbObjectError + 513, Description:=“Invalid value for Gender”
End If
End Property
Can also be useful when you want to loop through all of the member values, assuming of course the values are sequential.
Hey, thanks Andy.
I do like your approach, and I’m pleased you posted it. It’s a much tidier method of range checking.
Rob – Fantastic examples. I’m slowly learning how class modules work, which I’ve always struggled with.
One quick question, is there any significance to the 513 in this line?
Great posts Rob. And I really like Andy’s suggestion as well, makes it really tidy.
Lane: From the Help page on Err.Raise: The range 0512 is reserved for system errors; the range 51365535 is available for user-defined errors.
What about the “[_First]“. How does that work?
@Tony
The underscore hides the member name from object browser and intellisense.
Or did you mean something else?