Class Modules – Multi-Data Type Arrays

In this post we will be demonstrating how using class modules can make your arrays more functional and easier to read!

In the first post we explained how class modules change the way you think about programming. You are now focusing on objects rather than actions. When dealing with arrays that require multiple data types, this idea will become even more apparent and will increase your understanding of class modules further.

Note: This post assumes you have a good understanding of arrays. If you would like to learn more about arrays, please read these posts:
Working With Arrays 1
Working With Arrays 2

Arrays in MS Access allow us to create lists of data in memory which we are then able to manipulate through code. They are similar in design to tables but don’t take a physical form. One of the main issues with arrays is that we often need to store data for multiple data types but primitive arrays only allow you to store one data type. So, what do we do?

We use strings!

Let’s take a look…

An Array Of Strings

In the provided database we want to create an array based off of data in the Films table. The films table holds data such as ID (long), FilmName (text) and RottonTomatoRating (double) but we are only going to use one array.

Create a standard module called modArrayOfStrings and add this code to it:

Public Sub subArrayOfStrings1()
On Error GoTo ErrorHandler
Dim myArray() As String
Dim i As Integer
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim iRND As Integer

Set db = CurrentDb
Set rs = db.OpenRecordset("Films")

With rs

    'ensure recordset is populated
    If Not .BOF And Not .EOF Then

        'populate recordcount
        .MoveLast
        .MoveFirst

        'set size of array to the number of records in Films
        ReDim myArray(0 To .RecordCount - 1, 0 To 4)

        For i = LBound(myArray) To UBound(myArray)
            myArray(i, 0) = .Fields("ID")
            myArray(i, 1) = .Fields("FilmName")
            myArray(i, 2) = .Fields("YearOfRelease")
            myArray(i, 3) = .Fields("RottenTomatoes")
            myArray(i, 4) = .Fields("DirectorId")

            .MoveNext
        Next i
    End If
End With

'code to generate a random number to test the array
iRND = Int((UBound(myArray) - LBound(myArray) + 1) * Rnd)

Debug.Print "ID: " & myArray(iRND, 0)
Debug.Print "FilmName: " & myArray(iRND, 1)
Debug.Print "YearOfRelease: " & myArray(iRND, 2)
Debug.Print "RottonTomatoes: " & myArray(iRND, 3)
Debug.Print "DirectorID: " & myArray(iRND, 4)
Debug.Print ""

ExitSub:
    Set rs = Nothing
    Set db = Nothing
    Exit Sub
ErrorHandler:
    Resume ExitSub
End Sub

If you call it multiple times from the immediate window by using this code,…

call subArrayOfStrings1

…you will get output similar to this…

ID: 7
FilmName: Jaws
YearOfRelease: 1975
RottonTomatoes: 0.98
DirectorID: 4

ID: 6
FilmName: 12 Years A Slave
YearOfRelease: 2014
RottonTomatoes: 0.96
DirectorID: 5

ID: 8
FilmName: The Dark Knight
YearOfRelease: 2008
RottonTomatoes: 0.94
DirectorID: 3

We are opening up the Films table, creating an array of strings and adding the relevant data to the relevant element.

The Explanation

Public Sub subArrayOfStrings1

The name of the sub-procedure is subArrayOfStrings1.

On Error GoTo ErrorHandler

Choosing to handler errors.

Dim myArray() As String
 Dim i As Integer
 Dim rs As DAO.Recordset
 Dim db As DAO.Database
 Dim iRND As Integer

We are declaring variables that we will need in order for the sub-procedure to function.

Set db = CurrentDb
 Set rs = db.OpenRecordset("Films")

We instantiate the db and rs variables. Open up table Films as a recordset.

With rs
 'ensure recordset is populated
 If Not .BOF And Not .EOF Then
 'populate recordcount
 .MoveLast
 .MoveFirst
 ...
 End With

Standard code to ensure the recordset and recordcount properties are populated.

'set size of array to the number of records in Films
 ReDim myArray(0 To .RecordCount - 1, 0 To 4)

Earlier in the module, we declared an array and called it myArray. We didn’t, however, say how big we wanted it. Arrays have to be a fixed size so in the code snippet above we are letting Access know that we want an array of elements that is equal to the number of records in the Films table.

For i = LBound(myArray) To UBound(myArray)
 myArray(i, 0) = .Fields("ID")
 myArray(i, 1) = .Fields("FilmName")
 myArray(i, 2) = .Fields("YearOfRelease")
 myArray(i, 3) = .Fields("RottenTomatoes")
 myArray(i, 4) = .Fields("DirectorId")
.MoveNext
 Next i

In this code snippet, we are looping through the Films recordset, adding the data from the records to myArray as we go. LBound and UBound refer to the lower and upper bounds of the array respectively.

'code to generate a random number to test the array
 iRND = Int((UBound(myArray) - LBound(myArray) + 1) * Rnd)
 Debug.Print "ID: " & myArray(iRND, 0)
 Debug.Print "FilmName: " & myArray(iRND, 1)
 Debug.Print "YearOfRelease: " & myArray(iRND, 2)
 Debug.Print "RottonTomatoes: " & myArray(iRND, 3)
 Debug.Print "DirectorID: " & myArray(iRND, 4)
 Debug.Print ""

In the above code snippet, we use a function to generate a random number for testing purposes only. iRND will be equal to a number between the lower and upper bounds of the array. We then proceed to print information about the array to the immediate window. This works fine but it isn’t obvious which field of data myArray(iRND, 1) (for example) refers to. It is the FilmName field, as it happens.

So, we have seen that you can use an array of strings to load and print out information about records in a table to the immediate window. But, is there a better way?

Of course!

An Array Of Objects

In the previous example we used an array of primitive data types to work with our data. It worked fine but we can in fact use an array of objects that we create ourselves. Let’s take a look.

Create a Class Module called clsFilmArray and add this code to it:

Private m_ID As Long
Private m_FilmName As String
Private m_YearOfRelease As String
Private m_RottenTomatoes As Double
Private m_DirectorID As Long

''''''''''''''''ID''''''''''''''''''''''

Public Property Get ID() As Long
ID = m_ID
End Property

Public Property Let ID(value As Long)
If Not IsNull(value) Then
    m_ID = value
End If
End Property

''''''''''''''''FilmName''''''''''''''''''''
Public Property Get FilmName() As String
FilmName = m_FilmName
End Property

Public Property Let FilmName(value As String)
If Not IsNull(value) Then
    m_FilmName = value
End If
End Property

''''''''''''''''YearOfRelease'''''''''''''''
Public Property Get YearOfRelease() As String
YearOfRelease = m_YearOfRelease
End Property

Public Property Let YearOfRelease(value As String)
If Not IsNull(value) Then
    m_YearOfRelease = value
End If
End Property

''''''''''''''''RottenTomatoRating''''''''''
Public Property Get RottenTomatoes() As Double
RottenTomatoes = m_RottenTomatoes
End Property

Public Property Let RottenTomatoes(value As Double)
If Not IsNull(value) Then
    m_RottenTomatoes = value
End If
End Property

''''''''''''''''DirectorID''''''''''''''''''
Public Property Get DirectorID() As Long
DirectorID = m_DirectorID
End Property

Public Property Let DirectorID(value As Long)
If Not IsNull(value) Then
    m_DirectorID = value
End If
End Property

This code is very simply a list of private variables (instance variables) and get and let methods to access them. There are no procedures or functions doing any calculating; this is (almost) as simple as a class module can get. But how do we test it? Create a standard module called modArrayClass and add this code:

Public Sub subArrayOfStrings1()
On Error GoTo ErrorHandler
Dim myArray() As clsFilmArray
Dim i As Integer
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim iRND As Integer

Set db = CurrentDb
Set rs = db.OpenRecordset("Films")

With rs

    'ensure recordset is populated
    If Not .BOF And Not .EOF Then

        'populate recordcount
        .MoveLast
        .MoveFirst

        'set size of array to the number of records in Films
        ReDim myArray(0 To .RecordCount - 1)

        For i = LBound(myArray) To UBound(myArray)
            'instantiate the array
            Set myArray(i) = New clsFilmArray

            myArray(i).ID = .Fields("ID")
            myArray(i).FilmName = .Fields("FilmName")
            myArray(i).YearOfRelease = .Fields("YearOfRelease")
            myArray(i).RottenTomatoes = .Fields("RottenTomatoes")
            myArray(i).DirectorID = .Fields("DirectorId")

            .MoveNext
        Next i
    End If
End With

'code to generate a random number to test the array
iRND = Int((UBound(myArray) - LBound(myArray) + 1) * Rnd)

Debug.Print "ID: " & myArray(iRND).ID
Debug.Print "FilmName: " & myArray(iRND).FilmName
Debug.Print "YearOfRelease: " & myArray(iRND).YearOfRelease
Debug.Print "RottonTomatoes: " & myArray(iRND).RottenTomatoes
Debug.Print "DirectorID: " & myArray(iRND).DirectorID
Debug.Print ""

ExitSub:
    Set rs = Nothing
    Set db = Nothing
    Exit Sub
ErrorHandler:
    Resume ExitSub
End Sub

This code doesn’t differ wildly from the code in the first module we created. But trust us, it does differ!

Calling subArrayOfStrings1 from the immediate window gives us identical functionality:

ID: 8
FilmName: The Dark Knight
YearOfRelease: 2008
RottonTomatoes: 0.94
DirectorID: 3

ID: 4
FilmName: The Shining
YearOfRelease: 1980
RottonTomatoes: 0.92
DirectorID: 2

ID: 4
FilmName: The Shining
YearOfRelease: 1980
RottonTomatoes: 0.92
DirectorID: 2

ID: 9
FilmName: 2001 A Space Odyssey
YearOfRelease: 1968
RottonTomatoes: 0.97
DirectorID: 2

So, how does it work?

The Explanation

First let’s take a look at the class module.

Private m_FilmName As String

''''''''''''''''FilmName''''''''''''''''''''
Public Property Get FilmName() As String
FilmName = m_FilmName
End Property

Public Property Let FilmName(value As String)
If Not IsNull(value) Then
    m_FilmName = value
End If
End Property

As stated, we are only using the class module to provide some instance variables to work with. We actually do not need to go as far as we did. The code below would server the same purpose on its own:

Public m_ID As Long
Public m_FilmName As String
Public m_YearOfRelease As String
Public m_RottenTomatoes As Double
Public m_DirectorID As Long

But we like to keep our instance variables private and have access to them through properties (much more secure that way).

Public Property Get FilmName() As String
FilmName = m_FilmName
End Property

These 3 lines of code let us retrieve the value of m_FilmName. Simple, right?

Public Property Let FilmName(value As String)
If Not IsNull(value) Then
    m_FilmName = value
End If
End Property

These 4 lines of code let us modify the value of m_FilmName. Simple again, right?

That is all there is to this class module. A simple module that let’s us update and retrieve values of instance variables. What is really interesting is how the standard module interacts with it.

Public Sub subArrayOfStrings1()
...
Dim myArray() As clsFilmArray
...

With rs
  ...
        'set size of array to the number of records in Films
        ReDim myArray(0 To .RecordCount - 1)

        For i = LBound(myArray) To UBound(myArray)
            'instantiate the array
            Set myArray(i) = New clsFilmArray

            myArray(i).ID = .Fields("ID")
            myArray(i).FilmName = .Fields("FilmName")
            myArray(i).YearOfRelease = .Fields("YearOfRelease")
            myArray(i).RottenTomatoes = .Fields("RottenTomatoes")
            myArray(i).DirectorID = .Fields("DirectorId")

            .MoveNext
        Next i
   ...
End With
...

Debug.Print "ID: " & myArray(iRND).ID
Debug.Print "FilmName: " & myArray(iRND).FilmName
Debug.Print "YearOfRelease: " & myArray(iRND).YearOfRelease
Debug.Print "RottonTomatoes: " & myArray(iRND).RottenTomatoes
Debug.Print "DirectorID: " & myArray(iRND).DirectorID
Debug.Print ""

...
End Sub

A lot of the code above is very similar to the example for the array of strings. We will highlight and explain the differences caused by using a class module.

Dim myArray() As clsFilmArray

Rather than declare the array as a primitive data type (String), we instead prefer to declare it as an array of objects (clsFilmArray) that we created.

ReDim myArray(0 To .RecordCount - 1)

An array of objects can be redimensioned (redeclared) in exactly the same way as an array of strings.

For i = LBound(myArray) To UBound(myArray)
...
Set myArray(i) = New clsFilmArray
...
Next i

Although we have declared myArray to be an array of objects, we still need to instantiate (bring to life) these objects. Each element of the array requires a separate instantiation and we are achieving this with a for loop.

myArray(i).ID = .Fields("ID")

Now we can really see the advantage of using an array of objects. This code writes the value of the field ID to the ID property of the class and it is very clear as to what field we are writing to.

Debug.Print "ID: " & myArray(iRND).ID

Along the same lines, when reading values from the array, we refer to the exact field we need, rather than a number, to get the value we want.

Conclusion

Using an array of objects in place of an array of strings means your code will be more readable and easier to debug. Arrays of objects are also an excellent way to get familiar with the concept of object-based programming.

Leave a Reply

Your email address will not be published. Required fields are marked *

Visit Us On TwitterVisit Us On FacebookVisit Us On Youtube