Learn Microsoft Access Advanced Programming Techniques, Tips and Tricks.

Showing posts with label Object Reference. Show all posts
Showing posts with label Object Reference. Show all posts

Running Sum in MS-Access Query

Introduction.

We are familiar with creating Auto-Numbers in Query Column, through an earlier Function created with the Name QrySeq(), published on this Website with the Post Title: auto-numbering Query Column, during January 2010. Hope you come across that Post, if not you may visit the Page by following the above link.

The RunningSum() Function is written somewhat on similar logic, with a few parameters matching the QrySeq() Function.

Before going into details let us take a look at some sample images, before and after a run of the new Function in a test Query Column.

A small Table with two Fields: Table_Units and with few records.

The SELECT Query: RunningSumQ1 record set in datasheet view, with summary values in a separate column, with the column name RunningSum, from where the RunningSum() Function is called from.

The SQL Code of RunningSumQ1 Query.
SELECT Table_Units.ID, Table_Units.Units, RunningSum([ID],"ID","Units","RunningSumQ1") AS RunningSum
FROM Table_Units;

A Report Designed using RunningSumQ1:

The Query Preparation Note.

Before diving deep into the VBA Code I want you to check the above sample data, to draw your attention to an important point while preparing the data for the RunningSum() Function.

  1. A unique ID Field, like Primary Key, is required in the Query, with either Numeric or String Data, and strictly no duplicates in them.
  2. If this is not readily available in the Source-Data, you may join (concatenate) two or more field values together, to create unique values in a separate column, as a Key Field in the Query.
  3. If this method is followed, then create a Test Query similar to the sample one given below, using the first Query as the source, to find out whether any duplicates still exist in the Source Query or not.
  4. Sample ID Field Record-Count Test Query:

    SELECT RunningSumQ2.ID2, Count(RunningSumQ2.ID2) AS CountOfID2
    FROM RunningSumQ2
    GROUP BY RunningSumQ2.ID2;
    
    

    The CountOfID2 Column result should, like the sample Image given below, with all Count values showing as one.

  5. When all the ID Field values are unique then the CountOfID2 Column will have the value 1 in all records.  Greater than one in any record means that those records have duplicate key values and need to join some other field to eliminate duplicates.
  6. Once you are sure that all records have unique ID values then you may add other required fields in the first Query for the purpose you plan to use, like Form or Report Source Query.
  7. Once you are ready with the Query data, then it is time to add the function in a new Column in the Query, like Summary: RunningSum([ID], ”ID”, ”Units”, ”MyQuery”).

The RunningSum() Function VBA Code.

Option Compare Database
Option Explicit

Dim D As Object

Public Function RunningSum(ByVal IKey As Variant, ByVal KeyFldName As String, ByVal SumFldName As String, ByVal QryName As String) As Double
'-----------------------------------------------------------
 'Function: RunningSum()
 'Purpose : Creates Running-Sum Value of a Field.
 'The Query can be used as Source for other Processing needs.
 '-----------------------------------------------------------
 'Author  : a.p.r. pillai
 'Date    : November 2019
 'Rights  : All Rights Reserved by www.msaccesstips.com
 '-----------------------------------------------------------
 'Parameter List, in the Order of it's placement
 '1. Key Value Data Field
 '2. Key-Field Name in String Format
 '3. Field-Name for Calculating Running Sum in String Format
 '4  Query-Name in String Format
 '-----------------------------------------------------------
 'Remarks: The Key-Value Field should have Unique Numeric or
 'String Values.
 '-----------------------------------------------------------
Static K As Long, X As Double, fld As String, y as Long
Dim p As Variant

On Error GoTo RunningSum_Err

y = DCount(“*”,QryName)

If SumFldName <> fld OR K > y Then
    fld = SumFldName
    Set D = Nothing
    K = 0
    X=0
 End If

K = K + 1
 If K = 1 Then
     Dim DB As Database, rst As Recordset

    Set D = CreateObject("Scripting.Dictionary")
 
    Set DB = CurrentDb
    Set rst = DB.OpenRecordset(QryName, dbOpenDynaset)
     
    While Not rst.EOF And Not rst.BOF
          X = X + rst.Fields(SumFldName).Value
          p = rst.Fields(KeyFldName).Value
          
          D.Add p, X
          
          rst.MoveNext
     Wend
     
     rst.Close
     Set rst = Nothing
     Set DB = Nothing
     
     RunningSum = D(IKey)
 Else
    RunningSum = D(IKey)
 End If

If K = y then
   K = K + 1
End If

RunningSum_Exit:
Exit Function

RunningSum_Err:
MsgBox Err & ":" & Err.Description, vbOKOnly, "RunningSum()"
Resume RunningSum_Exit
End Function

VBA Code Line by Line.

On the Global area of the Standard Module, an Object Variable is declared with the name D. 

The function RunningSum() is declared with four parameters.

  1. The Unique Key Field Value.
  2. The Key-Field Name in String format.
  3. The Summary Field Name in String format.
  4. The Query-Name in String format.

The returned value from a function is a Double precision number.

Four Static Variables are declared:

  1. K – is a control variable.
  2. X – to hold the Summary Values, added to it at a record level.
  3. fld – is the control variable to keep the Summary Field Name as a flag to ensure that the function runs for the same Query.

The Static Variables will retain their values during repeated calls of the Function.

Variable p is to hold the IDKey value retrieved from the record.  It is declared as a Variant Type to accept either Numeric or String Key Values.

The Working Logic of the Function.

The statement If SumFldName <> fld Then checks whether the Key-Field name passed to the function is different from the last call of the Function.  If it is different then it assumes that a different Query is passed to the function.

The Dictionary Object D is erased from memory and other variables are initialized.

In the next step, the K Variable is incremented by one. When K=1 the function’s main task is initiated.  

The Database and Recordset Objects are declared.

The D Object variable is instantiated as a new Dictionary Object, with the Object creation statement: Set D = CreateObject(“Scripting.Dictionary”).

By default, the Dictionary Object Reference is not added to the list of Microsoft Access Library Files. If you add it manually then you can declare and instantiate a Dictionary Object, like the Class Object of Access or Collection Object.

Note: If you are not familiar with Dictionary, Class Object, or Collection Object, then we have all the information you need to learn the fundamentals about them, on this Web site.  The links are given at the end of this page. You may visit them to learn with sample code and Demo databases, as working models to download.

Adding Dictionary Object Reference File.

To add the Dictionary Object to your Database’s Library Files List does the following:

On the VBA Window, select Tools - - >References… and look for the file: Microsoft Scripting Runtime in the displayed list, and put the check mark to select it.

Once you do this you can declare and instantiate a Dictionary Object as given below.

Dim D As Dictionary
Set D = New Dictionary

If you do this you have an added advantage of displaying its Properties and Methods, when you type a dot (D.) after its Object name, by IntelliSense.

Next, the database object DB is set to the active database and the Query is opened at a record set in the rst object.

Within the  While. . .Wend Loop the summary field and the unique key Field values are read from each record. The Summary field value is added to Variable X.  The Key value of the record is written as Key-Value of Dictionary Object and the current Value in X is written as Dictionary Object Item, as Key, Item pair.

The Dictionary Object Items are always written in this way.  The Item can be a single value, an Array object, or a collection of objects. All of them should have a Unique Key Value to retrieve the Item values later.

The purpose of the Key in Dictionary Object is similar to the function of the Primary Key in a Table.  We can retrieve any value Randomly or Sequentially from the Dictionary Object using the Key,  like A = D(Key) or  A = D.Item(Key).

In this way, the cumulative summary value, at each record level, is added to the Dictionary Object as its Item, with a unique Key. When all the record level processing is complete, the first record summary field value is returned to the function calling record by executing the RunningSum = D(IKey) statement, from the first Dictionary Item.  All the above actions are taking place when the control-variable K=1.  

Subsequent calls of the function with the Key-Value parameter of each record, retrieve the corresponding summary value of that record from Dictionary Item and return it to the Query Column, that’s how it works.

Some Images of a sample Run done on the Products Table of NorthWind are given below.

Sample Query Run (Key Values are String Type) Data on Form.

SELECT Trim(Str([ID])) & [Product Code] AS ID2, Products.[Product Code], Products.[Product Name], Products.[List Price], RunningSum([ID2],"ID2","[List Price]","RunningSumQ2") AS RunningSum
FROM Products;

The RunningSumQ2 Query is the Record Source of the Form.

The Sample Run Data of Report.

The RunningSumQ2 Query is the Record Source of the Report.

Download the Demo Database.


CLASS MODULE

  1. MS-Access Class Module and VBA
  2. MS-Access VBA Class Object and Arrays
  3. MS-Access Base Class and Derived Objects
  4. VBA-Base Class and Derived Object-2
  5. Base Class and Derived Object Variants
  6. MS-Access Recordset and Class Module
  7. Access Class Module and Wrapper Classes
  8. Wrapper Class Functionality


COLLECTION OBJECT

  1. MS-Access and Collection Object Basics
  2. MS-Access Class Module and Collection Objects
  3. Table Records in Collection Object


DICTIONARY OBJECT

  1. Dictionary Objects Basics
  2. Dictionary Object Basics-2
  3. Sorting Dictionary Object Keys and Items
  4. Display Records from Dictionary
  5. Add Class Objects as Dictionary Items
  6. Update Class Object Dictionary Item


MS-ACCESS EVENT HANDLING

  1. Withevents MS-Access Class Module
  2. Withevents and Defining Your Own Events
  3. Withevents Combo List Textbox Tab
  4. Access Form Control Arrays And Event
  5. Access Form Control Arrays And Event-2
  6. Access Form Control Arrays And Event-3
  7. Withevents in Class Module for Sub-Form
  8. Withevents in Class Module and Data
  9. Withevents and Access Report Event Sink
  10. Withevents and Report Line Hiding
  11. Withevents and Report-line Highlighting
  12. Withevents Texbox and Command Button
  13. Withevents Textbox Command Button
  14. Withevents and All Form Control Types


Share:

Add Class Objects as Dictionary Items

Introduction.

We have learned the fundamentals of the Dictionary Object, experimented with sorting simple items, and displayed Access table records on an MS Access form using the Dictionary Object.

Now, let us move a step further and learn how to add MS Access Class Objects to a Dictionary, retrieve each object item, and display their Property values and Method outputs in the Debug Window.

If you would like to revisit the earlier related articles for reference, their links are provided below.

We need to have the ClsArea class object in the database to try out this example using the Dictionary object.

If it is not already present, you can create it by following these steps:

  1. Open the VBA Editor.

  2. Insert a new Class Module.

  3. In the Properties Window, change the (Name) property of the new class to ClsArea.

  4. Copy and paste the following VBA code into the ClsArea class module and save it.

Option Compare Database
Option Explicit

Private p_Desc As String
Private p_Length As Double
Private p_Width As Double

Public Property Get strDesc() As String
  strDesc = p_Desc 'copy the value from p_Desc
End Property

Public Property Let strDesc(ByVal strNewValue As String)
  p_Desc = strNewValue
End Property

Public Property Get dblLength() As Variant
  dblLength = p_Length
End Property

Public Property Let dblLength(ByVal dblNewValue As Variant)
    Do While Val(Nz(dblNewValue, 0)) <= 0
      dblNewValue = InputBox("Negative/0 Values Invalid:", "dblLength()", 0)
    Loop
  p_Length = dblNewValue
End Property

Public Property Get dblWidth() As Variant
  dblWidth = p_Width
End Property

Public Property Let dblWidth(ByVal dblNewValue As Variant)
    Do While Val(Nz(dblNewValue, 0)) <= 0
      dblNewValue = InputBox("Negative/0 Values Invalid:", "dblwidth()", 0)
    Loop
  p_Width = dblNewValue
End Property

Public Function Area() As Double
  If (Me.dblLength > 0) And (Me.dblWidth > 0) Then
     Area = Me.dblLength * Me.dblWidth
  Else
     Area = 0
     MsgBox "Error: Length/Width Value(s) Invalid., Program aborted."
  End If
End Function

Private Sub Class_Initialize()
    p_Length = 0
    p_Width = 0
    'MsgBox "Initialize.", vbInformation, "Class_Initialize()"
End Sub

Private Sub Class_Terminate()
   'MsgBox "Terminate.", vbInformation, "Class_Terminate()"
End Sub

The ClsArea Class object has three properties — strDesc, dblLength, and dblWidth — and one method: Area().

We will create multiple instances of this class to represent different rectangular shapes or rooms, and calculate their areas using the Area() method, and add each class object instance as an Item in a Dictionary object.

The value stored in the class object's strDesc property will be used as the Key for each corresponding item in the dictionary.

Since dictionary keys must be unique, make sure that the strDesc property values are not duplicated.

For example, if you have several bedrooms to calculate areas for, you can name them Bedroom1, Bedroom2, Bedroom3, and so on.

The ClassObjInDictionary() Procedure Code.

Let us try the Dictionary with the Class Module Object ClsArea as Items. The sample VBA Code for the Dictionary Object is given below.  Copy and paste it into a Standard Module and save the Module.

Public Sub ClassObjInDictionary()
'--------------------------------------------------
'Add Class Object as Items to Dictionary Object
'Retrieve the Class Object from Dictionary Object
'and Print the values in the Debug Window.
'--------------------------------------------------
Dim C As ClsArea
Dim D As Object, Desc As String, mKey

Set D = CreateObject("Scripting.Dictionary")
D.CompareMode = 1
Desc = ""

Do While Not Desc = "Q"
'instantiate Class Object
Set C = New ClsArea
    
    'Get input Values for ClsArea Object\
    Do While Len(Desc) = 0
      Desc = InputBox("Description or Q=Quit:")
    Loop
       If Desc = "Q" Then Exit Do
       
    C.strDesc = Desc
    C.dblLength = CDbl(InputBox("Length of " & UCase(Desc) & ": "))
    C.dblWidth = CDbl(InputBox("Width of " & UCase(Desc) & ": "))
    
'add to Dictionary
'Description is added as Key of Dictionary Object
    D.Add Desc, C
    Desc = ""
'Clear Class Object
    Set C = Nothing
Loop


If D.Count = 0 Then
  MsgBox "No Data in Dictionary Object!" & vbCr & "Program Aborted."
  Exit Sub
End If

'Output Section
Debug.Print "Key Value", "Description", "Length", "Width", "Area"

For Each mKey In D.keys
        Set C = D(mKey)
        Debug.Print mKey, C.strDesc, C.dblLength, C.dblWidth, C.Area
Next

End Sub

The VBA Code Line by Line.

At the beginning of the code, we create and instantiate the Dictionary object D.

We use the Desc string variable to capture the description text that will be assigned to the strDesc property of the class object. This same variable also acts as the control for the Do While ... Loop.

The loop continues to run until the user enters the single character Q (for Quit) into the Desc variable.

This approach allows you to enter any number of class object instances into the dictionary. When you are done, simply enter Q in the description prompt to exit the loop.

Next, we create an instance of the ClsArea class object using the object variable C.

Inside this loop, the statement Desc = InputBox() is placed within a second Do While ... loop. This ensures that the user actually enters a value into the Desc variable.

If the user presses Enter, clicks OK, or Cancel without entering any text, the InputBox() function will repeat to prompt until a valid value is entered.

The valid Description Value is assigned to the C.strDesc Property of the Class Object.

Through the next two InputBox() functions, collect the Length and width values of the Room from the user and assign them to C.dblLength and C.dblWidth Properties, respectively. 

Now, the ClsArea Class Object is ready to be added to the Dictionary Object.

The statement D.Add Desc, C adds the current instance of the ClsArea Class Object in the Dictionary Object as its first Item to Desc (or C.strDesc Property Value) as the Key of Dictionary Item.

Next, we clear the ClsArea Class Object instance C from memory.

You might have noticed by now that the Class Object instance C is created at the beginning of the outer Do While ... Loop, fills up the Class Object Property Values, adds it to the Dictionary Object, and the Class Object instance C is Set to Nothing as the last statement within the Loop.  That means we are creating a New Class Object instance for each Item in the Dictionary Object.

Why it has to be this way, creating new instances of the Class Object for each Item, is an important point to keep in mind.

When we add the Class Object instance as an Item to the Dictionary Object, internally, only the Class Object’s Location Address is saved in the Dictionary Object as a Pointer.  The actual Class Object Property values are not moved to the Dictionary Object Item. 

When we execute the statement Set C = Nothing, the Class Object instance C is cleared, but the instance’s location reference (pointer) is saved in the Dictionary Object Item.  The actual ClsArea Class Object remains in that location, and we can retrieve it using the Object Pointer saved in the Dictionary Object Item.

When a new Class Object Instance is created, it is done in a new location in memory, and its reference is added to the Dictionary Object.

Enter some Description for a few bedrooms, Length, and Width Values to test the Code. Enter the letter Q to complete the Data entry when you are ready to take a dump of the data in the Debug Window.

A sample Listing is given below:

Key Value     Description   Length        Width         Area
Bed Room1     Bed Room1      14            15            210 
Bed Room2     Bed Room2      12            12            144 
Living Room   Living Room    23            24            552 
Kitchen       Kitchen        11            11            121 
Store Room    Store Room     21            14            294 

In the printing code segment, we did not create a new instance of the ClsArea object (C) to read the class object pointers stored in the dictionary items. Instead, we directly accessed the stored object references from the dictionary and printed their values in the Debug Window.

Note: If you feel more comfortable doing so, you may create an ClsArea object instance (C) and assign the stored dictionary item reference to it. Both approaches work equally well.

The statement

Set C = D(mKey)

reads the object reference (pointer) of the ClsArea object from the dictionary item into C. Once assigned, you can retrieve its property values and method output and print them to the Debug Window.

If you have already run the sample code and understood how it works, try a small modification:

  • Move the object creation and object cleanup (removal) statements for the ClsArea object outside the Do While...Loop.

  • Then, rerun the code, add a few items to the dictionary, and print their property values to verify the output in the Debug Window.

Take a Trial Run With the Following Changes in the Code

The Do While ... loop segment with suggested changes is given below for you to make changes in your code.  Check the highlighted statements above and below the Do While ... Loop.

Desc = ""
Set C = New ClsArea

Do While Not Desc = "Q"
'instantiate Class Object
    
    'Get input Values for ClsArea Object
    Do While Len(Desc) = 0
      Desc = InputBox("Description or Q=Quit:")
    Loop
       If Desc = "Q" Then Exit Do
       
    C.strDesc = Desc
    C.dblLength = CDbl(InputBox("Length of " & UCase(Desc) & ": "))
    C.dblWidth = CDbl(InputBox("Width of " & UCase(Desc) & ": "))
    
'add to Dictionary
'Description is added as Key of Dictionary Object
    D.Add Desc, C
    Desc = ""
'Clear Class Object
Loop
    Set C = Nothing

I moved the statement Set C = New ClsArea to a position above the Do While...Loop, and placed the Set C = Nothing statement below the Loop so that it executes only after completing the data entry of Class Objects into the Dictionary within the Do While...Loop.

I entered all five sample items listed earlier, using the same names but with different values for Length and Width.

Finally, the printing section listed all five items in the Debug Window.
However, instead of showing the individual values entered for each item, the values of the last item were printed for all five entries.

Key Value     Description   Length        Width         Area
Bed Room1     Store Room     12            13            156 
Bed Room2     Store Room     12            13            156 
Living Room   Store Room     12            13            156 
Kitchen       Store Room     12            13            156 
Store Room    Store Room     12            13            156 


Why has it happened this way?

When we add a Class Object with its Properties to a Dictionary Object, only the reference (memory address) of the Class Object is stored in the Dictionary Item—not its actual Property values.

When an instance of the Class Object is created using the New keyword, that instance is assigned a fixed memory location (address). Any new values entered into its Properties will overwrite the previous values stored in that same instance. Each time this same object reference is added to the Dictionary, the Dictionary stores only the address of the Class Object, not a copy of its current Property values.

By contrast, if we create a new instance of the Class Object during each loop cycle, a fresh object is created, with a different memory address. The Dictionary stores these unique addresses, allowing each Item to retain its own distinct Property values.

When the statement Set C = Nothing is executed, it simply clears the reference from the object variable C so it no longer points to any location. However, the actual object data remains alive in memory because its reference is still held by the Dictionary.

But when we moved the Set C = New ClsArea and Set C = Nothing statements outside the Do While...Loop, we ended up using only a single instance of the Class Object to input multiple sets of values. Each new set of Property values overwrote the previous ones in that same object. As a result, all the Dictionary Items ended up pointing to this single object instance, which holds only the last set of values entered.

Therefore, during printing, even though the Keys appear correctly in the listing, all the Items show the same (last entered) Property values.

Next week, we will learn how to add, edit, update, and delete Class Objects in the Dictionary through an MS Access Form.

MS-ACCESS CLASS MODULE

  1. MS-Access Class Module and VBA
  2. MS-Access VBA Class Object Arrays
  3. MS-Access Base Class and Derived Objects
  4. VBA Base Class and Derived Objects-2
  5. Base Class and Derived Object Variants
  6. Ms-Access Recordset and Class Module
  7. Access Class Module and Wrapper Classes

    COLLECTION OBJECT

  8. Ms-Access and Collection Object Basics
  9. Ms-Access Class Module and Collection Object
  10. Table Records in Collection Object and Form

    DICTIONARY OBJECT

  11. Dictionary Object Basics
  12. Dictionary Object Basics-2
  13. Sorting Dictionary Object Keys and Items
  14. Display Records from Dictionary to Form
  15. Add Class Objects as Dictionary Items
  16. Update Class Object Dictionary Item on Form

Share:

VBA Base Class and Derived Object-2

Introduction.

Last week, we created a derived Class, 'lsVolume', using ClsArea as the base class. In that approach, we added property procedures in the derived class to expose the base class’s properties and functions to the user programs. This method, however, requires repeating all the base class property procedures in the derived class.

In this section, we will explore how to create the same derived ClsVolume class without duplicating the property procedures of the ClsArea base class.

So far, we have learned about Get and Let property procedures in classes. There is also a third type: the Set Property Procedure, which is used to directly assign an object to a class object of the same type.

Before proceeding, you may want to revisit the earlier pages on this topic if you haven’t already. Links are provided below:


ClsVolume Class, the Makeover.

We shall create a different variant of the same ClsVolume Class Module we created last week, using ClsArea as Base Class, with a different approach, and with less Code. 

Create a new Class Module and change its Name Property Value to ClsVolume2.

Copy and Paste the following Code into the Class Module ClsVolume2 and Save the Module:

Option Compare Database
Option Explicit
'Method two-1
Private p_Height As Double
Private p_Area As ClsArea

Public Property Get dblHeight() As Double
    dblHeight = p_Height
End Property

Public Property Let dblHeight(ByVal dblNewValue As Double)
    p_Height = dblNewValue
End Property

Public Function Volume() As Double
    Volume = p_Area.dblLength * p_Area.dblWidth * p_Height
End Function

The new Get and Set Property Procedure for the ClsArea Object.

Public Property Get CArea() As ClsArea
   Set CArea = p_Area
End Property

Public Property Set CArea(ByRef AreaValue As ClsArea)
  Set p_Area = AreaValue
End Property

From the Debug menu, select Compile [Project Name] to compile all the VBA code in your database and ensure it is error-free. If any errors are found in other VBA programs, locate and correct them, then recompile the project.

While unresolved errors will not prevent you from assigning or retrieving values from object properties, the VBA IntelliSense—which displays a list of an object’s properties and functions—will not work properly until the project compiles successfully.

Seeing the property list appear in IntelliSense is an invaluable aid during coding, especially while learning and experimenting with class objects.


Get / Set instead of the Get / Let Property Procedure.

In this version of the ClsVolume class, we have omitted all the property procedures  ClsArea that were present in the previous version. Instead, we use Get and Set property procedures rather than the traditional Get/Let pair.

Take a look at the declaration: the private member p_Area is declared as a ClsArea class object. Normally, when an object is declared this way, we would create an instance of it in the Class_Initialize() procedure.

However, in this approach, we have not instantiated it within the class. The plan is to create and populate the ClsArea object in the user program, set its properties with appropriate values, and then pass it to the ClsVolume2 class. The class will then use these values during the final calculation phase.

Take note of the Set CArea() procedure. Its ByRef parameter, AreaValue, is declared as a ClsArea object. When an ClsArea instance is passed to this property procedure, the object variable AreaValue receives it and assigns it to the private p_Area property of the ClsVolume2 object.

This mechanism allows the ClsVolume2 class to use an externally created and populated ClsArea object without having to instantiate it internally, maintaining flexibility and reusability.

The Get CArea() Property procedure returns the Object to the calling program.

In our earlier programs, we wrote property procedures for individual elements of an object, such as Length, Width, and Height—to assign or retrieve values. In this version, the difference is that we are passing an entire object as a parameter to a Set property procedure.

To access a property of this passed object—for example, dblLength—we use the syntax CArea.dblLength. Here, the Get/Set property procedure name CArea essentially becomes a child object of the main object when declared in the main program. Its individual properties can then be accessed directly using the object address, such as:

Vol.CArea.dblLength

This approach allows the main object to interact with the entire child object and its properties as a single unit, simplifying property management and enhancing reusability.

A Test Program in Standard Module.

We will now create a small test program in a Standard Module to verify the functionality of our newly derived class object. ClsVolume2.

  1. Insert a new Standard Module into your project.

  2. Copy and paste the following code into the module.

  3. Save the module before running the program.

Public Sub SetNewVol2_1()
'Method 1/2
Dim Vol As New ClsVolume2

'ClsArea Object instantiated and passed to the
'Property Procedure Set CArea in ClsVolume2.

Set Vol.CArea = New ClsArea 'declare and instantiate the object in one statement

Stop

Vol.CArea.strDesc = "Bed Room"
Vol.CArea.dblLength = 90
Vol.CArea.dblWidth = 10

Vol.dblHeight = 10 'assign height to ClsVolume2

Stop

Debug.Print "Description", "Length", "Width", "Area", "Height", "Volume"
Debug.Print Vol.CArea.strDesc, Vol.CArea.dblLength, Vol.CArea.dblWidth, Vol.CArea.Area, Vol.dblHeight, Vol.Volume

Set Vol.CArea = Nothing
Set Vol = Nothing

End Sub

Code Review Line by Line.

Let’s quickly review the VBA code above.

  1. The first line instantiates the ClsVolume2 class with the object name Vol.

  2. After the comment lines, the Set Vol.CArea statement calls the property procedure and passes the newly instantiated ClsArea object as its parameter.

I included a Stop statement on the next line to pause the program so you can observe how the object is assigned to the CArea property. We’ll explore how to inspect this in memory shortly.

  1. The following four lines assign values to the ClsArea object properties (strDesc, dblLength, dblWidth) and to the dblHeight property of the ClsVolume2 object.

  2. The next Stop statement pauses the program again, allowing you to inspect how these values are stored in memory.

  3. The subsequent line prints the headings in the Debug Window for clarity.

  4. Finally, the last line prints the values of the object properties retrieved from memory, displaying them in the Debug Window.

Run the Code to the Next Stop Statement

  1. Let’s run the code and inspect the memory to see what happens at each stage where the Stop statements are placed.

    1. Click anywhere inside the code and press F5 to run the program. The execution will pause at the first Stop statement.

    2. From the View menu, select Locals Window. This opens a window below the code editor that displays the ClsArea and ClsVolume2 objects, along with their properties and member procedures, as they are stored in memory.

    3. Observe the structure of the objects and how the property values are held. A sample image of the Locals Window is shown below for reference.

    The Locals Window View.


    To give more space for the Locals Window, drag the sizing handles of other windows upward to reduce their height. Alternatively, you can close the Debug Window temporarily and press Ctrl+G to bring it back when needed.

    The Locals Window provides a graphical view of all objects and their properties in memory:

    1. The first item with a plus [+] symbol shows the name of the Standard Module from which the program is running.

    2. The next plus [+] symbol represents the Vol object, which is the instantiated ClsVolume2 object in memory.

    Click the plus [+] symbols to expand each item and display detailed information about the object’s properties and member procedures.

    You will find the next level of Objects and Properties.

    The [+]CArea indicates that this Object has the next level of Properties and their Values.

    The dblHeight Get property Procedure comes directly under the Vol Object.

    The [+]p_Area is the Private Property declared ClsArea Class in the ClsVolume2 Class.

    The p_Height is also the Private Property declared in the ClsVolume2.

    Click on the plus [+] symbols to expand the objects to show their Properties and Values.

    Expanding the [+] CArea node displays the ClsArea object that was passed to the Set CArea() property procedure.

    The expansion of [+]p_Area gives the view of the ClsArea Property declared as Private.

    Note that the p_Area Private Property, of the ClsVolume2 Class Object, and all its elements are accessible only through the CArea Object Property Get/Set Procedures to the outside world.

    The second column in the Locals Window displays the values assigned to the object’s properties. At this stage, no values have been assigned, so the fields are currently empty.

    The Third Column shows the Data Type or Object Class Module Names.

    Press F5 to run the program further, till it is paused at the next Stop statement, to assign some values to the Object Properties.  The program will pause at the next Stop statement.  Check the Locals Window for changes in Values.

    Inside the CArea Object, the first two lines with values 90, 10, and the last strDesc Variable with value "Bedroom" are assigned through the Get Property Procedures, respectively. The p_Desc, p_Length, and p_width are values assigned through Set Property Procedures to the p_Area Property of the ClsVolume2 Class Object as well.

    The p_Area Object of ClsArea Class declared as Private Property of ClsVolume2 is seen with its Get/Set Property Procedures and assigned values.

    Check the Type Column of [-]CArea and [-]p_Area; both Objects are derived from the ClsArea Base Class.

    Usage of ClsArea and ClsVolume2 Class Objects Differently.

    Next week, we will explore another approach using the same two objects.

    If you’d like to experiment on your own beforehand, here’s a clue to get you started:

    1. Instantiate ClsVolume2 and ClsArea classes as two different Objects in the Standard Module Program.

    2. Assign values to both Object Properties.

    3. Assign the ClsArea instantiated Object to the CArea Object in the ClsVolume2 Class Object, before printing the Values to the Debug Window.

    In this example, we can achieve the same result as in the previous example without having to repeat the Get/Let property procedures in the ClsVolume class module.

    The Links of All the Pages in this Topic.

    1. MS-Access Class Module and VBA
    2. MS-Access VBA Class Object Arrays
    3. MS-Access Base Class and Derived Objects
    4. VBA Base Class and Derived Objects-2
    5. Base Class and Derived Object Variants
    6. Ms-Access Recordset and Class Module
    7. Access Class Module and Wrapper Classes
    8. Wrapper Class Functionality Transformation
    9. Ms-Access and Collection Object Basics
    10. Ms-Access Class Module and Collection Object
    11. Table Records in Collection Object and Form
    12. Dictionary Object Basics
    13. Dictionary Object Basics-2
    14. Sorting Dictionary Object Keys and Items
    15. Display Records from Dictionary to Form
    16. Add Class Objects as Dictionary Items
    17. Add Class Objects as Dictionary Items
    18. Update Class Object Dictionary Item on Form

Share:

MS-Access Base Class and Derived Objects

Introduction.

If you have not seen the earlier Posts on the Microsoft Access Class Module, please go through them before continuing. The links are given below.

  1. MS-Access Class Module and VBA.
  2. MS-Access VBA Class Object Array.

 ClsArea A Class can serve as a base class for other class objects, allowing its calculations to be reused as part of more advanced operations. For instance, it could be integrated into a class designed to calculate the volume of an object, where area is only one step in the overall computation.

The dbl prefix in the dblLength and dblWidth Property procedure names simply indicate that these properties expect double-precision numeric values. Similarly, if we were to rename the property procedures to Quantity and UnitPriceMultiplying one by the other would yield the Total Price of an item.

This demonstrates the flexibility of the ClsArea class. Wherever the result of multiplying two values is required—such as TotalPrice * TaxRate to compute tax, or TotalPrice * DiscountRate to determine a discount—it can be adapted as a base class to fit the scenario.

Although we started with a simple class module, it has the potential to be used as part of many other derived classes. The possibilities are limited only by your imagination and creativity.

Currently, our ClsArea Class calculates the area of materials, rooms, or similar objects using only the Length and Width properties. It does not yet support shapes like triangles or circles. However, it can be extended into a new class object that calculates the volume of rooms or warehouses to determine storage capacity. To achieve this, we would simply introduce an additional property, such as Height, into the design.

The Volume Class: ClsVolume.

Let’s now create a new class module named ClsVolume, using ClsArea as its base class.

  1. Insert a new Class Module.

  2. In the Properties Window, change the Name property to ClsVolume.

  3. Type or copy and paste the following code into the class module:

Option Compare Database
Option Explicit

Private p_Area As ClsArea
Private p_Height As Double

Private Sub Class_Initialize()
    Set p_Area = New ClsArea
End Sub

Private Sub Class_Terminate()
    Set p_Area = Nothing
End Sub

Public Property Get dblHeight() As Double
    dblHeight = p_Height
End Property 

Public Property Let dblHeight(ByVal dblNewValue As Double)
   Do While Val(Nz(dblNewValue, 0)) <= 0
      dblNewValue = InputBox("Negative/0 Values Invalid:", "dblHeight()", 0)
    Loop
    p_Height = dblNewValue
End Property

Public Function Volume() As Double

If (p_Area.Area() > 0) And (p_Height > 0) Then
    Volume = p_Area.Area * p_Height
Else
    MsgBox "Enter Valid Values for Length,Width and Height.", , "ClsVolume"
End If

End Function

Let’s examine the code line by line. On the third line, we declare a private member  p_Area of type ClsArea — a reference to an ClsArea instance that this class will use internally. The next line declares a private field p_Height As Double to store the height value. Both members use the p_ prefix to indicate private scope; they will be accessed and validated through property procedures rather than directly from outside the class.

The Class_Initialize() and Class_Terminate() Sub-Routines.

The next two subroutines—Class_Initialize() and Class_Terminate()—play a crucial role in the ClsVolume class.

  • Class_Initialize() runs automatically when an ClsVolume object is created in a standard module. Within this routine, we instantiate the ClsArea object in memory, ensuring that all of its functionality is available to the new class.

  • Class_Terminate() is triggered when we explicitly clear the ClsVolume object with a statement such as Set ClsVolume = Nothing. At this point, the subroutine ensures that the memory allocated to the ClsArea object is also released properly.

 Property Get dblHeight The procedure simply returns the current value of the private field p_Height to the calling program.

Property Let dblHeight The procedure validates the value passed into the NewValue parameter before assigning it to the private property p_Height. This safeguard prevents invalid values (such as zero or negative numbers) from being stored.

The Public Function Volume() calculates the volume by calling the p_Area.Area() function. The returned area value is then multiplied by p_Height using the expression:

Volume = p_Area.Area * p_Height

Before executing this calculation, a validation check ensures that p_Area.Area() returns a value greater than zero (which confirms that both p_Area.dblLength and p_Area.dblWidth contain valid values) and that the p_Height Property is also greater than zero. Only when all three properties hold valid values is the volume calculation performed.

Note: Since the p_Area object of the ClsArea Class is defined as a private member of the ClsVolume class, we must expose its properties (strDesc, dblLength, dblWidth) and its Area() function to the outside world. This is done by creating corresponding Get/Let property procedures in the ClsVolume class module, effectively making these members accessible for use while maintaining encapsulation. The Let/Get Property Procedures.

Here’s the refined code you can add to your ClsVolume class module. These procedures expose the strDesc, dblLength, dblWidth, and Area() members of the private p_Area object to the outside world:

Public Property Get strDesc() As String
   strDesc = p_Area.strDesc
End Property

Public Property Let strDesc(ByVal NewValue As String)
   p_Area.strDesc = NewValue
End Property

Public Property Get dblLength() As Double
   dblLength = p_Area.dblLength
End Property

Public Property Let dblLength(ByVal NewValue As Double)
   p_Area.dblLength = NewValue
End Property

Public Property Get dblWidth() As Double
   dblWidth = p_Area.dblWidth
End Property

Public Property Let dblWidth(ByVal NewValue As Double)
   p_Area.dblWidth = NewValue
End Property

Public Function Area() As Double
    Area = p_Area.Area()
End Function

Check the strDesc property procedures (Get/Let) carefully. The choice of the procedure name strDesc is arbitrary—you could use a different name if you prefer. However, since the original property in the ClsArea class is also named strDesc Reusing the same name here helps maintain a clear connection with the base class and makes the relationship between the two classes more intuitive.

In the Get dblLength() property procedure, the expression to the right of the equals sign p_Area.dblLength retrieves the stored length value from the ClsArea object and returns it to the calling program.

The corresponding Let procedure assigns the incoming parameter value (NewValue) to the p_Area.dblLength property of the ClsArea object. Notice that we do not run a separate validation check here—the validation is already enforced within the ClsArea class itself when the value is assigned.

The same logic applies to the dblWidth property. Its Get and Let procedures expose the corresponding property of the p_Area object while delegating validation to the base class.

Finally, the p_Area.Area() function is surfaced through the ClsVolume class, making it directly accessible to the calling program. This ensures that the Area method defined in the base class can be reused seamlessly in the derived class.

The ClsVolume Derived Class Module Code.

The completed code of the ClsVolume Class Module is given below.

Option Compare Database
Option Explicit

Private p_Area As ClsArea
Private p_Height As Double

Private Sub Class_Initialize()

‘Open ClsArea Object in Memory with the name p_Area
    Set p_Area = New ClsArea 

End Sub

Private Sub Class_Terminate()

‘Removes the Object p_Area from Memory
    Set p_Area = Nothing ‘
End Sub

Public Property Get dblHeight() As Double
    dblHeight = p_Height
End Property

Public Property Let dblHeight(ByVal dblNewValue As Double)

Do While Val(Nz(dblNewValue, 0)) <= 0
      dblNewValue = InputBox("Negative/0 Values Invalid:", "dblHeight()", 0)
    Loop
     p_Height = dblNewValue

End Property

Public Function Volume() As Double

If (p_Area.Area() > 0) And (Me.dblHeight > 0) Then
    Volume = p_Area.Area * Me.dblHeight
Else    

MsgBox "Enter Valid Values for Length,Width and Height.",vbExclamation , "ClsVolume"
End If

End Function

‘ClsArea Class Property Procedures and Method are exposed here

Public Property Get strDesc() As String
   strDesc = p_Area.strDesc
End Property 

Public Property Let strDesc(ByVal NewValue As String)
   p_Area.strDesc = NewValue
End Property

Public Property Get dblLength() As Double
   dblLength = p_Area.dblLength
End Property

Public Property Let dblLength(ByVal NewValue As Double)
   p_Area.dblLength = NewValue
End Property

Public Property Get dblWidth() As Double
   dblWidth = p_Area.dblWidth
End Property

Public Property Let dblWidth(ByVal NewValue As Double)
   p_Area.dblWidth = NewValue
End Property

Public Function Area() As Double
    Area = p_Area.Area()
End Function

At this point, you might be thinking: “This feels like double work—wouldn’t it be better if we could somehow skip the repeated property procedures in the ClsVolume class?” Or perhaps, “Why not just add the dblHeight property directly into ClsArea and calculate both Area() and Volume() from there?”

That’s a fair question. But the purpose here is to demonstrate how a base class object can be incorporated into the design of another class object. This pattern shows the power of reusability and encapsulation in VBA class design.

The key benefit of building reusable class modules is that your main programs remain simple, while all the complexity stays hidden inside the class object. This separation makes your code easier to maintain, reuse, and extend.

Of course, there are more compact approaches that could reduce code repetition—we’ll explore those later. For now, let’s continue with our original plan so you can fully understand the step-by-step process.

The Main Program that Uses the ClsVolume Class.

Let us test our new ClsVolume Class in the main Program in the Standard Module.  The sample code is given below.

Public Sub TestVolume()
Dim vol As ClsVolume

Set vol = New ClsVolume

vol.strDesc = "Warehouse"
vol.dblLength = 25
vol.dblWidth = 30
vol.dblHeight = 10

Debug.Print "Description", "Length", "Width", "Height", "Area", "Volume"

With vol
    Debug.Print .strDesc, .dblLength, .dblWidth, .dblHeight, .Area(), .Volume()
End With
End Sub

You can see how simple the main program is, without the printing lines.

Copy and paste the code into a Standard Module. If the Immediate (Debug) Window is not already open, press Ctrl+G to display it. Next, click anywhere inside the code and press F5 to run the procedure.

The sample output displayed in the Debug Window should look similar to the example shown below: Description Length Width Height Area Volume

Warehouse      25            30            10            750           7500 

Description Length Width Height Area Volume
Warehouse 25 30 10 750 7500

Validation Checks Performance Tests.

Next, we will run a few tests to confirm that the validation checks built into the base class (ClsArea) still works when values are passed through the ClsVolume class. Recall that we also added additional validation in the Area() and Volume() functions.

Let’s test them one at a time.

Test 1: Pass a negative value to the ClsArea.dblLength property through the ClsVolume class. This should immediately trigger the error message and invoke the InputBox() function within the Do While…Loop, prompting you to enter a valid (positive) value.

1.  Replace the Value 25 with -5 in the line Vol.dblLength = 25 and press the F5 Key to run the Code.

  The validation check will trigger the error and will ask for a value greater than zero.  Enter a value greater than 0.  After that, restore the value 25.

2.  Disable the line Vol.dblHeight = 10 by inserting a comment symbol (‘) at the beginning of the line as shown: ‘Vol.dblHeight = 10.  After the change, press the F5 Key to run the Code.

If no input values are provided for the properties, the Volume() Function will generate an error, indicating that all three properties—dblLength, dblWidth, and dblHeight—must contain valid values before the calculation can proceed.

In the same way, you can also test the behavior of the Area() function to verify that it responds correctly to missing or invalid inputs.

To make testing easier, we can create a data-printing function that accepts an ClsVolume object as a parameter and prints its property values and calculated results in the Debug Window.

The Changed Code of Main Programs.

The changed Code for both Programs is given below:

Public Sub TestVolume()
Dim Vol As ClsVolume

Set Vol = New ClsVolume

Vol.strDesc = "Warehouse"
Vol.dblLength = 25
Vol.dblWidth = 30
Vol.dblHeight = 10

Call CVolPrint(Vol)

Set Vol = Nothing

End Sub
Public Sub CVolPrint(volm As ClsVolume)

   Debug.Print "Description", "Length", "Width", "Height", "Area", "Volume"  
With volm
    Debug.Print .strDesc, .dblLength, .dblWidth, .dblHeight, .Area, .Volume
End With

End Sub

Next week, we will build the Volume Class Object.

The Links of All Pages on this Topic.

Earlier Post Link References:

  1. MS-Access Class Module and VBA
  2. MS-Access VBA Class Object Arrays
  3. MS-Access Base Class and Derived Objects
  4. VBA Base Class and Derived Objects-2
  5. Base Class and Derived Object Variants
  6. Ms-Access Recordset and Class Module
  7. Access Class Module and Wrapper Classes
  8. Wrapper Class Functionality Transformation
  9. Ms-Access and Collection Object Basics
  10. Ms-Access Class Module and Collection Object
  11. Table Records in Collection Object and Form
  12. Dictionary Object Basics
  13. Dictionary Object Basics-2
  14. Sorting Dictionary Object Keys and Items
  15. Display Records from Dictionary to Form
  16. Add Class Objects as Dictionary Items
  17. Update Class Object Dictionary Item on Form

Share:

PRESENTATION: ACCESS USER GROUPS (EUROPE)

Translate

PageRank

Post Feed


Search

Popular Posts

Blog Archive

Powered by Blogger.

Labels

Forms Functions How Tos MS-Access Security Reports msaccess forms Animations msaccess animation Utilities msaccess controls Access and Internet MS-Access Scurity MS-Access and Internet Class Module External Links Queries Array msaccess reports Accesstips WithEvents msaccess tips Downloads Objects Menus and Toolbars Collection Object MsaccessLinks Process Controls Art Work Property msaccess How Tos Combo Boxes Dictionary Object ListView Control Query VBA msaccessQuery Calculation Event Graph Charts ImageList Control List Boxes TreeView Control Command Buttons Controls Data Emails and Alerts Form Custom Functions Custom Wizards DOS Commands Data Type Key Object Reference ms-access functions msaccess functions msaccess graphs msaccess reporttricks Command Button Report msaccess menus msaccessprocess security advanced Access Security Add Auto-Number Field Type Form Instances ImageList Item Macros Menus Nodes RaiseEvent Recordset Top Values Variables Wrapper Classes msaccess email progressmeter Access2007 Copy Excel Export Expression Fields Join Methods Microsoft Numbering System Records Security Split SubForm Table Tables Time Difference Utility WScript Workgroup database function msaccess wizards tutorial Access Emails and Alerts Access Fields Access How Tos Access Mail Merge Access2003 Accounting Year Action Animation Attachment Binary Numbers Bookmarks Budgeting ChDir Color Palette Common Controls Conditional Formatting Data Filtering Database Records Defining Pages Desktop Shortcuts Diagram Disk Dynamic Lookup Error Handler External Filter Formatting Groups Hexadecimal Numbers Import Labels List Logo Macro Mail Merge Main Form Memo Message Box Monitoring Octal Numbers Operating System Paste Primary-Key Product Rank Reading Remove Rich Text Sequence SetFocus Summary Tab-Page Union Query User Users Water-Mark Word automatically commands hyperlinks iSeries Date iif ms-access msaccess msaccess alerts pdf files reference restore switch text toolbar updating upload vba code