|
|
Classes
[edit] Understanding Class Modules & their UsageFor 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... [edit] InstancingIf 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.
[edit] Defining a Class ModuleIn 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. [edit] Defining Class PropertiesLet 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 [edit] Readonly Properties & Validation of DataThis 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. [edit] Let vs Set - Basic Datatypes vs Object TypesThe 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 [edit] Methods: Class ProceduresSimilar 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 [edit] Initializing & TerminatingThe 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. [edit] Putting the Basics TogetherWith 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. Congratulations - you now know how to write a Class.
[edit] External Classes for Library DatabasesWhen 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. [edit] Other Examples & Links[edit] Adding a Class Module[edit] More ExamplesThe 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 '--------------------------------------------------------------------
|
| This page has been accessed 4,053 times. This page was last modified 16:43, 6 October 2012 by Schizolocal. Contributions by Jack Leach, BruceM and Ace Disclaimers |