UtterAccess HomeUtterAccess Wiki

Welcome Guest ( Log In | Register )

Custom Search
Edit Discussion
> Classes    
Classes

Image:NotifWarningWeak.gif This page has been marked as incomplete

Notes:
Additional commentary on Collection Classes would be nice

If this page has been completed please remove the {{INCOMPLETE}} template from the page's source markup.

Contents

Understanding Class Modules & their Usage

For the programmer advancing towards using Class modules, one of the more difficult things to overcome at first is to understand what Class modules are and how they are to be used. We can read in any number of articles how they are easier to maintain and work with than standard module procedures, but that doesn't tell us what we should be using them for.

Many articles have been written about Classes and a multitude of examples have been given to portray their uses. Classes have been likened to books, animals, various office supplies, or just about any other tangible or even non-tangible entity.

Plainly speaking, a Class is a template used to represent an object. Let's take a look at instantiation...

Instancing

If a Class is a template used to represent an object, let us work with an example of a book. The Class module itself defines how we want to represent this book, and after the class module itself is complete we create "instances" of this book for actual use in our code. Consider that a book has a certain amount of properties, such as an Author, an ISBN number, a Publisher, a Number of Pages, even a dimensional footprint conveying it's length width and height (because we want to know if it will fit in our bookshelf, right?).

If we write a Class module to represent a book, we may give this class module a means to hold information such as these book properties described above. In the class module we define that this representation will store information regarding an Author, an ISBN number, the number of pages, etc..

If the Class module itself defines these peices of data, we then draw off this "template" to create a representation of the actual object - say the Handbook of Chemistry and Physics, written by Charles D Hodgman, M.S.. After the template is made (the Class module), in our "regular" code we will create an instance of it. This instance of the Class module that defines a book will hold the actual information regarding the specific book that we want to work with. Here's a very brief example:

CODE

' create an new instance of the clsBook class
Dim myBook As New clsBook

' give this instance some meaningful data
myBook.Title = "Handbook of Chemistry and Physics"
myBook.Author = "Charles D Hodgman, M.S."

(note that the module name that you save the class module as is the name that you must refer to when creating an instance of the object)

Above you can imply that we have created a Class module that is the template for our data regarding this book, and you can see that we've created a new instance of this class which holds information about a specific book. Some of the real power when using Classes is that we are generally not limited to having only a single instance of the class - we can create many instances of the same book class, applying different book-specific information to each instance of the class:

CODE

Dim myBoringBook As New clsBook
Dim myFunBook As New clsBook

myBoringBook.Title = "Handbook of Chemistry and Physics"
myFunBook.Title = "Stuff my Kids Ruined"

Both books work off the same Class module - the same template - but each instance of the book is a seperate entity. While they share the same format regarding the books properties, each instance of our two books are not tied together in any way.

The process of creating a new, specific object representation from a Class module is called Instancing.


Defining a Class Module

In order to make a Class module, we need to consider that there are two things that are going to be happening in our code: stuff that happens internally, which only this class module will use, and stuff that the class exposes - properties and procedures that the user interacts with, such as setting the Title or Author property of a book. Generally speaking, we can go forward under the assumption that if something is declared as Private, the user will not be able to directly interact with it after they make an instance of the class, whereas if we declare it as Public, it will be exposed - allowed to be used by the programmer of that instance.

Defining Class Properties

Let us first work out how to define Properties in a class module. Following our Book example, let's make two properties that can be exposed - a Title property and an Author property.

In order to retain the information in the class throughout the lifetime of the instance, and also in order to maintain validation and authorization control on properties, they are a two-part set up: a private (hidden) variable that holds the actual data, and Public methods that allow the user to access the those variables. The Public methods are what we call the Property, and we use these to control the information going in, and coming out of the variables.

Let's first set up the private variables that will actually hold the values:

CODE

Option Compare Database
Option Explicit

Private m_Title As String
Private m_Author As String

(note that it is customary to include "m_" at the beginning of private variables in a class module).

Now let's work on the Properties themselves - the accessors to these two private variables. These properties essentially come in two styles - a way to Set the property, and a way to Get the property. Here's the basic syntax:

CODE

Public Property Get Title() As String
 Title = m_Title
End Property
Public Property Let Title(s As String)
 m_Title = s
End Property

Here we have created the properties themselves, and exposed them through use of the Public keyword. The "Get" property returns the value of m_Title, and the "Let" property takes the value passed to it and set's the m_Title variable. With these properties in place, the user can then create an instance of the class and set the Title property for that instance:

CODE

Dim myBook As New clsBook

myBook.Title = "Some Title"
Debug.Print myBook.Title

Readonly Properties & Validation of Data

This format of creating properties is very powerful as it allows us a fine amount of control over how the data can be set. Let's consider that we want to validate information going into the property, for instance if we were to create a property for the number of pages:

CODE

Public Property Get NumOfPages() As Integer
 NumOfPages = m_NumOfPages
End Property

Public Property Let NumOfPages(i As Integer)
 'validate this data coming in...
 If i > 5000 Then
   MsgBox "We don't believe this book is over 5000 pages." & vbCrLf & _
     "Please enter a realistic value!"
 Else
   m_NumOfPages = i
 End If
End Property

How about making a read-only property? They can certainly be handy as well, and it's very simple: just make the Property Let procedure Private instead of Public, or omit it altogether. The user will still be able to see and read the property from outside the class, but at that point the only way for the variable to be changed is internally by the class. Consider that we'll provide a property to let the user know what page they are on. We'll have it public for them to get the information, but we don't want them to be able to change it (later we will provide a means for them to "turn the page", which will in turn update this value):

CODE

Public Property Get CurrentPage() As Integer
 CurrentPage = m_CurrentPage
End Property

Private Property Let CurrentPage(i As Integer)
 m_CurrentPage = i
End Property

Later, when we write a method to allow the user to change pages, we will call the private Let procedure. Note that this Let procedure can be eliminated completely and we can have the other part of our class update the variable directly, but in some cases having a single point to do this (the Let procedure) provides an easy way to make sure all data being ported to the variable is validated in a way that we may require.

Let vs Set - Basic Datatypes vs Object Types

The last notable mention concerning properties is Let vs. Set. Similar to the difference in assignment in normal coding (where an object variable is required to have the Set keyword), so is the same with Property procedures. To apply a basic datatype value to a variable, we use a Let procedure, and to apply an object to a property we use the Set procedure:

CODE

Public Property Let NumOfPages(i As Integer)
 m_NumOfPages = i
End Property

Public Property Set SomeObject(o As Object)
 Set m_SomeObject = o
End Property

Methods: Class Procedures

Similar to Properties, class Methods (the subs and functions that make the class do stuff) can be hidden (private) or exposed (public). The syntax for writing these is identical to when we write them in standard modules. Subs do something without returning a value, and Functions do something as well as return a value. Let's consider two Possible Methods to add to a Book class - a way to turn to the next page, and a way to turn to a specified page:

CODE

Public Sub TurnToNextPage()
  m_PageContent = "this page doesn't have much text on it..."
  m_CurrentPage = m_CurrentPage + 1
End Sub

Public Sub TurnToPage(PageNumber As Integer)
 If PageNumber > m_NumOfPages Then
   ' display some error
 Else
   m_PageContent = "this page doesn't have much content either..."
   m_CurrentPage = PageNumber
 End If
End Sub

Initializing & Terminating

The last thing to mention is two special subs for a class: the Initialize procedure and the Terminate procedure. The Initialize procedure runs when the class instance is created, and the Terminate procedure runs when the class instance's lifetime ends. You can use these two procedures to initialize default data for the class, or to do general cleanup when the user is done with it.

Putting the Basics Together

With a basic understanding of how a Class module works - readonly properties, methods, etc - let's take a look at a complete yet simple example of a class module:

CODE

Option Compare Database
Option Explicit

' some variables to hold data for properties
Private m_NumOfPages As Integer
Private m_IsLastPage As Boolean
Private m_IsFirstPage As Boolean
Private m_CurrentPage As Integer
Private m_PageContent As String
private m_Title As String
private m_Author As String

' Properties to access the variables

'read/write
Public Property Get NumOfPages() As Integer
 NumOfPages = m_NumOfPages
End Property
Public Property Let NumOfPages(i As Integer)
 m_NumOfPages = i
End Property

'readonly
Public Property Get IsLastPage() As Boolean
 IsLastPage = m_IsLastPage
End Property

'readonly
Public Property Get IsFirstPage() As Boolean
 IsFirstPage = m_IsFirstPage
End Property

'readonly
Public Property Get CurrentPage() As Integer
 CurrentPage = m_CurrentPage
End Property

'readonly
Public Property Get PageContent() As String
 PageContent = m_PageContent
End Property

'read/write
Public Property Get Title() As String
 Title = m_Title
End Property
Public Property Let Title(s As String)
 m_Title = s
End Property

'read/write
Public Property Get Author() As String
 Author = m_Author
End Property
Public Property Let Author(s As String)
 m_Author = s
End Property


' a private sub to set the page
Private Sub setCurrentPage(PageNum As Integer)
 If (PageNum > NumOfPages) Or (PageNum < 1) Then
   MsgBox "You can't go to that page!"
 Else
   m_CurrentPage = PageNum
   m_PageContent = DLookup("PageContent", "tblBooks", _
                           "Title = " & m_Title & " And PageNum = " & PageNum)
   m_IsLastPage = (PageNum < m_NumOfPages)
   m_IsFirstPage = (PageNum = 1)
 End If
End Sub

' some public procedures to allow the user to navigate pages
Public Sub OpenBook()
 If m_NumOfPages = 0 Then
   MsgBox "Tell us the number of pages first..."
 Else
   setCurrentPage (1)
 End If
End Sub

Public Sub TurnToNextPage()
 setCurrentPage (m_CurrentPage + 1)
End Sub

Public Sub TurnToPage(PageNumber As Integer)
 setCurrentPage (PageNumber)
End Sub

Above is the definition of our simple Class that defines some properties and methods of a book. Below is a screenshot which gives a view of how the user will see it - a familiar look that we are used to when working with standard VBA programming.

Image:ClassExampleSS.jpg

Congratulations - you now know how to write a Class.


Events

VBA is event driven, meaning that it can be programmed to do certain things only when a certain event occurs. Each defined event has a handler that acts only when the event happens.

What is an event?

As with most database programming and design the work you do is analogous to something in the real world. In real life, we deal with events constantly. Driving a vehicle, for example, is one analogy for the event driven programming model. When you are driving, you observe traffic signals. When you learned to drive, you learned how to handle the three traffic light colors, red, yellow, and green. So, when you are driving, and you see a red traffic light, hopefully your driver training programmed your "OnRed" handler to initiate stopping the vehicle. Similarily, when the light turns green, your "OnGreen" handler should tell you to put the vehicle in motion by moving your foot from the brake to the accelerator.

Media:Demo_EventsAndCollections.zip demonstrates the use of custom events

Pre-defined events

Using its pre-defined events, Access allows you to make an application responsive many conditions that occur regularly whenever the application is in use. For example forms have a number events, including open, load, before upate and after update. By adding either a macro or vba code you can control what happens when any of these events 'fire.' Typical uses include postioning the cursor to a specific control when a form opens and the validation of data before new data is inserted into a form's data source. You can see a list of an object's pre-defined events by opening the object in design mode and looking at the Event tab of the Property Sheet.

Custom events

Custom events allow you to bring event-driven functionality to your applications that is difficult or impossible to implement in any other way.


Collections

Collections are specialized objects used for managing and manipulating similar objects that you wish to deal with as a group (collection). For example Access Forms have a Controls collection to which every control on the form belongs.

Custom collections can be a useful alternative to using the tag property to designate synonomous controls.

See Media:Demo_EventsAndCollections.zip for an illustration using a custom collection to manage required data entry fields.

External Classes for Library Databases

When writing classes for a Library Database, there's a VB setting for the class that must be set properly for the class to be exposed to other VBA projects. The following can be used from the Immediate Window to set the Instancing property of the class for external consumption:

CODE
vbe.ActiveVBProject.VBComponents("classname").Properties("Instancing")=5

Once this is done, the module can then be exported and imported without losing the property setting, but if the module text is copy/pasted, it will lose the setting.

Other Examples & Links

Adding a Class Module

VBE_Menu,Class Module

More Examples

The following gives another example of a Class. Additionally, the wiki Class Library contains a collection of classes that you can analyize and experiment with.

CODE

Option Explicit
Option Compare Database
Private Const mk_CurModuleName = "CLS_ARRAY_01"
Private mv_Array() As Variant
'--------------------------------------------------------------------
Private Sub Class_Initialize()
  ' Class_Initialize
End Sub
'--------------------------------------------------------------------
Private Sub Class_Terminate()
   ' CLEANUP
  Erase mv_Array
End Sub
'--------------------------------------------------------------------
Public Function GetElementValue(ElementIndex As Long) As Variant
On Error GoTo LBL_xPAC_ERR

Dim lc_ProcName As String, lc_MSG_ERR As String

lc_ProcName = mk_CurModuleName & ".GetElementValue"
GetElementValue = mv_Array(ElementIndex)

LBL_xPAC_END:
  Exit Function

LBL_xPAC_ERR:
lc_MSG_ERR = "Err:" & Err & "," & Err.Description
If (Err = 9) Then
  lc_MSG_ERR = lc_MSG_ERR & vbCrLf & "Array-ElementIndex/Subscript= " & ElementIndex
  If GetElementsCount > 0 Then
      lc_MSG_ERR = lc_MSG_ERR & vbCrLf & _
      "Current Valid range for Array-ElementIndex is: " & vbCrLf & _
      "From:" & GetElementIndexFirst & " To:" & GetElementIndexLast
  Else
      lc_MSG_ERR = lc_MSG_ERR & vbCrLf & "Array is **EMPTY** !" & vbCrLf & "-> '.AddNewElement'"
  End If
End If
  MsgBox lc_MSG_ERR, vbCritical, lc_ProcName
  Resume LBL_xPAC_END
  Resume Next
  Resume
End Function
'--------------------------------------------------------------------
Public Sub AddNewElement(ElementValue As Variant)
On Error GoTo LBL_xPAC_ERR

Dim lc_ProcName As String, ln_LB As Long, ln_UB As Long

lc_ProcName = mk_CurModuleName & ".AddNewElement"
  ln_LB = GetElementIndexFirst
  ln_UB = GetElementIndexLast
  ln_LB = IIf((ln_LB < 0), 0, ln_LB)
  ln_UB = IIf((ln_UB < 0), 0, (ln_UB + 1))
  ReDim Preserve mv_Array(ln_LB To ln_UB)
  mv_Array(ln_UB) = ElementValue

LBL_xPAC_END:
Exit Sub

LBL_xPAC_ERR:
  MsgBox "Err:" & Err & "," & Err.Description, vbCritical, lc_ProcName
  Resume LBL_xPAC_END
  Resume Next
  Resume
End Sub
'--------------------------------------------------------------------
Public Function GetElementIndexFirst() As Long
On Error GoTo LBL_xPAC_ERR

  GetElementIndexFirst = LBound(mv_Array)

LBL_xPAC_END:
  Exit Function

LBL_xPAC_ERR:
  GetElementIndexFirst = -1
  Resume LBL_xPAC_END
  Resume Next
  Resume
End Function
'--------------------------------------------------------------------
Public Function GetElementIndexLast() As Long
On Error GoTo LBL_xPAC_ERR
  GetElementIndexLast = UBound(mv_Array)

LBL_xPAC_END:
  Exit Function

LBL_xPAC_ERR:
  GetElementIndexLast = -2
  Resume LBL_xPAC_END
  Resume Next
  Resume
End Function
'--------------------------------------------------------------------
Public Function GetElementsCount() As Long
On Error GoTo LBL_xPAC_ERR

Dim ln_LB As Long, ln_UB As Long

ln_LB = GetElementIndexFirst
ln_UB = GetElementIndexLast

If (ln_LB >= 0) And (ln_UB >= 0) Then
  GetElementsCount = ln_UB - ln_LB + 1
End If

LBL_xPAC_END:
  Exit Function

LBL_xPAC_ERR:
  Resume LBL_xPAC_END
  Resume Next
  Resume
End Function
'--------------------------------------------------------------------
Edit Discussion
Custom Search
Thank you for your support!
This page has been accessed 17,498 times.  This page was last modified 16:00, 30 May 2014 by Glenn Lloyd. Contributions by Schizolocal, Jack Leach, BruceM and Ace  Disclaimers