Class Foundations

The first in a series of articles on Class solutions.

By John Colby

 

In this first article on Class modules, we’ll examine a fully functional timer that you can use in your applications, but the objective is to present all the pieces required so that you can build and use more complicated classes. The full code can be found on the DatabaseAdvisor’s web site, in a zipped mdb file, with all code and a demo form using the timer class (see http://www.databaseadvisors.com/downloads/ClassFoundations.zip)

The Code

The following is the entire code needed to dimension, use, and destroy a timer class variable. The code is generic—you’ll need to supply names for your own objects.

 

Function TimeSomething()

Dim clsTimeFrmOpen as new clsTimer      ‘DIMENSION THE CLASS VARIABLE

 

    ClsTimeFrmOpen.Init Nothing      ‘INITIALIZE THIS INSTANCE OF THE TIMER

    ClsTimeFrmOpen.StartTimer           ‘START THE TIMER

    Docmd.OpenForm “MyForm”       ‘DO SOMETHING THAT NEEDS TIMING

    Debug.Print ClsTimeFrmOpen.StopTimer      ‘PRINT THE TIME TO THE DEBUG WINDOW

    Set clsTimeFrmOpen = nothing      ‘DESTROY THE TIMER

End function

 

 

Private Const DebugPrint As Boolean = True

Private Const mcstrModuleName As String = "clsTimer"

 

Private mobjParent As Object

Private mobjChildren As Collection

Public strInstanceName As String

 

Private Declare Function apiGetTime Lib "winmm.dll" Alias "TimeGetTime" () As Long

Dim lngStartTime As Long

 

Private Sub Class_Initialize()

    assDebugPrint "initialize " & mcstrModuleName, DebugPrint

    IncObjCounter

    Set mobjChildren = New Collection

End Sub

 

Public Sub Init(ByRef robjParent As Object)

    Set mobjParent = robjParent

    LogIntoParentCol Me

    assDebugPrint "init() " & strInstanceName, DebugPrint

End Sub

 

Private Sub Class_Terminate()

    assDebugPrint "Terminate " & mcstrModuleName, DebugPrint

    DecObjCounter

    TerminateChildren Me.Children

    Set mobjChildren = Nothing

    Term

End Sub

 

Public Sub Term()

    assDebugPrint "Term() " & strInstanceName, DebugPrint

    Set mobjParent = Nothing

End Sub

 

Property Get strModuleName() As String

    strModuleName = mcstrModuleName

End Property

 

Public Property Get Parent() As Object

  Set Parent = mobjParent

End Property

 

Public Property Get Children() As Collection

  Set Children = mobjChildren

End Property

 

Function EndTimer()

    EndTimer = apiGetTime() - lngStartTime

End Function

 

Sub StartTimer()

    lngStartTime = apiGetTime()

End Sub

 

Because I work with classes often, I have created my own framework that I can reuse for every class. Many of the statements above refer to functions that are provided in another module, called basClassGlobal; I import this module into every Access database where I use classes, and it provides these support functions. Most of these functions are mainly for debugging; the purpose of each is explained in the course of the discussion below.

 

We will discuss each line of the class module as it is used in the course of the example. We can begin, however, by mentioning that I define two constants in the class header; these are purely for debugging purposes, and are primarily used by the statements that refer to the support functions in basClassGlobal:

 

Private Const DebugPrint As Boolean = True

Private Const mcstrModuleName As String = "clsTimer"

 

The first of these turns debug printing on or off throughout the module; the second provides the debugging statements with the name of the module.

Class Initialization

To create a new instance of the clsTimer class, dimension it with a line similar to

 

Dim clsTimeFrmOpen as New clsTimer      ‘DIMENSION THE CLASS VARIABLE

 

We are all familiar with dimensioning objects; the New keyword in this code actually causes the class to instantiate, which means to load and initialize. When a class instantiates, its Class_Initialize() event fires. (The Class_Initialize() is similar to a form’s Open event.) The difference between Class_Initialize() and the methods that we will write later is that Class_Initialize is a true event, fired by Access just because the class instantiated. This event has no parameters and is used just to initialize any internal structures needed once the class is loaded and in use. Here is the Class_Initialize() code from our timer class module:

 

Private Sub Class_Initialize()

    assDebugPrint "initialize " & mcstrModuleName, DebugPrint

    IncObjCounter

    Set mobjChildren = New Collection

End Sub

 

The three statements in this sub do the following:

 

·         assDebugPrint prints stuff to the debug window; it is one of the functions provided in the extra module, basClassGlobal.

·         IncObjCounter counts this instance of the timer being opened. We use this for troubleshooting mostly to see that when we finally clean up, every instance of every class has been accounted for; this function is also found in basClassGlobal.

·         The statement Set mobjChildren = New Collection initializes a collection for the storage of child classes that this class may use if needed. One of the keys to successful class usage is ensuring that things get cleaned up after we are done. By using a collection to store all classes that this class instantiates in the course of its operation, we can then simply pass the whole collection to a cleanup routine when it’s time to tear down this class. Since every class has one of these, if the child class uses classes, it also uses a collection, and it will cleanup all of its child classes in exactly the same way.

 

ClsTimer doesn’t require any child classes, but the real intention of this example is to introduce the structures we will be using in more complex classes (which we’ll present later in subsequent articles). This kind of construction is not necessary to the use of classes in programming, but is one that I find useful for managing classes and have built into my own system.

 

Once the class has been initialized, the next thing that happens is that the programmer calls an Init() function, as seen in the second line of our programming example:

 

ClsTimeFrmOpen.Init Nothing      ‘INITIALIZE THIS INSTANCE OF THE TIMER

 

The Class_Initialize function is an event called automatically by Access when the class is initialized; as such it cannot be passed parameters. I have found it useful to create a second function that is called explicitly and that can be passed parameters, and these will be used to initialize variables inside the class. In this case, all we pass in is a reference to the object which is the parent of the class being instantiated (the timer). Because we are using the timer standalone, there is no parent class. If there were, we could pass a pointer to Me as in the following example:

 

ClsTimeFrmOpen.Init Me        ‘INITIALIZE THIS INSTANCE OF THE TIMER

 

Here is the ‘Init’ sub listing from our class module:

Public Sub Init(ByRef robjParent As Object)

    Set mobjParent = robjParent

    LogIntoParentCol Me

    assDebugPrint "init " & strInstanceName, DebugPrint

End Sub

 

The code operates as follows:

 

·         The statement Set mobjParent = robjParent stores a pointer to this class’s parent. We often need to be able to find something that the parent is doing, and this pointer allows us to get at anything that the parent is making public.

·         LogIntoParentCol Me registers itself in its parent’s mobjChildren collection, so that when the parent closes, it can clean up all of its children. This function is found in basClassGlobal.

·         The statement assDebugPrint "init " & strInstanceName, DebugPrint   prints to the debug window.

Class Operation

The operation of this class is trivial as there are only two methods and a single variable. The StartTimer gets a tick count and stores it in a long integer variable lngStartTime. EndTimer Gets the tick count, subtracts lngStartTime from it, and returns the difference. From our example, here is the code that calls the appropriate functions:

 

ClsTimeFrmOpen.StartTimer     ‘START THE TIMER

Docmd.OpenForm “MyForm”       ‘DO SOMETHING THAT NEEDS TIMING

Debug.Print ClsTimeFrmOpen.EndTimer      ‘PRINT THE TIME TO THE DEBUG WINDOW

 

And here are the methods as they appear in the class module:

 

Sub StartTimer()

    lngStartTime = apiGetTime()

End Sub

 

Function EndTimer()

    EndTimer = apiGetTime() - lngStartTime

End Function

Class Termination

Finally, in order to clean up, the programmer sets the timer variable equal to nothing:

 

Set clsTimeFrmOpen = Nothing            ‘DESTROY THE TIMER

 

Setting the variable = Nothing causes the class’s Class_Terminate() event to fire. Similarly to Class_Initialize, this is an automatic, Access-generated event. The Terminate event will directly perform its cleanup, destroying all the child classes stored in its mobjChildren collection, and calling its Term sub. Just as I create my own Init sub that can be called explicitly by the programmer in addition to the Class_Initialize() event, I also create a separate Term sub because there are times when it should be called before the class instance is destroyed and will be called by the programmer before setting the variable pointer to nothing. Even if that is done, Term() should be written in such a way that it can be called again by Terminate() so that we can have a consistent cleanup in case some unforeseen event causes the variable to be cleared, which would destroy the class instance. In other words, the call to Term() in Terminate() is a failsafe cleanup.

 

Private Sub Class_Terminate()

    assDebugPrint "Terminate " & mcstrModuleName, DebugPrint

    DecObjCounter

    TerminateChildren Me.Children

    Set mobjChildren = Nothing

    Term

End Sub

 

After the debugging statement, the code proceeds as follows:

 

·         DecObjCounter decrements the count of open class instances to account for this class closing (another function found in basClassGlobal).

·         TerminateChildren Me.Children passes a reference to our children collection to a function which will clean up any child classes in this collection

·         The statement Set mobjChildren = Nothing cleans up the pointer to our child collection.

 

 

The Term() sub is as follows:

 

Public Sub Term()

    assDebugPrint "Term() " & mcstrModuleName, DebugPrint

    Set mobjParent = Nothing

End Sub

 

Again a debugging statement precedes the real action of this sub. Set mobjParent = Nothing cleans up the pointer to our parent (the class that instantiated this class).

Summary

That in a nutshell is the structure that I use for instantiating, using and tearing down a class. Each piece is required to implement a reliable setup/tear down mechanism, which in turn allows the programmer to concentrate on what the class does, not how to get it to set up and close reliably.

 

Classes are nothing more than a method of modeling some real world object. They embed all the constants, variables and methods required to make some thing work. They can be instantiated as often as needed to implement as many of the objects as needed. With the proper foundation, we can implement new classes to meet our needs with a minimum of fuss and bother, yet be assured that they will function in a consistent manner and when it is time to cleanup they will do so cleanly and reliably.

 

All the code for the clsTimer, basClassGlobal, and a demo of the timer in action can be found on the web site for DatabaseAdvisors.com. In the next issue I will demonstrate how to build a class for a custom control.

 

John Colby ©2001
May be distributed as long as the copyright remains.

John Colby Bio