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.
1 |
Public Gender as String |
That would work.
We could be a little more rigid with our variable, and define an Enum.
1 2 3 4 5 6 7 |
Public Enum GenderEnum 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
1 2 3 |
per.Gender = Male 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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
VERSION 1.0 CLASS 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:
1 2 3 4 5 6 7 |
Public FirstName As String 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Sub test() 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.
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?