Microsoft Access VBA Tutorials for self-paced learning, Class Modules, SQL Techniques, and AI Integration Guides.

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

Auto-Numbers in Query Column Version-2

Auto-Numbers in Query Column Version 2.

In January 2010, I published an article, “Function: QrySeq() – Auto-Numbering in Query Column,” on this website. Over the years, it has been well received by readers. While reviewing the article again, I thought the Function could be rewritten with less code and improved performance than a variant Array.

When the QrySeq() function is called for a record in the query, the program searches the Array of Unique Keys for the key value passed from the record as a Parameter. Once it finds a matching key, it retrieves the corresponding sequence number from the Array element and returns it to the calling record.

If the query contains a large number of records, this process can take considerable time because the program begins the search from the start of the Array each time it looks for a key value.

The New Version is Named: QryAutoNum()

Using a Collection Object instead of an Array.

You can find a detailed discussion of the Collection Object on the MS Access and Collection Object Basics Page.

Here, we will have a brief introduction to the Collection Object, including what it is and how it is used in VBA. The Collection Object is a versatile Object that can generally hold any type of value, including numeric or string values, Class Module Objects, or a collection of other Objects. The Collection Object is instantiated in VBA programs as follows:

'declare a Collection Object. Dim ABC as Collection 'create an instance of Collection Object in Memory Set ABC = New Collection 'We can Add built-in data types: Numeric, Strings etc ‘or Objects like Class Module Objects, ‘or other Collection Object as Items to the Collection Object.

'Use the Add method to add a Collection Item to the Object. ABC.Add 25 ABC.Add "ms-accesstips" 'When Collection Object Items added this way, ‘it can be retrieved only in the added order. For j = 1 to ABC.Count 'gets the count of Items Debug.Print ABC(J)’ retrieve in Item index Order. Next 'When ADDed an Item with a String Key 'we can use the Key value to retrieve the Items Randomly. 'But, usage of Key is optional. ABC.Add 25, "1" ABC.Add "ms-Accesstips", "2" x = "2" Debug.Print ABC(x) Result: ms-accesstips

We will use the Collection Object to store the Query Auto-numbers, with the corresponding Unique Key Values assigned as the Collection Object Keys. With this approach, we can retrieve the Auto-numbers directly, eliminating the need to work with Arrays and their more complicated data storage and retrieval steps.

The QryAutoNum() Function Code.

Option Compare Database
Option Explicit

Dim C As Collection

Public Function QryAutoNum(ByVal KeyValue As Variant, ByVal KeyfldName As String, ByVal QryName As String) As Long
'-------------------------------------------------------------------
'Purpose: Create Sequence Numbers in Query Column Ver.-2
'Author : a.p.r. pillai
'Date : Dec. 2019
'All Rights Reserved by www.msaccesstips.com
'-------------------------------------------------------------------
'Parameter values
'-------------------------------------------------------------------
'1 : Column Value - must be UNIQUE Numeric/String Type Values from Query Column
'2 : Column Name  - the Field Name in Quotes from where Unique Values taken
'3 : Query Name   - Name of the Query this Function is Called from
'-------------------------------------------------------------------
'Limitations - Function must be called with Unique Field Values
'            - as First Parameter
'            - Need to Save the Query, if changes made, before opening
'            - in normal View.
'-------------------------------------------------------------------
Static K As Long, Y As Long, fld As String
On Error GoTo QryAutoNum_Err

Y = DCount("*", QryName) ' get count of records for control purpose

'If KeyfldName Param is different from saved name in variable: fld
'or Value in K more than count of records in Variable: Y
'then it assumes that the QryAutoNum() is called from a different Query
'or a repeat run of the same Query. In either case the Control Variable
'and Collection Object needs re-initializing.
If KeyfldName <> fld Or K > Y Then
'initialize Control Variable
'and Collection Object
    K = 0
    Set C = Nothing
    'save incoming KeyfldName
    fld = KeyfldName
End If

'if KeyValue parameter is Numeric Type then convert
'it to string type, Collection Object needs it's Key as String Type.
If IsNumeric(KeyValue) Then
    KeyValue = CStr(KeyValue)
End If

K = K + 1
If K = 1 Then
Dim j As Long, db As Database, rst As Recordset
Dim varKey As Variant

Set C = New Collection

Set db = CurrentDb
Set rst = db.OpenRecordset(QryName, dbOpenDynaset)

'Add recordlevel AutoNumber with Unique KeyValue
'to Collection Object, in AutoNumber, KeyValue Pair
While Not rst.BOF And Not rst.EOF
    j = j + 1 ' increment Auto Number
    
    'Get key value from record
    varKey = rst.Fields(KeyfldName).Value
    
    'if numeric key convert it to string
    If IsNumeric(varKey) Then
      varKey = CStr(varKey)
    End If
    
    'Add AutoNumber, KeyValue pair to Collection Object
    C.Add j, varKey
    
    rst.MoveNext
Wend
    rst.Close
    Set rst = Nothing
    Set db = Nothing

'Retrieve AutoNumber from Collection Object
'using the KeyValue.  Works like Primary Key of Table
    QryAutoNum = C(KeyValue)
Else
    QryAutoNum = C(KeyValue)
End If

If K = Y Then 'All record level AutoNumbers are Returned
    K = K + 1 ' increment control variable
End If

QryAutoNum_Exit:
Exit Function

QryAutoNum_Err:
MsgBox Err & " : " & Err.Description, , "QryAutoNum"
Resume QryAutoNum_Exit

End Function

Sample Source Query SQL.

With the Northwind Products Table.

SELECT Products.ID, 
Products.Category, 
Mid([Product Name],18) AS PName, 
Sum(Products.[Standard Cost]) AS StandardCost, 
QryAutoNum([ID],"ID",
"Product_AutoNumQ") AS QrySeq
FROM Products
GROUP BY Products.ID, Products.Category, Mid([Product Name],18)
ORDER BY Products.Category, Mid([Product Name],18);

Review of VBA Code Line-By-Line.

In the Global Area of the Module, we have declared a Collection Object, the Object Variable C.

The QryAutoNum() Function declaration is the same as our earlier QrySeq() with three parameters.

  1. Unique Key-Value, either Numeric or String, as the first Parameter.
  2. The Key-Value Field’s Name in String Format.
  3. The Query Name in String Format.

The returned AutoNumber is in a Long Integer format.

Three Static Variables,  K and Y, are declared as Long Integers, and fld was declared as a String Variable.

All three Variables control the Code execution Paths, and determine when to initialize Collection objects and control variables.

The DCount() Function takes a count of records in the Query in Variable Y.

If the KeyFldName differs from the previously saved field name in the fld variable, the function assumes that the call originates from a new Query Record. If the field name is the same, but the value of the variable K is greater than Y, the function assumes that the earlier Query is calling QryAutoNum() again as part of a repeated run. In either case, the control variable K is reset to zero, and the Collection Object containing the existing Items is cleared from memory. The new Key field name from the KeyFldName variable is then saved in the fld variable for subsequent validation.

Next, if the KeyValue parameter is numeric, it is converted to String format using the statement KeyValue = CStr(KeyValue). The Item Key of the Collection Object must be in String format.

The variable K is then incremented by one. When K = 1, the function assumes that this is the first call to the function, originating from the first record of the Query. In this case, the main processing of the function begins.

The local temporary Variables are declared here, and their values are not preserved between calls of this function from different records of the query.

The Collection Object declared in the Standard Module’s  Global area is instantiated in memory with the statement Set C = New Collection.

The Query recordset is opened to read records. The local variable J will create Auto-numbers and add them to the Collection Object for each record.  The Unique Key-Value, read from the recordset into variable varKey, is added to the Collection Object as its Key Value.

If the varKey variable value is Numeric Type, then it is converted to String format.

The Auto-Number Value in Variable J and the string value in variable varKey are added to the Collection Object in the following statement, as its Item value, Key pairs:

C.Add J, varKey

This process is repeated for all the records in the Query.  The Auto-Numbers are generated for all records and added to the Collection Object.  All this work is done during the first call of the function from the first record of the query.

Did you notice that we are reading the Unique Key value of each record directly from the record set within the While . . . Wend Loop to add them to the Collection Object?  After adding the Auto-Numbers for all records, the record set and Database Objects are closed.

Remember, we are still on the first call of the function from the first record of the query, and the first parameter variable KeyValue still holds the first record Key Value.

The next statement QryAutoNum = C(KeyValue) retrieves the Collection Object’s first Item Auto-Number Value 1, using the Unique Key Value in parameter variable KeyValue, and returns it to the function calling record. This will happen only once because the variable K will be greater than one on subsequent calls of this function.

Function calls from the second record onward take the Else path of the If K = 1 Then statement. They retrieve the AutoNumber from the Collection Object using the KeyValue passed as a parameter and return it to the corresponding record in the Query.

This process is very fast because the required Item can be retrieved directly using the Collection Object Key, rather than searching through the Array from the beginning each time to locate the required Key.

When the Auto-number values for all records have been returned, the value of the control variable K equals Y. The record count of the Query was obtained and stored in the variable Y at the beginning of the program. At this point, K is incremented by 1, making its value greater than Y. Because K and Y are Static Variables, their values are retained in memory after the last record call has completed. If the same Query is run a second time, these variable values can be tested to determine whether the variables need to be reset and the existing Collection Object cleared from memory, allowing the entire process to start afresh.

If the QryAutoNum() function is called from the same Query again, the Static Variables and Collection Object are cleared from memory, preparing for a fresh run of the Function for the same Query or for a different Query.

The sample Report image, generated using the above Query, is shown below for reference

You can use the Query as the source for a Report or Form. 

A sample demo database is attached. 


  1. Auto-Numbering in Query Column
  2. Product Group Sequence with Auto-Numbers.
  3. Preparing Rank List.
  4. Auto-Number with Date and Sequence Number.
  5. Auto-Number with Date and Sequence Number-2.
Share:

Running Sum in MS-Access Query

Running Sum in an MS Access Query.

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

The RunningSum() Function is written using similar logic and matches a few QrySeq() Function Parameters.

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 a few records.

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

The SQL Code of the 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 data preparation procedure for the RunningSum() Function.

  1. A unique ID Field, like a primary key, is required in the Query, with either Numeric or String Data, and ensure there are no duplicates.
  2. If this is not readily available in the Source-Data, you may join (concatenate) two or more field values (any type) 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 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 value should be 1.

  5. When all the ID Field values are unique, the CountOfID2 Column will have a value of 1 in all records.  A value greater than one in any record means that those records have duplicate keys and need to join some other field to eliminate duplicates.
  6. Once you are sure that all records have unique IDs, you may add other required fields to the first Query for the purpose you plan to use, Form or Report Source.
  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.

In 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 the 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, you can declare and instantiate a Dictionary Object, like the Class Module or Collection Object.

Note: If you are not familiar with the Dictionary, Class, or Collection Object, then we have all the information you need to learn the Fundamentals on this website.  The links are given at the end of this page. You may visit them to learn using 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, do 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 the added advantage of displaying its Properties and Methods when you type a dot (.) 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 a Dictionary Object Key-Value, and the current Value in X is written as a Dictionary Object Item, as a 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 a 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: 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 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 take place while the control-variable K=1.  

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

Some Images of the sample run, done in the NorthWind Products table, 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 the 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 Events
  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:

Call Function From MouseMove Event Property

Calling a Function from the MouseMove Event Property.

This is about running a User-Defined Function (say =myFunction (Parameter)) on the TextBox MouseMove event property.  The difficult part is that when the MouseMove event occurs, the TextBox Name that runs the event must be passed as a parameter to the function.

This question was asked by a member in the Access Users Forum (www.accessforums.net), in the Forms Category of Posts, seeking suggestions for a solution.  A sample demo database was posted there twice on page 5, but the last one is the final version. 

I thought this would be useful to our readers and have presented it here, with details of how this difficult task was solved using an Object-Oriented Programming approach.

Manual Option.

This is easy to set up if it is manually entered =myFunction("Text1")  on each TextBox's Mouse Move Event Property

But the requirement is to pass the TextBox Name as a parameter dynamically to the Function.  It means we should somehow get the TextBox Name from the Mouse Move Event and pass it as a parameter to the function, placed on the same Mouse Move Event Property.

The real situation that demands this method is the following requirement of an Access Application:

The complexity of Requirements.

Assume that you are developing a database for a movie ticket booking Application and need around 350 or more text boxes on the Form, for a graphical seating arrangement design.  Each TextBox represents a single seat in the cinema hall, in several rows & Columns (i.e., each row has several seat positions). Each seat has a unique identity number (that is, the TextBox name), indicating its position in the auditorium; for example: Row-A, Seat No.5 (A5) or B1, etc. The TextBox will display its current status either Booked or Vacant.

The idea is that when the mouse moves over the TextBox (Seat), it should display the Seat Number (A5, or B1, etc.) on a dedicated Label in the Header or Footer Section of the Form, to help the customer look for their choice of Seat Numbers and book the Seat(s).

A simple Form with several TextBoxes and labels for sample TextBox arrangements, and try out this method to solve the problem.

PS: The technical details presented above may have lapses or may form suggestions in the reader's mind.  That is not important; the core point is how we could get the TextBox Name on the Mouse Move Event and pass the name as a string parameter to the =myFunction() Function, placed on the Mouse Move Event Property of the same TextBox.

Why Manual Method unacceptable.

So, writing =myFunction("A5") or =myFunction("B1") etc., in each one of the 350 Text Boxes' Event Property demands a lot of manual work.  Besides that, if any change to the seating arrangement or Seat Numbering scheme becomes necessary, then all the TextBox Properties have to undergo manual changes. 

Another option available is to set the Control Tip Text Property with the TextBox Name. When the mouse pointer rests on the TextBox, a brief delay occurs (not acceptable), then it displays the Seat Number from the Control Tip Text property.  Modifying the Control Tip Text Property is easy and can be done dynamically in the Form_Load() Event Procedure. 

But the database designer insists on passing the TextBox Name as a parameter to the Function.  Besides displaying the TextBox Name on the designated Label Caption, the Function may have other issues in the program to take care of.  

The Difficult Question.

Even though it sounds like a simple issue, the difficult question is: how do we get the TextBox name, say Text1, from the Name property of the running Form when the mouse moves over that TextBox, and pass the name as a parameter to the Calling Program?   Remember, the Mouse Move Event fires repeatedly, at every mouse-point coordinate on the text box (or on any other control it moves)  and this Event has some default parameters: Button, Shift, X, and Y coordinates of the Mouse Pointer on the Control.  But not the Control Name among them.

The Programming Roadblocks.

There are times when we face roadblocks in solving issues, when conventional programming solutions don't work.  But such issues can be easily handled by a few lines of code through Object-Oriented Programming.  This is a classic example, easy to understand, and does the job with a few lines of code.

Access Class Module Objects.

We have already covered earlier the fundamentals of Access Class Modules and Objects-based programming.  If you are not familiar with stand-alone Access Class Modules and Objects, then the Page links at the bottom of this page to start learning the basics.

The Easy Solution.

To solve the above-narrated issue, we have used a few lines of code in the Access Class Module Objects (both Form and stand-alone Class Modules) and used a Collection Object to organize several instances of the Class Module Objects, rather than using Arrays.

The General-purpose TextBox Object Class Module: ClsTxt Code:

Option Compare Database Option Explicit Private WithEvents txt As Access.TextBox Private frm As Access.Form Public Property Get pFrm() As Access.Form Set pFrm = frm End Property Public Property Set pFrm(ByRef vNewValue As Access.Form) Set frm = vNewValue End Property Public Property Get pTxt() As Access.TextBox Set pTxt = txt End Property Public Property Set pTxt(ByRef vNewValue As Access.TextBox) Set txt = vNewValue End Property Private Sub txt_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) '------------------------------------------------ 'The first MouseMove Event, of each TextBox, 'comes into this sub-routine. 'The MouseMove Event Property is set with the Function: '"=RunMouseOver('Textbox_Name','Form_Name')" 'with the TextBox & Form Names as Parameters. 'Subsequent MouseMove Events Calls the Function 'directly from Standard VBA Module1, 'control will not come into this sub-routine, any more. '------------------------------------------------

txt.OnMouseMove = "=RunMouseOver('" & txt.Name & "')" End Sub

Two Class-Module Properties, the Access.TextBox and 'Access.Form' Objects are declared in txt and frm object variables, respectively, with Private scope.  The txt Property is declared with the WithEvents keyword to capture Events originating from TextBoxes on the form. The next twelve lines of code assign and retrieve objects in TextBox and Form Properties with Set/Get  Property Procedures.  This will prevent direct access to the Class Module Properties txt and frm from outside.  Up to this point, it is the TextBox Object's common feature of assigning and retrieving values to and from the Object Variables.  The frm Property is not used here.

The sub-routine part is what we are interested in.  Any number of Text-Box-based Event Procedure subroutines can be written here rather than directly on the Form's Class Module.

The txt_MouseMove() Event.

The Text Box's first Mouse Move Event transfers control into the txt_MouseMove() Subroutine.  There is only one executable statement in the subroutine that overwrites the Text Box's Mouse Move Event Property Value. 

txt.OnMouseMove = "=RunMouseOver('" & txt.Name & "')"

We can get the TextBox name from the txt Property.  The TextBox Mouse Move Event Property (initially set as "[Event Procedure]" in the Form_Load() Event Procedure) is replaced with the Function "=RunMouseOver('" & 'txt.name' & "')", and the TextBox name as a string Parameter.  The subsequent Mouse Move Events will call the RunMouseOver() Function in the Standard Module from the Mouse Move Event Property, and never come back to the above sub-routine txt_MouseMove() anymore. 

So, the first Mouse Move Event on any Text Box will do the trick, and other TextBoxes will wait for their turn for a Mouse Move Event to take place.

The simple RunMouseOver() Function Code will be presented later on this page.

Form3 Class Module Code.

The Form's (Form3) Class Module VBA Code is given below:

Option Compare Database
Option Explicit

'Declare Class ClsTxt as Object F
Private F As ClsTxt
'Declare Collection Object as C
Private C As Collection

Private Sub Form_Load()
Dim ctl As Control

Set C = New Collection 'instantiate Collection Object

For Each ctl In Me.Controls 'scan through the controls
   If TypeName(ctl) = "TextBox" Then ' Take only Text Boxes
        Set F = New ClsTxt 'instantiate ClsTxt Class Object
        
            Set F.pFrm = Me 'Assign Form to pFrm Property
            Set F.pTxt = ctl 'Assign TextBox to pTxt property
            'enable mouse move event
            F.pTxt.OnMouseMove = "[Event Procedure]"
            
        C.Add F 'add ClsTxt Object to Collection
        
        Set F = Nothing 'remove the ClsTxt object instance from memory
    End If
Next

End Sub

Private Sub Form_Unload(Cancel As Integer)
    'Erase the Collection Object when Form3 is closed.
    Set C = Nothing
End Sub

In the global declaration area, Class-Module ClsTxt is declared as Object F and a Collection Object as C.

In the Form_Load() Event Procedure, we scan through Form3 controls and take only TextBox controls.  The Form Object and TextBox controls are assigned to the F.pFrm and F.pTxt Properties of the ClsTxt Object.

The F.pTxt Object's OnMouseMove() Event Procedure is enabled so that, when it happens, the control goes to the txt_MouseMove() sub-routine of the Class Module instance of ClsTxt for the first time. In the next step, the ClsTxt Object instance F is added to the Collection Object, as its Item.  In the next step, the ClsTxt Object instance F is cleared from memory.  A new F object instance is created for the next Text Box.  This is necessary to identify each instance of the TextBox Object with a different internal reference of each TextBox added to the Collection Object as its Item.

This process repeats for all the TextBoxes on Form3.

When the Form is closed, the Form_Unload() Event executes, and the Collection Object is cleared from Memory.

When Form3 is open, these initialization steps are performed, and all the TextBox Controls are enabled with the Mouse Move Event, added to the Collection Object, and stay in memory till Form3 is closed.  Each Text Box's Mouse Move Event is handled by its respective ClsTxt Object instance added to the Collection Object.

The RunMouseOver() Function Call.

When the user moves the mouse over a Text Box (say Textbox name A1) for the first time, the Mouse Move Event executes and calls the txt_MouseMove() Event Procedure in the ClsTxt Object instance for that Text Box, in the Collection Object item.  In this procedure, the TextBox's MouseMove Event Property is modified and set to the =RunMouseOver("A1")  Function, with the Text Box name A1 as a parameter.

From the second MouseMove event onwards, the event calls the RunMouseOver() Function from the Standard Module1.  The VBA Code of this Function is given below.

Option Explicit

Public Function RunMouseOver(strN As String)
    Screen.ActiveForm.Controls("Label0").Caption = strN

End Function

The RunMouseOver() Function receives the textbox name as a parameter.  The statement addresses the Label0 control directly through the Screen Object ActiveForm route and changes the Label's Caption with the Mouse Moved Textbox Name.

The RunMouseOver() Function can be modified to pass the Form's Name as a second parameter and can be used to address the Label0 control as Forms(strForm).Controls("Label0").Caption = strN.  This is avoided to keep the parameter expression simple.

When the Mouse is moved over other TextBoxes, the same procedure is repeated for that TextBox object instance in the Collection Object.

When Form3 is closed, the Collection Object instance C, containing all TextBoxes' ClsTxt Class Object instances, is cleared from memory.

The Function RunMouseOver() assigned to the TextBox's Mouse Move Event Properties is cleared (as they are assigned dynamically), and the Property will remain empty.

Next time Form3 is opened, everything falls into place again and is ready for action.  So everything is controlled by Object-oriented Programming and happens dynamically.  This sample database is uploaded as a solution to the Access User's Forum Page 5, where several alternative options are suggested by other members.  

The Demo Database is attached and may be downloaded; try it out yourself.



Class Module Tutorials.

  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:

Access Form Control Arrays and Event-3

Access Form Control Arrays and Event-3.

This article builds on last week’s topic, focusing on how to capture TextBox AfterUpdate and LostFocus events and validate their Values through a class module array.

In the previous session, we stopped short of discussing how to move all the VBA code from the Form_Load() event procedure into a separate class module, leaving the form module almost free of event procedures. The VBA Code in this new structure will define the TextBox Control Class Module Array and handle the required built-in Events within their respective array elements. This approach will leave only three or four lines of code in the form module, while shifting all the logic into a derived class module object.

Earlier, we had created Derived Class Objects by using a class module as a base class and extending its functionality. We will apply the same concept here.

We are using TextBox controls first—rather than other controls on the form—for these array-based examples because they are the most commonly used controls. A TextBox supports several events, including BeforeUpdate, AfterUpdate, LostFocus, Enter, Exit, KeyDown, KeyUp, and OnKey. Depending on the requirements, we can invoke one or more of these events from within the derived Class Object.

We can define a set of standard event procedures in the TextBox class module to handle commonly used events such as BeforeUpdate, AfterUpdate, Enter, or Exit. However, only the required event handlers need to be activated for each TextBox control. This can be done during the array element initialization by assigning:

obj.txt.EventName = "[Event Procedure]"

This approach enables the selective activation of event procedures for individual TextBox instances.

Since each Form may require different validation rules or processing logic, the code inside these class event procedures often needs customization. An effective way to manage this is to create a TextBox Class Module Template and incorporate the most frequently used event procedures. For a new form, simply copy this template and modify it to suit the specific requirements of the TextBox controls on that form.

Other control types on a form—such as Command Buttons, Combo Boxes, and List Boxes—generally rely on fewer events, most commonly Click or DblClick. We will examine how to manage these other control types in arrays later.

Eventually, we will also explore whether there are more effective approaches than arrays for managing multiple instances of different control types on the same form.

Moving Form's Class Module Code to Derived Class Module

Returning to today’s topic—moving the Form Module code into a separate Class Module—we will create a new Derived Class Module Object based on the existing ClsTxtArray1_2 Class Module as the Base Class. The code currently in the Form_Load() event procedure of the Form Module will be relocated into this new Derived Class.

If you haven’t already downloaded last week’s demo database, please do so using the link provided before proceeding. We will make copies of the relevant Modules and Forms to modify the code, ensuring that both the original and the updated versions of the code and forms are available within the same database. After making these changes, open and run the Forms to observe how the new implementation works.


After downloading the database, open it in Microsoft Access. Open the Form Module and review its code.

Next, copy the ClsTxtArray1_2 Class Module into a new Class Module named ClsTxtArray1_3, without modifying its code. In the same way, create a copy of the Form and rename it as TxtArray1_3Header. The new copies will undergo changes, ensuring the original Form and Class Module remain intact and unaltered.

We will use last week’s sample Form (shown in the image below) along with its Form Module VBA code, also reproduced below for your reference.


Option Compare Database
Option Explicit

Private Ta() As New ClsTxtArray1_2

Private Sub Form_Load()
Dim cnt As Integer
Dim ctl As Control

For Each ctl In Me.Controls
  If TypeName(ctl) = "TextBox" Then
     cnt = cnt + 1
     ReDim Preserve Ta(1 To cnt)
     Set Ta(cnt).Txt = ctl
     
     If ctl.Name = "Text8" Then
       Ta(cnt).Txt.OnLostFocus = "[Event Procedure]"
     Else
       Ta(cnt).Txt.AfterUpdate = "[Event Procedure]"
     End If
     
  End If
Next
End Sub

Make a Copy of the above Form and name it frmTxtArray1_3Header.

Create a new Class Module named ClsTxtArray1_3.  Copy the VBA Code from the ClsTxtArray1_2 Class Module and paste it into the new Module.

Last week’s Class Module ClsTxtArray1_2  Code is reproduced below for reference.

Option Compare Database
Option Explicit

Private WithEvents Txt As Access.TextBox

Public Property Get mTxt() As Access.TextBox
  Set mTxt = Txt
End Property

Public Property Set mTxt(ByRef txtNewValue As Access.TextBox)
  Set Txt = txtNewValue
End Property

Private Sub Txt_AfterUpdate()
Dim txtName As String, varVal As Variant
Dim msg As String

txtName = Txt.Name
msg = ""
Select Case txtName
    Case "Text0"
        'Valid value range 1 to 5 only
        varVal = Nz(Txt.Value, 0)
        If varVal < 1 Or varVal > 5 Then
           msg = "Valid Value Range 1-5 only: " & varVal
        End If
    Case "Text8"
        'validates in LostFocus Event
    Case "Text10"
        'valid value 10 characters or less
        'Removes extra characters, if entered
        varVal = Nz(Txt.Value, "")
        If Len(varVal) > 10 Then
           msg = "Max 10 Characters Only. " & varVal
           Txt.Value = Left(varVal, 10)
        End If
    Case "Text12"
        'Date must be <= today
        'Future date will be replaced with Today's date
        varVal = DateValue(Txt.Value)
        If varVal > Date Then
          msg = "Future Date Invalid. " & varVal & vbCr & "Corrected to Today's Date."
          Txt.Value = Date
        End If
    Case "Text14"
        'A 10 digit number only valid
        varVal = Trim(Str(Nz(Txt.Value, 0)))
        If Len(varVal) <> 10 Then
          msg = "Invalid Mobile Number: " & varVal
        End If
End Select

If Len(msg) > 0 Then
    MsgBox msg, vbInformation, Txt.Name
End If

End Sub

Private Sub Txt_LostFocus()
Dim tbx As Variant, msg As String

tbx = Nz(Txt.Value, "")

msg = ""
If Len(tbx) = 0 Then
  msg = Txt.Name & " cannot be left Empty."
  Txt.Value = "XXXXXXXXXX"
End If

If Len(msg) > 0 Then
   MsgBox msg, vbInformation, Txt.Name
End If

End Sub

The Derived Class: ClsTxtArray1_3Header

The ClsTxtArray1_3 Class Module will be used as the Base Class for our new Derived Class Module. We will name it ClsTxtArray1_3Header, with the extended functionality.

Create a new Class Module named ClsTxtArray1_3Header. The Derived Class Module, with its Properties and Property Procedures, is given below:

Option Compare Database
Option Explicit

Private Ta() As New ClsTxtArray1_3
Private frm As Access.Form

Public Property Get mFrm() As Access.Form
  Set mFrm = frm
End Property

Public Property Set mFrm(vFrm As Access.Form)
  Set frm = vFrm
  Call Class_Init
End Property

Private Sub Class_Init()
 'Form Module Code goes here
End Sub

Copy and paste the code above into the new Header Class Module you have created.

Check the first two Property declarations.  First, Property ClsTxtArray1_3 Class Object is instantiated as an undefined Array: Ta() – Ta stands for TextBox-Array.

The next property, frm object, will be armed with the physical Form's reference, from which we plan to transfer the existing VBA code. All actions that were previously handled in the Form Module will now be managed here.

We will create Get and Set Property procedures to handle references to the Form. It will be a Set property (not a Let property) because we are passing a Form object, not a simple value, to it.

Immediately after the Form’s reference is received in the Set Property Procedure, we call the Class_Init() subroutine (this is not the same as Class_Initialize(), which runs automatically when a Class Object is instantiated). 

Now, transfer the following Code from the Form_Load() Event Procedure into the Class_Init() subroutine and make changes in the Form Module.

Copy and paste the following lines of code from the Form Module into the Class_Init() sub-routine, replacing the Comment line:

Dim cnt As Integer
Dim ctl As Control

For Each ctl In frm.Controls
  If TypeName(ctl) = "TextBox" Then
     cnt = cnt + 1
     ReDim Preserve Ta(1 To cnt)
     Set Ta(cnt).Txt = ctl
     
     Select Case ctl.Name
        Case "Text8"
            'Only LostFocus Event
            Ta(cnt).Txt.OnLostFocus = "[Event Procedure]"
     Case Else
            'All other text Boxes wiil trigger AfterUpdate Event
            'i.e. entering/editing value in textbox
            Ta(cnt).Txt.AfterUpdate = "[Event Procedure]"
     End Select
     
  End If
Next

Form's Class Module Code

Open the Form frmTxtArray1_3Header in the design view. Display the Code Module. Copy and paste the following Code into the Form's Module, overwriting the existing Code:

Option Compare Database
Option Explicit

Private T As New ClsTxtArray1_3Header

Private Sub Form_Load()
  Set T.mFrm = Me
End Sub

We have instantiated the Derived Class ClsTxtArray1_3Header in Object Variable T. With the statement Set T.mFrm = Me, the active form's reference is passed to the T.mFrm() Set Property Procedure.

Immediately after this action, in the Form_Load () Event procedure, the Class_Init() subroutine runs in the ClsTxtArray1_3Header Class, and the txtArray1_3 Class Object array elements are created by invoking Events for each Text Box on the Form.  Hope you understood the Code above.

If you are ready to modify the Form Module, compile the database to ensure that everything is in order.

Save and close the Form. Open it in Normal View, try each TextBox by entering sample data, and ensure that the Event Subroutines are performing as expected.

Replacing Class Object Array with Collection Object Items

The TextBox Class Object Array method works well for handling multiple TextBoxes. However, creating an array requires a counter variable, resizing the array for each new element while preserving the existing elements, and incrementing the counter for the next TextBox on the form, and so on.

When a form contains multiple controls of other types—such as Command Buttons, ComboBoxes, or ListBoxes—we would need to create separate arrays for each control type, each with its own counter and resizing logic in the class module. We will explore this approach in a future example.

A more efficient way to handle such complex scenarios is to use a Collection object instead of arrays. We will do a demo run with TextBoxes, so you can get a practical feel for managing multiple controls using collections.

  1. Create a new Derived Class Module named ClsTxtArray1_3Coll.
  2. Copy and Paste the following Code into the Class Module:
Option Compare Database
Option Explicit

Private C As New Collection
Private Ta As ClsTxtArray1_3
Private frm As Access.Form

Public Property Get mFrm() As Access.Form
  Set mFrm = frm
End Property

Public Property Set mFrm(vFrm As Access.Form)
  Set frm = vFrm
  Call Class_Init
End Property

Private Sub Class_Init()
'-----------------------------
'Usage of Collection Object, replacing Arrays
'-----------------------------
Dim ctl As Control

For Each ctl In frm.Controls
  If TypeName(ctl) = "TextBox" Then
     
     Set Ta = New ClsTxtArray1_3  'instantiate TextBox Class
     Set Ta.Txt = ctl 'pass control to Public Class Property
     
     Select Case ctl.Name
        Case "Text8"
            'Only LostFocus Event
            Ta.Txt.OnLostFocus = "[Event Procedure]"
     Case Else
            'All other text Boxes wiil trigger AfterUpdate Event
            'i.e. entering/editing value in textbox
            Ta.Txt.AfterUpdate = "[Event Procedure]"
     End Select
     C.Add Ta 'add to Collection Object
  End If
Next

End Sub

A Collection Object Property is declared and instantiated at the beginning. 

The TextBox Class Module is defined, not instantiated, in the Object Variable Ta.

The TextBox Class Ta Object is instantiated within the Control Type Test condition.  A new Ta Object instance is created for each TextBox on the Form.

After enabling the Events, the Ta Class Object is added to the Collection Object as its Item.

The same procedure is repeated for each TextBox Class Object instance on the Form, with its required Events enabled, and each one added as a new item to the Collection Object. This approach produces cleaner Code than the Array method.

Make a copy of the Form frmTxtArray1_3Header named frmTxtArray1_3Coll. 

  1. Open it in Design View and display the Form's Code Module.
  2. Copy and paste the Following Code into the Form Module, replacing the existing Code.
Option Compare Database
Option Explicit

Private Ta As New ClsTxtArray1_3Coll

Private Sub Form_Load()
  Set Ta.mFrm = Me
End Sub

The only change here is the name of the derived object, which has been updated to ClstxtArray1_3Coll. After making this change, recompile the database.

Save the Form, and open it in Normal View. Test the TextBoxes as before.

It should work as before.

Downloads

You can download the database, which includes all the modules and forms with the suggested changes applied.



Links to WithEvents ...Tutorials.

  1. WithEvents MS-Access Class Module Tutorial
  2. WithEvents and Defining Your Own Events
  3. withevents Button Combo List TextBox Tab
  4. Access Form Control Arrays and Event Capturing
  5. Access Form Control Arrays and Event-2
  6. Access Form Control Arrays and Event-3
  7. WithEvents in Class Module for Sub-Form TextBox Events
  8. WithEvents in Class Module and Data Entry
  9. WithEvents and Access Report Event Sink
  10. WithEvents and Report Lines Hiding
  11. WithEvents and Report Lines Highlighting
  12. Withevents TextBox and Command Button Arrays
  13. Withevents TextBox CommandButton Dictionary
  14. Withevents and all Form Control Types

Share:

Update Class Object Dictionary Item through Form

Manage Dictionary Item Through Form.

Last week, we conducted a trial run to add and retrieve Class Objects from Dictionary Object Items.
One important point we emphasized was that each Class Object added to the Dictionary must be created as a new temporary instance of the Class Object every time it is added.

This practice is not exclusive to Class or Dictionary objects; it is a general best practice followed when adding objects to a Collection, Dictionary, or Object Array, to prevent accidental overwriting of existing instances in memory. A brief review of the correct procedure is given below:

  1. Create a temporary Object instance.

    Set tmpObj = New Object
  2. Assign values to the temporary object’s Properties.

  3. Add the temporary object instance as an Item to the Dictionary, Collection, or as an element in the Object Array.

  4. Release the temporary object instance from memory so that it can be reused:

    Set tmpObj = Nothing

Repeat steps 1 to 4 for each new object you want to add.

If you would like to know why we need a temporary Class Object to add several Class Object Instances to the Dictionary Object, visit the page Add Class Object as Dictionary Object Items.

Class Object Instances as Dictionary Items

Now, let us try adding, editing, updating, and deleting Class Objects as Dictionary Items from an MS Access Form.  We worked with the Form and Dictionary Object about two weeks back, by adding and retrieving simple items in the Dictionary Object. 

This time, we are dealing with Objects and Properties in Dictionary Objects, not simple Items, with added data management functions.  Besides that, when the Form is closed, the Dictionary Item Values (ClsArea Class Object Property Values) are saved to a Table.

We will use our simple ClsArea Class Object, with only three Property Values, to fill in (Description, Length, and Width Values) for our trial run.

Even though the Table is used only at the end of the session, we will design a simple table first with the fields: Description (strDesc), Length (dblLength), and Width (dblWidth), matching the Class Module ClsArea Object Property Names in brackets.

  1. Create a new Table named ClsArea with the Field Names and Field Types shown in the Table Design Image below. For Numeric Data Fields, select Double Precision Number Type.

  2. Next, create a Form with the following design to manage the Dictionary Object data.

  3. Design a Form with the following Controls (not bound to the Table):

    • Combo Box on the Header:

      Name Property Value: cboKey

      Row Source Type = Value List

    • Four Unbound Text Boxes in the Detail Section of the Form with the following names:

      strDesc

      dblLength

      dblWidth

      Area

    • Create another TextBox to the right of the last TextBox with the Name:

      ItemCount

    • Create three Command Buttons below the Text Boxes, on the Detail Section, with the Names:

      cmdAdd and Caption: Add

      cmdEdit and Caption: Edit

      cmdDelete and Caption: Delete

    • Create two Command Buttons in the Footer Section of the Form with the Names:

      cmdSave and Caption: Save to Table

      cmdExit and Caption: Exit

  4. The Form’s Normal View Image, with some sample data, is given below:

As the Form control names indicate, you can enter the Property values of the ClsArea Class Object and add the Class Object as an Item to the Dictionary Object.
The value entered in the strDesc Property will be used as the Key for the Dictionary Item.

Important: Ensure that the Description (strDesc) values are unique for each item. If there is a duplicate key, the Dictionary Object will reject it and trigger an error.
(Extensive validation checks have been intentionally omitted in the code to keep it simple.)

You can also retrieve a specific Class Object’s property values from the Dictionary by selecting its Key from the cboKey Combo Box, then:

  • Edit the values directly on the Form and update them back into the Dictionary, or

  • Delete the unwanted item from the Dictionary Object entirely.

The Form Module Event Procedures.

There are several Event Procedures defined in the Form’s Class Module.

A brief explanation of each procedure is provided below.

Option Compare Database
Option Explicit

Dim C As ClsArea, xC As ClsArea
Dim D As Object, Desc As String
Dim editFlag As Boolean, saveFlag As Boolean
Dim strV As String

All important Objects and Variables are declared in the Global declaration area.

Private Sub Form_Load()

Private Sub Form_Load()
    Set D = CreateObject(“Scripting.Dictionary)
    Me.cmdEdit.Enabled = False
    Me.cmdDelete.Enabled = False
    saveFlag = False
End Sub

At the Form Load Event, the Dictionary Object is instantiated.

Edit and Delete CommandButtons are disabled because the Form is in data entry mode by default.  The data save detection flag saveFlag is set to False. The flag will be set to True when the Dictionary Data is saved to the Table, from one of two possible selections of Command Buttons at the Footer of the Form.

Private Sub Form_Current()

Private Sub Form_Current()
Dim o As String
    o = cboKeys.RowSource
    If Len(o) = 0 Then
       Me.cboKeys.Enabled = False
    Else
       Me.cboKeys.Enabled = True
    End If
End Sub

Checks whether the Combo box's Row Source property has any Value in it (when the Form is open, it will be empty); if not, disable the Combo box Control.

Private Sub strDesc_GotFocus()
Private Sub strDesc_GotFocus()
 If editFlag Then
  Me.cmdAdd.Enabled = False
  Me.cmdEdit.Enabled = True
  Me.cmdDelete.Enabled = True
 Else
  Call initFields
  Me.cmdAdd.Enabled = True
  Me.cmdEdit.Enabled = False
  Me.cmdDelete.Enabled = False
  editFlag = False
 End If

End Sub

Checks whether the form is in Edit mode.  Edit Mode Flag is set when the user clicks the Edit Command Button.  This happens after the user selects an item from the ComboBox.

If in edit mode, the data entry fields are not cleared, the Edit and Delete Command Buttons are enabled, and the Add Command Button is disabled.

If it is in Add mode, then data fields are emptied in preparation for a new Item, the Add Command Button is enabled, and the Edit and Delete Command Buttons are disabled.

Private Sub dblWidth_LostFocus()

Private Sub dblWidth_LostFocus()
'Area is displayed for info purpose only
  Me!Area = Nz(Me!dblLength, 0) * Nz(Me!dblWidth, 0)
End Sub

On the dblWidth Text Box's LostFocus Event, the product of dblLength * dblwidth is displayed in the Area Text Box.

Private Sub cmdAdd_Click()

Private Sub cmdAdd_Click()
'--------------------------------------------------
'1. Add Class Object as Item to Dictionary Object
'2. Update Combobox RowSource Property
'--------------------------------------------------
Dim tmpstrC As String, tmpLength As Double, tmpWidth As Double
Dim flag As Integer, msg As String
Dim cboVal As String, cbo As ComboBox

editFlag = False
Set cbo = Me.cboKeys

tmpstrC = Nz(Me!strDesc, "")
tmpLength = Nz(Me!dblLength, 0)
tmpWidth = Nz(Me!dblWidth, 0)

flag = 0
If Len(tmpstrC) = 0 Then flag = flag + 1
If tmpLength = 0 Then flag = flag + 1
If tmpWidth = 0 Then flag = flag + 1
If flag > 0 Then
   msg = "Invalid Data in one or more fields, correct them and retry."
   MsgBox msg, , "cmdAdd()"
   Exit Sub
End If

Desc = ""
Set C = New ClsArea 'create a new instance
'add Property Values
    C.strDesc = tmpstrC
    C.dblLength = tmpLength
    C.dblWidth = tmpWidth
    
'add Class Object instance to Dictionary
    
    D.Add tmpstrC, C 'Description is the Key
    
    Call comboUpdate(tmpstrC) 'update description as combobox item
       
    Me.ItemCount = D.Count 'display dictionary Items count
    
    'Call initFields 'set all fields to blanks
    Me.strDesc.SetFocus 'make description field current
    
'Clear Class Object
    Set C = Nothing

End Sub

The TextBox values are moved into temporary variables, and checks are made to see whether any text box is empty.

The ClsArea Class Object Properties are assigned with strDesc, dblLength & dblwidth TextBox values from temporary variables.

Adds the Class Object to the Dictionary Object.

Updates the Combo Box with the Dictionary Object Key from the Class Object's Description (strDesc) Value.

The Dictionary Items Count is displayed on the ItemCount Text Box.

The focus is set in the strDesc field. The text boxes are cleared for entry of new values.

Private Sub cboKeys_AfterUpdate()

Private Sub cboKeys_AfterUpdate()
On Error Resume Next

strV = Me!cboKeys
Set xC = D(strV)
If Err > 0 Then
   MsgBox "Item for Key: " & strV & " Not Found!"
   Exit Sub
End If

Me!strDesc = xC.strDesc
Me!dblLength = xC.dblLength
Me!dblWidth = xC.dblWidth
Me!Area = xC.Area

Me.cmdAdd.Enabled = False
Me.cmdEdit.Enabled = True
Me.cmdDelete.Enabled = True
Me.strDesc.Enabled = False

On Error GoTo 0

End Sub

Retrieving the Dictionary Item

After adding several items to the Dictionary Object, you can retrieve any item by selecting its Key from the Combo Box. The corresponding details will be displayed on the Form. In this mode, the Add command button and the strDesc field will remain disabled. The strDesc field will be unlocked only when you click the Edit button.

If you select an invalid Key (for example, a Key value that was changed during earlier edit operations) from the Combo Box, an error message — ‘Item for Key: XXXX not found’ — will be displayed, indicating that the specified item does not exist in the Dictionary Object.

Private Sub cmdEdit_Click()

Private Sub cmdEdit_Click()
'Edit the displayed item properties
Dim cap As String
Dim mDesc As String

editFlag = True

strV = Me!cboKeys

cap = Me.cmdEdit.Caption
Select Case cap
   Case "Edit"
      Me.strDesc.Enabled = True
      Me.cmdAdd.Enabled = False 'when editing Add button is disabled
      Me.cmdEdit.Caption = "Update" 'Edit Button Caption is changed
      
   Case "Update" 'Button clicked when Caption is Update
   'directly replace the property value in the Item
      xC.strDesc = Me!strDesc
      xC.dblLength = Me!dblLength
      xC.dblWidth = Me!dblWidth
      mDesc = Me!strDesc 'changed Description is copied to mDesc
      
   If mDesc <> strV Then 'checks with key in combobox value if not same then
      D.Key(strV) = mDesc 'Change Dictionary Key of Item
      
      'update new desc to Combobox
      'old desc also remains in combobox
      Call comboUpdate(mDesc)
   End If
      Call initFields
      Me.strDesc.SetFocus
      Me.cmdAdd.Enabled = True 'Enable Add button to add new item
      Me.cmdEdit.Caption = "Edit" 'change caption from Update to Edit
      Me.cmdEdit.Enabled = False 'disable Edit Button
      Me.cmdDelete.Enabled = False 'disable Edit Button
End Select

End Sub

Editing Dictionary Item

The record is retrieved from the Dictionary and placed on the Form controls. If the data needs to change, then click the Edit CommandButton. The CommandButton click changes its Caption to Update.

Make the required changes to the fields (including the Description, if needed), then click the Update button again to save the changes back to the Dictionary Object.

If the strDesc value (the Dictionary Key) is modified, the corresponding old Key in the Dictionary is replaced with the new Description value. The Combo Box will also be updated to reflect the new Key, though the old Key appears in the ComboBox until it is refreshed.

After the update, the data fields are cleared, the Add button is enabled again, and the Edit and Delete buttons are disabled.

Private Sub cmdDelete_Click()

Private Sub cmdDelete_Click()p
Dim txtKey As String, msg As String

txtKey = Me!cboKeys
msg = "Delete Object with Key: " & txtKey & " ..?"
If MsgBox(msg, vbYesNo + vbDefaultButton2 + vbCritical) = vbYes Then
   D.Remove (txtKey) 'Remove the Item matching the Key
   
   MsgBox "Item Deleted."
   
   Call initFields
   
   Me.strDesc.Enabled = True
   Me.strDesc.SetFocus 'make description field current
   Me.ItemCount = D.Count 'display items count
   Me.cmdAdd.Enabled = True
   Me.cmdEdit.Enabled = False
   Me.cmdDelete.Enabled = False
   
End If

End Sub

Deleting Dictionary Item

When the Delete Command Button is clicked, the current Item on the Form is deleted from the Dictionary, after the user reconfirms it.

The data entry fields are cleared, and the item count control is updated with the reduced number of items.

Private Sub cmdSave_Click()

Private Sub cmdSave_Click()
Dim msg As String

msg = "Form will be closed After Saving Data,"
msg = msg & vbCr & "Proceed...?"
If MsgBox(msg, vbYesNo + vbDefaultButton2 + vbInformation, "cmdSave_Click()") = vbNo Then
  Exit Sub
Else
  Call Save2Table
  DoCmd.Close
End If

End Sub
 

Saving Data from the Dictionary Object.

When you have finished working with the Form and want to save the Dictionary Data Class’s property values from the Dictionary to the temporary Table (clsArea) and to close the Form, click on the 'Save to Table' Command Button.

Private Sub cmdExit_Click()

Private Sub cmdExit_Click()
Dim msg As String
If saveFlag Then
    DoCmd.Close
Else
    msg = "Dictionary Data Not saved in Table!"
    msg = msg & vbCr & vbCr & "Click Cancel to Go back"
    msg = msg & vbCr & vbCr & "Click OK to discard Data and close the Form!"
    If MsgBox(msg, vbOKCancel + vbDefaultButton2 + vbQuestion, "cmdExit()") = vbOK Then
        DoCmd.Close
    Else
        Call Save2Table
        DoCmd.Close
    End If
End If
End Sub

If you choose to click the Exit command button instead of the Save to Table button, you will be prompted to confirm whether you want to save the data. If you decide not to save, you can select the appropriate option to close the form without writing the data to the table.

A few common subroutines that are called from multiple event procedures. Their code is included in the full listing provided below:

  • Private Sub initFields()
    Clears all text boxes on the form when called from event procedures.

  • Private Sub comboUpdate(ByVal stDesc As String)
    Called from the cmdAdd_Click() and cmdEdit_Click() event procedures to update the Combo Box items.

  • Private Sub Save2Table()
    Called from the cmdSave_Click() and cmdExit_Click() event procedures to save the Dictionary Object data to the table.

The Form's Class Module Code.

Highlight, Copy, and Paste the Entire Code below into the Form's Class Module. Save the Form as frmDictionary.

Option Compare Database
Option Explicit

Dim C As ClsArea, xC As ClsArea
Dim D As Object, Desc As String
Dim editFlag As Boolean, saveFlag As Boolean
Dim strV As String

Private Sub Form_Load()
    Set D = CreateObject(“Scripting.Dictionary”)
    Me.cmdEdit.Enabled = False
    Me.cmdDelete.Enabled = False
    saveFlag = False
End Sub

Private Sub Form_Current()
Dim o As String
o = cboKeys.RowSource
If Len(o) = 0 Then
  Me.cboKeys.Enabled = False
Else
  Me.cboKeys.Enabled = True
End If
End Sub


Private Sub cboKeys_AfterUpdate()
On Error Resume Next

strV = Me!cboKeys
Set xC = D(strV)
If Err > 0 Then
   MsgBox "Item for Key: " & strV & " Not Found!"
   Exit Sub
End If

Me!strDesc = xC.strDesc
Me!dblLength = xC.dblLength
Me!dblWidth = xC.dblWidth
Me!Area = xC.Area

Me.cmdAdd.Enabled = False
Me.cmdEdit.Enabled = True
Me.cmdDelete.Enabled = True
Me.strDesc.Enabled = False

On Error GoTo 0

End Sub

Private Sub cmdAdd_Click()
'--------------------------------------------------
'1. Add Class Object as Item to Dictionary Object
'2. Update Combobox RowSource Property
'--------------------------------------------------
Dim tmpstrC As String, tmpLength As Double, tmpWidth As Double
Dim flag As Integer, msg As String
Dim cboVal As String, cbo As ComboBox

editFlag = False
Set cbo = Me.cboKeys

tmpstrC = Nz(Me!strDesc, "")
tmpLength = Nz(Me!dblLength, 0)
tmpWidth = Nz(Me!dblWidth, 0)

flag = 0
If Len(tmpstrC) = 0 Then flag = flag + 1
If tmpLength = 0 Then flag = flag + 1
If tmpWidth = 0 Then flag = flag + 1
If flag > 0 Then
   msg = "Invalid Data in one or more fields, correct them and retry."
   MsgBox msg, , "cmdAdd()"
   Exit Sub
End If

Desc = ""
Set C = New ClsArea 'create a new instance
'add Property Values
    C.strDesc = tmpstrC
    C.dblLength = tmpLength
    C.dblWidth = tmpWidth
    
'add Class Object instance to Dictionary
    
    D.Add tmpstrC, C 'Description is the Key
    
    Call comboUpdate(tmpstrC) 'update description as combobox item
       
    Me.ItemCount = D.Count 'display dictionary Items count
    
    'Call initFields 'set all fields to blanks
    Me.strDesc.SetFocus 'make description field current
    
'Clear Class Object
    Set C = Nothing

End Sub

Private Sub cmdDelete_Click()
Dim txtKey As String, msg As String

txtKey = Me!cboKeys
msg = "Delete Object with Key: " & txtKey & " ..?"
If MsgBox(msg, vbYesNo + vbDefaultButton2 + vbCritical) = vbYes Then
   D.Remove (txtKey) 'Remove the Item matching the Key
   
   MsgBox "Item Deleted."
   
   Call initFields
   
   Me.strDesc.Enabled = True
   Me.strDesc.SetFocus 'select description field current
   Me.ItemCount = D.Count 'display items count
   Me.cmdAdd.Enabled = True
   Me.cmdEdit.Enabled = False
   Me.cmdDelete.Enabled = False
   
End If

End Sub

Private Sub cmdEdit_Click()
'Edit the displayed item properties
Dim cap As String
Dim mDesc As String

editFlag = True

strV = Me!cboKeys

cap = Me.cmdEdit.Caption
Select Case cap
   Case "Edit"
      Me.strDesc.Enabled = True
      Me.cmdAdd.Enabled = False 'when editing Add button is disabled
      Me.cmdEdit.Caption = "Update" 'Edit Button Caption is changed
      
   Case "Update" 'Button clicked when Caption is Update
   'directly replace the property value in the Item
      xC.strDesc = Me!strDesc
      xC.dblLength = Me!dblLength
      xC.dblWidth = Me!dblWidth
      mDesc = Me!strDesc 'changed Description is copied to mDesc
      
   If mDesc <> strV Then 'checks with key in combobox value if not same then
      D.Key(strV) = mDesc 'Change Dictionary Key of Item
      
      'update new desc to Combobox
      'old desc also remains in combobox
      Call comboUpdate(mDesc)
   End If
      Call initFields
      Me.strDesc.SetFocus
      Me.cmdAdd.Enabled = True 'Enable Add button to add new item
      Me.cmdEdit.Caption = "Edit" 'change caption from Update to Edit
      Me.cmdEdit.Enabled = False 'disable Edit Button
      Me.cmdDelete.Enabled = False 'disable Edit Button
End Select

End Sub

Private Sub cmdExit_Click()
Dim msg As String
If saveFlag Then
    DoCmd.Close
Else
    msg = "Dictionary Data Not saved in Table!"
    msg = msg & vbCr & vbCr & "Click Cancel to Go back"
    msg = msg & vbCr & vbCr & "Click OK to discard Data and close the Form!"
    If MsgBox(msg, vbOKCancel + vbDefaultButton2 + vbQuestion, "cmdExit()") = vbOK Then
        DoCmd.Close
    Else
        Call Save2Table
        DoCmd.Close
    End If
End If
End Sub

Private Sub cmdSave_Click()
Dim msg As String

msg = "Form will be closed After Saving Data,"
msg = msg & vbCr & "Proceed...?"
If MsgBox(msg, vbYesNo + vbDefaultButton2 + vbInformation, "cmdSave_Click()") = vbNo Then
  Exit Sub
Else
  Call Save2Table
  DoCmd.Close
End If

End Sub

Private Sub Save2Table()
Dim db As Database, rst As Recordset
Dim recCount As Long, j As Long, item

On Error GoTo Save2Table_Error

recCount = D.Count

Set db = CurrentDb
Set rst = db.OpenRecordset("ClsArea", dbOpenTable)
For Each item In D.Items
   With rst
        .AddNew
         !strDesc = item.strDesc
         !dblLength = item.dblLength
         !dblWidth = item.dblWidth
        .Update
    End With
Next

rst.Close
Set rst = Nothing
Set db = Nothing

saveFlag = True
MsgBox "Data Saved  to Table: ClsArea"

Save2Table_Exit:
Exit Sub

Save2Table_Error:
MsgBox Err & ":" & Err.Description, , "Save2Table_Click()"
Resume Save2Table_Exit

End Sub

Private Sub Form_Unload(Cancel As Integer)
Set D = Nothing
End Sub

Private Sub strDesc_GotFocus()
    If editFlag Then
        Me.cmdAdd.Enabled = False
        Me.cmdEdit.Enabled = True
        Me.cmdDelete.Enabled = True
    Else
        Call initFields
        Me.cmdAdd.Enabled = True
        Me.cmdEdit.Enabled = False
        Me.cmdDelete.Enabled = False
        editFlag = False
    End If

End Sub


Private Sub dblWidth_LostFocus()
'Area is displayed for info purpose only
  Me!Area = Nz(Me!dblLength, 0) * Nz(Me!dblWidth, 0)
End Sub

Private Sub initFields()
'Empty all fields
    Me!strDesc = Null
    Me!dblLength = Null
    Me!dblWidth = Null
    Me!Area = Null
    Me.cmdAdd.Enabled = True
End Sub

Private Sub comboUpdate(ByVal stDesc As String)
Dim cbo As ComboBox, cboVal As String
Set cbo = Me.cboKeys
    cboVal = cbo.RowSource
    cboVal = cboVal & ";" & stDesc
    cbo.RowSource = cboVal
    cbo.Requery
    If Len(cboVal) > 0 Then
      Me.cboKeys.Enabled = True
    End If
End Sub

Perform a Demo Run.

  1. Open the frmDictionary form, and try adding a few entries to the Dictionary object.

  2. Select an item from the Combo Box to view the property values of the corresponding class object.

  3. Click the Edit command button and modify the Description, Length, and Width values as needed.

  4. Click the Edit button again (now showing the caption Update) to save the changes in the Dictionary object. If you open the Combo Box afterwards, you will see both the updated item with the new description and the old item with its previous description.

  5. Select the old description from the Combo Box. You will receive an error message indicating that the item no longer exists, as its Dictionary key has been updated to the new description.

  6. To delete an entry, select an item from the Combo Box and click the Delete command button. The selected item will be removed from the Dictionary object.

  7. The event procedure code has been kept simple, without validation checks, error-handling routines, or safeguards against unintended user actions on the form.

  8. You may enhance the code with these features, if needed, and reuse it in your own projects.

Downloads

You may download this demo database from the link given below.

Download Dictionary_2003.zip

Download Dictionary_2007.zip


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:

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 External Links Queries Array Class Module msaccess reports Accesstips msaccess tips WithEvents Downloads Objects Menus and Toolbars MsaccessLinks Process Controls Art Work Collection Object Property msaccess How Tos Combo Boxes ListView Control Query VBA msaccessQuery Calculation Dictionary Object 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 Recordset Top Values Variables msaccess email progressmeter Access2007 Copy Excel Expression Fields Join Methods Microsoft Numbering System RaiseEvent Records Security Split SubForm Table Tables Time Difference Utility WScript Workgroup Wrapper Classes 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 Export 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