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.

Public Gender as String

That would work.

We could be a little more rigid with our variable, and define an Enum.

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

    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?

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:

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.

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

7 Comments

  1. Andy Pope says:

    Great series of posts Rob.

    Here is an alternative to the LoLimit and HiLimit consts.

    Public Enum GenderEnum
        [_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.

  2. Rob van Gelder says:

    Hey, thanks Andy.
    I do like your approach, and I’m pleased you posted it. It’s a much tidier method of range checking.

  3. Lane Slabaugh says:

    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?

    Err.Raise Number:=vbObjectError + 513, Description:=“Invalid value for Gender”
  4. Great posts Rob. And I really like Andy’s suggestion as well, makes it really tidy.

  5. Rob van Gelder says:

    Lane: From the Help page on Err.Raise: The range 0–512 is reserved for system errors; the range 513–65535 is available for user-defined errors.

  6. Tony Hammer says:

    What about the “[_First]“. How does that work?

  7. Andy Pope says:

    @Tony
    The underscore hides the member name from object browser and intellisense.
    Or did you mean something else?

Leave a Reply