Learn Microsoft Access Advanced Programming Techniques, Tips and Tricks.

Streamlining Form Module Code - Part Nine

 Introduction.

After working through the last eight episodes of this series, you are likely familiar with this new coding approach and its benefits. By using the stand-alone Class Module–based method, we can achieve greater functionality while writing significantly less VBA code.

Episode Seven demonstrated how to highlight an entire array of TextBoxes on the Form. The OnGotFocus() Subroutine highlights the active TextBox, while the LostFocus() Subroutine resets it to its original state. Remarkably, this was achieved with just six lines of VBA code in the WrapTextBox Class. The public functions GFColor() and 'LFColor()' in the Standard Module are called from the GotFocus() and LostFocus() Event Subroutines in the WrapTextBox Class, respectively.

Private Sub txt_GotFocus()
    GFColor frm, Txt 'Field Highlight
End Sub

Private Sub txt_LostFocus()
    LFColor frm, Txt 'Field Highlight
End Sub

If you add more TextBoxes on the Form or delete existing ones, and not necessary to make any changes to the VBA Code.

Similarly, the following code snippet tracks all TextBox controls on the Employees form, detecting any attempts to modify their values. This monitoring applies to every TextBox on both the main Employees form and the Orders subform.

The OnDirty Event Subroutine.

Private Sub txt_Dirty(Cancel As Integer)
    If MsgBox("Editing the " & UCase(Txt.Name) _
    & ": Value? " & Txt.Value, vbYesNo + vbQuestion, _
    Txt.Name & " DIRTY()") = vbNo Then
    
        Cancel = True
    End If
End Sub

The code snippet above monitors all TextBoxes on the main form, Employees, and its subform, Orders, to prevent unintended changes. When a user attempts to modify a field, a warning message appears, requiring confirmation before the edit can proceed. If the user recognizes the action as a mistake, they can cancel the event and restore the field to its original value. This approach helps prevent accidental modifications and maintains data integrity.

The TextBoxes on the Orders SubForm are also under the surveillance of this Code.

The BeforeUpdate Event Subroutine.

When data is modified and the Enter key is pressed, the BeforeUpdate event procedure is triggered, safeguarding all TextBox controls on the forms. These events are handled collectively within the single subroutine txt_BeforeUpdate(), as demonstrated in the code below.

'Data Update Reconfirm to Save the Change
Private Sub txt_BeforeUpdate(Cancel As Integer)

    If MsgBox("Field Name: " & Txt.Name & vbCr & _
        "Original Value '" & UCase(Txt.OldValue) & "'" & _
        vbCr & "Change to: '" _
        & UCase(Txt.Value) & "'", vbYesNo + vbQuestion, _
        UCase(Txt.Name) & " BeforeUpdate()?") = vbNo Then
    
        Cancel = True
    End If


End Sub

Again, the User has to reconfirm to save the data in the Field.

Reusability of Streamlined VBA Code Writing.

The key advantage of this streamlined VBA coding approach is reusability. Instead of writing separate event-handling code for each TextBox within the form module, you only need to define it once in a TextBox wrapper class module. This class module serves as a template for managing BeforeUpdate() events across all TextBox controls.

Here's how it works:

  1. Write the BeforeUpdate event handling code in the TextBox Wrapper Class Module.

  2. Instantiate the Wrapper Class Module for each TextBox in the Main Form and Subform.
  3. Assign the references of the Form and TextBox controls to their corresponding properties in the Wrapper Class Module.

  4. Enable the required event procedures (e.g., BeforeUpdate) in the Wrapper Class EmpObject_Init Module.

  5. Store the instantiated Wrapper Class EmpTextBox in memory through the Collection Object.

By adopting this approach, you can efficiently manage events for multiple TextBoxes across different forms without duplicating code. It improves code organization, eliminates redundancy, and simplifies future maintenance and updates to the event-handling logic.

So far, we have not worked with an example involving a main form and subform together—specifically, how to reference controls on the subform, enable their events, and streamline the code for both forms.

In this episode, the Employees table will serve as the record source for the main form Employees. The subform Orders, based on the Orders table, is designed as a tabular form.

We will explore how to reference subform controls (both TextBox and ComboBox) within the EmpObject_Init wrapper class, enable their required events, and store the wrapper class instances in a collection object, just as we did earlier.

The Control Wrapper Class (EmpTextBox) instances, once enabled with their required events, remain in memory as items within the collection object. They capture events triggered by the TextBoxes on both the form and subform and execute the corresponding event subroutines.

In the last episode, we created a new Wrapper Class EmpCmdButton for the Command Button Class Object and added it as the Property of the EmpObject_Init Class.

This time, we need a new wrapper class, EmpCombo, for the ComboBox control to capture events triggered by ComboBoxes on both the main form and subform.

In the footer section of the Employees form, there is a TextBox control used to search for an employee record by EmployeeID as the search key.

After the search operation, a Label control will briefly flash (label animation) to indicate whether the search was successful, and then disappear.

The important question is: where should we place the code for the data search and record lookup, as well as the label animation?

If you recall from the previous episode (Part Eight), we implemented a form-close countdown display within the WrapCmdButton class, specifically inside the cmd_Click() event subroutine. The countdown was shown in a Label control on the form. Similarly, a digital clock was also displayed in a Label control, but the code that managed the form’s TimerInterval settings was executed from the WrapObject_Init wrapper class.

Similarly, another common task is handled in the footer section of the form. An unbound TextBox, FindIDTextBox, is provided for searching by EmployeeID. When a value is entered, it is used to locate the corresponding record on the Employees form. The key question is: in which wrapper class should we place the VBA code for the search and find operations?

Besides that, there is a Label Control below the FindID TextBox to display the result of the search operation.

The EmployeeID will be entered into the unbound FindID TextBox, followed by pressing the Enter key. This action should trigger a search for the matching EmployeeID record on the Employees form. The code can be placed in the TextBox AfterUpdate() or LostFocus() event subroutine. However, the record lookup itself should be performed on the form’s RecordsetClone.

Alternatively, a Command Button can be added next to the unbound TextBox to initiate the search. In that case, the code can be written in the Command Button’s Click event subroutine within the EmpCmdButton wrapper class.

To provide feedback, a Label control in the form’s footer will display an animated (On/Off) status message indicating whether the search was successful or not. Events that involve the Form Object.

Each wrapper class we create—such as EmpTextBox, EmpCmdButton, EmpCombo, and others—will include the form object as a property, defined either with or without the WithEvents declaration.

When declared with WithEvents (e.g., Private WithEvents frm As Form), the wrapper class can directly capture the form’s events. Without WithEvents, the form property can still be used to access its controls, allowing you to read or write values and work with control properties as needed.

Regardless of whether the form property is actively used, a reference to the physical form is assigned to all wrapper class instances within the Class_Init() subroutine of the intermediate classes, such as WrapObject_Init or EmpObject_Init.

For this reason, we prefer to place the FindID TextBox AfterUpdate() event subroutine code within the EmpTextBox wrapper class module.

First, let us see how to incorporate the FindID unbound TextBox’s AfterUpdate() event-enabling code into the EmpObject_Init wrapper class, within the existing implementation. The complete VBA code is provided below. In addition, the ComboBox wrapper class property (named CBO) and its event-enabling code have been added to the Class_Init subroutine. All newly added code lines are highlighted in red.

The WrapObject_Init class was copied from the earlier project, renamed as EmpObject_Init, and extended with the new code lines marked in red. The same approach applies to other classes. This method allows you to quickly create wrapper classes, reuse existing code for different forms, and apply modifications where necessary.

Option Compare Database
Option Explicit

Private WithEvents iFrm As Access.Form
Private WithEvents oFrm As Form

Private iTxt As EmpTextBox
Private wcbo As EmpCombo
Private wcmd As EmpCmdButton
Private Coll As New Collection

'------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Main Form SubForm with Data
'Author: a.p.r. pillai
'Date  : 06/08/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------

'Form's Property GET/SET Procedures
Public Property Get m_Frm() As Form
    Set m_Frm = iFrm
End Property

Public Property Set m_Frm(ByRef mfrm As Form)
    Set iFrm = mfrm
Call Class_Init
End Property

'Events Enabling Subroutine
Private Sub Class_Init()
Dim ctl As Control

Const EP = "[Event Procedure]"
iFrm.OnTimer = EP  'Enable TimerInverval for Label Annimation

'Scan for TextBox, CommandButton & Combobox Controls on Employees Form
'and Enable the required Event Procedures
For Each ctl In iFrm.Controls 'Find TextBox, ComboBox & CommandButtons

    Select Case TypeName(ctl)
            Case "TextBox"
              Select Case ctl.Name
                Case "FindID" 'Employee ID Search TextBox
                    Set iTxt = New EmpTextBox 'Create Instance
                    Set iTxt.tx_Frm = iFrm 'Assign Form Object
                    Set iTxt.t_Txt = ctl   'TextBox
                    
                        iTxt.t_Txt.OnGotFocus = EP
                        iTxt.t_Txt.OnLostFocus = EP
                        iTxt.t_Txt.AfterUpdate = EP 'For EmployeeID Search
                        
                    Coll.Add iTxt 'Save EmpTextBox Class
                    Set iTxt = Nothing 'Erase temp Instance
                  GoTo CmdButton
              End Select

                    Set iTxt = New EmpTextBox 'Create Instance
                    Set iTxt.tx_Frm = iFrm
                    Set iTxt.t_Txt = ctl       'Pass TextBox Control on Form
 
                         iTxt.t_Txt.OnGotFocus = EP
                         iTxt.t_Txt.OnLostFocus = EP
                         iTxt.t_Txt.OnDirty = EP      'To warn against Data Change
                         iTxt.t_Txt.BeforeUpdate = EP 'Reconfirm Data Change to update
                        
                    Coll.Add iTxt 'Save EmpTextBox Class in Collection Object
                    Set iTxt = Nothing 'Erase temp Instance
CmdButton:
        Case "CommandButton"
            Select Case ctl.Name
                Case "cmdClose"
                    Set wcmd = New EmpCmdButton
                    Set wcmd.cmd_Frm = iFrm
                    Set wcmd.c_cmd = ctl
                    
                    wcmd.c_cmd.OnClick = EP
                    
                    Coll.Add wcmd
                    Set wcmd = Nothing
            End Select
        
        Case "ComboBox"
                    Set wcbo = New EmpCombo
                    Set wcbo.cbo_Frm = iFrm
                    Set wcbo.c_cbo = ctl
                    
                   wcbo.c_cbo.OnGotFocus = EP
                   wcbo.c_cbo.OnLostFocus = EP
                    
                    Coll.Add wcbo
                    Set wcbo = Nothing
    End Select
Next

'Order Sub-Form
  Set oFrm = iFrm.Orders.Form

For Each ctl In oFrm.Controls 'Scan for SubForm Control and enable Events
Select Case TypeName(ctl)
    Case "TextBox"

            Set iTxt = New EmpTextBox 'Create Instance
            Set iTxt.tx_Frm = oFrm
            Set iTxt.t_Txt = ctl       'Pass TextBox Control on Form

               iTxt.t_Txt.OnGotFocus = EP 'To highlght TextBox
               iTxt.t_Txt.OnLostFocus = EP 'Reset Highlight
               
               iTxt.t_Txt.OnDirty = EP
               iTxt.t_Txt.BeforeUpdate = EP
               
                Coll.Add iTxt 'Save EmpTextBox Class
            Set iTxt = Nothing 'Erase temp Instance

        Case "ComboBox"
             Set wcbo = New EmpCombo
             Set wcbo.cbo_Frm = oFrm
             Set wcbo.c_cbo = ctl

                 wcbo.c_cbo.OnGotFocus = EP 'To highlght ComboBox
                 wcbo.c_cbo.OnLostFocus = EP 'Reset Highlight
                     
                   Coll.Add wcbo
                Set wcbo = Nothing
    End Select
Next

End Sub

Private Sub Class_Terminate()
'Delete Collection Object contents
    Do While Coll.Count > 0
        Coll.Remove 1
    Loop
    
    Set iFrm = Nothing
    Set oFrm = Nothing
End Sub

The EmpTextBox Wrapper Class EmployeeID Search VBA Code is given below:

Option Compare Database
Option Explicit

Private WithEvents frm As Form
Private subFrm As Form

Private WithEvents Txt As TextBox 'TextBox object
Dim L As Integer
Dim ForeColor As Long
'------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Main Form SubForm with Data
'Author: a.p.r. pillai
'Date  : 06/08/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------
'Form's Property GET/SET Procedures
Public Property Get tx_Frm() As Form
    Set tx_Frm = frm
End Property

Public Property Set tx_Frm(ByRef pfrm As Form)
    Set frm = pfrm
End Property

'TextBox Property GET/SET Procedures
Public Property Get t_Txt() As TextBox
    Set t_Txt = Txt
End Property

Public Property Set t_Txt(ByRef tTxt As TextBox)
    Set Txt = tTxt
End Property

'Evbent Subroutines
'===================

Private Sub txt_GotFocus()
    GFColor frm, Txt 'Field Highlight
    
    If Txt.Name = "FindID" Then
        Txt.Value = Null
    End If
End Sub

Private Sub txt_LostFocus()
    LFColor frm, Txt 'Field Highlight
End Sub

Private Sub txt_Dirty(Cancel As Integer)
    If MsgBox("Editing the " & UCase(Txt.Name) _
    & ": Value? " & Txt.Value, vbYesNo + vbQuestion, _
    Txt.Name & " DIRTY()") = vbNo Then
    
        Cancel = True
    End If
End Sub

'Data Update Reconfirm to Save the Change
Private Sub txt_BeforeUpdate(Cancel As Integer)
Dim msg As String

msg = "Field Name: " & Txt.Name & vbCr & _
        "Original Value '" & UCase(Txt.OldValue) & "'" & _
        vbCr & "Change to: '" & UCase(Txt.Value) & "'"
    
    If MsgBox(msg, vbYesNo + vbQuestion, _
        Txt.Name & "_BeforeUpdate()") = vbNo Then
        Cancel = True
    End If

End Sub

Private Sub txt_AfterUpdate()
Select Case Txt.Name
    Case "FindID"
        Dim rst As Recordset
        Dim ToFind As Integer
        Dim msg As String
        Dim max As Integer
        
        'max = DCount("*", "Employees")
        ToFind = Nz(frm!FindID, 0)
        If ToFind < 1 Then
            msg = "Employee ID: < 1 Invalid!"
            MsgBox msg, vbOK + vbCritical, Txt.Name & "_AfterUpdate()"
        Else
            Set rst = frm.RecordsetClone
            rst.FindFirst "EmployeeID=" & ToFind
            If Not rst.NoMatch Then
                frm.Bookmark = rst.Bookmark
        
                With frm.Result
                    .Caption = "*** Successful ***"
                    ForeColor = 16711680
                    .ForeColor = ForeColor
                End With
            Else
                With frm.Result
                    .Caption = "**Sorry, Not found!"
                    ForeColor = 255
                End With
            
            End If
            L = 0
            frm.TimerInterval = 250 'Enable Timer
        End If
    End Select
End Sub

'Label Animation Code.
Private Sub frm_Timer()
L = L + 1
Select Case L
    Case 1, 3, 5, 7, 9, 11, 13, 15, 17
        frm.Result.Visible = True
    Case 2, 4, 6, 8, 10, 12, 14, 16, 18
        frm.Result.Visible = False
    Case 19
       frm.Result.ForeColor = ForeColor
       frm.Result.Visible = False
       frm.TimerInterval = 0
End Select
End Sub

The EmpCombo is a new wrapper class for the ComboBox control. Its GotFocus() and LostFocus() event subroutine VBA code is shown below:

Option Compare Database
Option Explicit

Private cbofrm As Access.Form
Private WithEvents cbo As Access.ComboBox 'ComboBox object

'------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'ComboBox Wrapper Class
'Author: a.p.r. pillai
'Date  : 06/08/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------

'Form's Property GET/SET Procedures
Public Property Get cbo_Frm() As Form
    Set cbo_Frm = cbofrm
End Property

Public Property Set cbo_Frm(ByRef cfrm As Form)
    Set cbofrm = cfrm
End Property

'TextBox Property GET/SET Procedures
Public Property Get c_cbo() As ComboBox
    Set c_cbo = cbo
End Property

Public Property Set c_cbo(ByRef pcbo As ComboBox)
    Set cbo = pcbo
End Property

'Event Subroutines Code
Private Sub cbo_GotFocus()
    GFColor cbofrm, cbo 'ComboBox highlight
End Sub

Private Sub cbo_LostFocus()
    LFColor cbofrm, cbo 'ComboBox highlight
End Sub

Your Assessment of the Streamlined Coding Procedure in the Standalone Class Module.

If you are an experienced Access VBA programmer, you may be able to assess the following points:

  • The amount of work and time required to write OnDirty() event subroutines for each TextBox and ComboBox control on the Employees form and the Orders subform using the traditional, manual VBA coding approach.

  • Similarly, the effort is needed to implement the AfterUpdate() event subroutines across both forms.

In practice, we typically write such code in the form module only for essential controls. Considering this, how do you feel about the new method in terms of ease of implementation and time savings compared to conventional coding practices? The focus here is not on quantifying the savings but on the reusability of code in standalone class modules and the potential reduction in development time for future projects.

Although this new approach—streamlining form module coding into standalone class modules—may be harder for beginners to fully evaluate, as an expert VBA programmer, what is your perspective on its effectiveness?

All episodes are written in a tutoring style, with title-related examples, so that non-experts can follow along and try them out. From your experience, how easy or difficult is it for non-experts to understand these concepts?

You are welcome to leave your feedback in the comment section. If you have a Gmail account, you can log in to post your comments. Thank you!

The upcoming demonstrations on this topic will cover the implementation of other Access class objects—such as Tab Control, ListBox, and Option Group—as well as examples of streamlining report module code.

The Demo Database is attached for Download.

Streamlining Form Module Code in Standalone Class Module.

  1. Reusing Form Module VBA Code for New Projects.
  2. Streamlining Form Module Code - Part Two.
  3. Streamlining Form Module Code - Part Three
  4. Streamlining Form Module Code - Part Four
  5. Streamlining Form Module Code - Part Five
  6. Streamlining Form Module Code - Part Six
  7. Streamlining Form Module Code - Part Seven
  8. Streamlining Form Module Code - Part Eight
  9. Streamlining Form Module Code - Part Nine
  10. Streamlining Form Module Code - Part Ten
  11. Streamlining Form Module Code - Part Eleven
  12. Streamlining Report Module Code in Class Module
  13. Streamlining Module Code Report Line Hiding-13.
  14. Streamlining Form Module Code Part-14.
  15. Streamlining Custom Made Form Wizard-15.
  16. Streamlining VBA Custom Made Report Wizard-16.
  17. Streamlining VBA External Files List in Hyperlinks-17
  18. Streamlining Events VBA 3D Text Wizard-18
  19. Streamlining Events VBA RGB Color Wizard-19
  20. Streamlining Events Numbers to Words-20
  21. Access Users Group(Europe) Presentation-21
  22. The Event Firing Mechanism of MS Access-22
  23. One TextBox and Three Wrapper Class Instances-23
  24. Streamlining Code Synchronized Floating Popup Form-24
  25. Streamlining Code Compacting/Repair Database-25
  26. Streamlining Code Remainder Popup Form-26
  27. Streamlining Code Editing Data in Zoom-in Control-27
  28. Streamlining Code Filter By Character and Sort-28
  29. Table Query Records in Collection Object-29
  30. Class for All Data Entry Editing Forms-30
  31. Wrapper Class Module Creation Wizard-31
  32. wrapper-class-template-wizard-v2
Share:

2 comments:

  1. I'm reading all the articles about Streamlining, and I find them very interesting and useful. Every chapter goes deeper and it's explained step by step so it's easy to follow. Thanks and good job.

    ReplyDelete

Comments subject to moderation before publishing.

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