Learn Microsoft Access Advanced Programming Techniques, Tips and Tricks.

Streamlining Form Module Code Part Two

Understanding Form Controls Event.

I hope you enjoyed last week's Article and demonstration of user-defined custom Events in the Form's Class Module.  We witnessed how Events are Defined, Raised, or fired on the same Form Module. The Raised Events are captured by another open Form's Class Module and trigger the related custom Event Procedure Code. I understand that you likely have many questions regarding this topic of Events and I look forward to exploring it further to gain deeper understanding.

Typically when designing an Access Form, we add various objects, such as TextBoxes, Command Buttons, and ComboBoxes, each with their own pre-defined Events and corresponding event-raising actions. These events are captured by their respective Event Procedures and execute VBA code to accomplish specific task.

In the past, we may have utilized the mechanics of adding objects to an Access form and used their corresponding events without fully understanding how they work. With VBA coding in the form module, we may have become proficient with these features through trial-and-error practice. However, we may not have had the opportunity to delve deeper into the underlying mechanisms and truly understand the inner workings of these processes.

When discussing events on a form, it is important to keep in mind the keywords Event, RaiseEvent, and WithEvents. These keywords are utilized in the following examples, as well as in forthcoming ones.

  1. Defines an Event with the Keyword Event: Public Event QtyUpdate()
  2. Fire the Event with the Keyword RaiseEvent: RaiseEvent QtyUpdate()

  3. Capture the Event in another Form's Class Module: Public WithEvents ofrm as Form. The User-Defined Event cannot be captured in the same Form it is defined?

    The resultant Event Procedure of ofrm_QtyUpdate() Event in the target Form Module:

    Private Sub ofrm_QtyUpdate()
       'VBA Code
    End Sub

User-Defined Event Example-2.

To better understand the usage of the aforementioned keywords, let's consider an example using user-defined events. Suppose we have a textbox that allows users to enter a quantity with certain limitations. We want to pass the entered value to another form via a user-defined event for validation and receive a suitable message in response.

To accomplish this, we can define a public event QtyUpdate() using the Event keyword. We can then raise this event using the RaiseEvent keyword and pass the entered value as an argument. The event can be captured in the class module of another open form using WithEvents and validated using the corresponding event procedure code. Any suitable message can then be displayed to the user based on the validation result.

To put our events into action, let's create two simple forms. In brief, we will define the events in the first form and run the corresponding user event procedure code in the second form's class module.

The events will be defined in the first form, while the second form's class module will capture the events and execute the user event procedure code. By doing so, we can effectively utilize user-defined events to pass values and execute tasks between different forms in Microsoft Access.

  1. Open your Database.

  2. Create a new Form.

  3. Insert a Textbox.

  4. Display the Property Sheet of the Textbox.

  5. Change the Name Property value: OrderQty (stands for Quantity).

  6. Change the child Label Caption value: Quantity  (1 - 5 only).

  7. Insert a Command Button below the Textbox and change its Name Property value to cmdClose.

  8. Change its Caption property to 'Close Form'.

  9. Add another Command Button below the earlier one and change its Name Property Value to cmdOpen.

  10. Change the Caption Property value to 'Open Event Capture Form'

  11. Display the Form's Property Sheet. Set the Record Selectors Property Value No.

  12. Set Navigation Buttons to No.

  13. Set Scroll Bars to Neither.

  14. Right-Click on the TextBox and select Properties from the displayed Menu.

    Event Announcer Form
  15. Select AfterUpdate [Event Procedure] option on the TextBox Property and Click on the Build (. . .) Button to open the VBA Module.

  16. Copy and Paste the following VBA Code into the Form's Module, overwriting existing lines, if any:

    Option Explicit
    
    'User-Defined Events
    'Captures in Second Form Module.
    
    Public Event QtyUpdate(mqty As Single)
    Public Event formClose(txt As String)
    
    Private Sub cmdOpen_Click()
        DoCmd.OpenForm "frmUDCapture", acNormal
    End Sub
    
    Private Sub OrderQty_AfterUpdate()
    
    'Announce the Event and pass the OrderQty as Prameter
      RaiseEvent QtyUpdate(Me!OrderQty)
      
    End Sub
    
    Private Sub cmdClose_Click()
    'Announce the Event and pass the Text as Parameter
      RaiseEvent formClose("Close the Form?")
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
      DoCmd.Close acForm, "frmUDCapture"
    End Sub
    
     
  17. Save the Form with the Name frmUserDefined.

User-Defined Events.

In the global declaration area of the form's class module, we have defined two user-defined events that closely resemble functions.

Public Event QtyUpdate(mqty As Single)
Public Event formClose(txt As String)

In addition to user-defined events, Access has built-in events that also resemble functions, such as Text0_Exit(Cancel As Integer), Form_Unload(Cancel As Integer), and Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single).

The user-defined event declarations both begin with the keywords Public Event, followed by the event name and any associated parameters enclosed in parentheses. It is important to note that the event name should not contain an underscore character (e.g., Qty_Update). Additionally, the declaration must have a public scope.

The first user-defined event is designed to validate the contents of the OrderQty textbox whenever it is updated on the form. The second event is triggered by the first command button to close the form and passes a text message as a parameter.

After entering a numeric value into the OrderQty TextBox and pressing the enter key, the OrderQty_AfterUpdate event procedure of the textbox is executed as usual. Within this event procedure, the RaiseEvent QtyUpdate(Me!OrderQty) statement is executed, which triggers the first user-defined event and passes the value of the OrderQty textbox as a parameter.

Private Sub OrderQty_AfterUpdate()
'RaiseEvent and pass the OrderQty as a parameter
  RaiseEvent QtyUpdate(Me!OrderQty)
  
End Sub

In Access, we typically write event procedures for objects such as TextBoxes and Command Buttons in the form module. These objects are part of the Access system and have their own built-in events and mechanisms for capturing those events. We write event procedures for these Objects in the class module of the form where they reside. Later, we will review how this process works and why it differs from the approach we take with user-defined events. You may have questions about these differences, and we will explore them in more detail shortly.

When you declare the Form1 object Instance with the WithEvents keyword in the form2 module, you can capture the events Raised by the Form1-based object in the Event Procedures defined in the Form2 module.  Instead of Form2 Class Module, it can be in a stand-alone Class Module too. This is because the WithEvents keyword allows the module/class to receive notifications about events raised by the form object, and to respond to them by running the corresponding event procedures.

The user-defined events are not related to any object-based events like the built-in events of a TextBox. The only mechanism for raising a user-defined event is to explicitly call the RaiseEvent statement in the code, as we did in the example.

Additionally, a user-defined event declaration doesn't have any inherent mechanism of its own like a TextBox. It simply defines the event's name, any parameters, it may have, and its public accessibility. It's up to the programmer to implement the code that raises the event and the code that handles the event when it's raised.

It's also worth noting that a user-defined event can be raised only from the Class Module of Form or from a stand-alone Class Module. However, as I mentioned earlier, in order to capture the event in another module, that module must use the WithEvents keyword to declare a reference to the module that will Raise the event.

The RaiseEvent statement within the OrderQty_AfterUpdate() event procedure announces the QtyUpdate() user-defined event and passes the OrderQty value as a parameter. This QtyUpdate() event is then captured in the second form's module, where the event procedure defined in the second form's module is executed, which validates the OrderQty value.

The cmdClose_Click() event procedure in the frmUserDefined Form raises the formClose() event with a message text as the parameter. Then, the second Form's Module captures this event and shows the message text to the User to determine whether to close the Form frmUserDefined or not, based on the User's responses.

This is a simple example of how user-defined events can be used to communicate between different Forms or Class Modules in VBA.

Private Sub cmdClose_Click()
'Announce the Event and pass the Text as Parameter
  RaiseEvent formClose("Close the Form?")
End Sub

Private Sub Form_Unload(Cancel As Integer)
  DoCmd.Close acForm, "frmUDCapture"
End Sub

The Form_Unload() event of the frmUserDefined form is triggered first, as the form is closed, and the frmUDCapture Form is closed first. 

 The Form_Close() event is triggered next after the frmUDCapture form is closed and closes the frmUserDefined Form in the cmdClose_Click() event procedure. 

The User-Defined Event Capturing Form.

  1. Create a new Form and open it in Design View.

  2. Insert two labels as shown in the Image of the Form's Design-view given below.

    Event Capturing Form
  3. Change the first label caption to EVENT MESSAGES.

  4. Select the second label and display its Property Sheet.

  5. Change the Name Property Value to Label2, if it is different.

  6. Change the Caption Property Value to Event.

  7. Display the Form's Property Sheet. Set the Record Selectors Property Value: No.

  8. Set Navigation Buttons to No.

  9. Set Scroll Bars to Neither.

  10. Change the Detail Section Area of the Form as small as shown in the above Image.

  11. Display the Form's VBA Module.

  12. Copy the following VBA Code and Paste it over the existing lines, if any, in the Module.

    Option Compare Database
    Option Explicit
    
    Public WithEvents ofrm As Form_frmUserDefined
    
    Private Sub Form_Load()
    On Error Resume Next
    
    Set ofrm = Forms("frmUserDefined")
    End Sub Private Sub ofrm_QtyUpdate(sQty As Single) Dim Msg As String If Nz(sQty, 0) < 1 Or Nz(sQty, 0) > 5 Then Msg = "Valid Qty Range: 1 - 5 Only" Else Msg = "Order Qty: " & sQty & " Approved." End If Me.Label2.Caption = Msg MsgBox Msg, vbInformation, "QtyUpdate()" End Sub Private Sub ofrm_formClose(txt As String) Me.Label2.Caption = txt If MsgBox(txt, vbYesNo, "FormClose()") = vbYes Then DoCmd.Close acForm, ofrm.Name Else Me.Label2.Caption = "Close Action = Cancelled." End If End Sub
  13. Save the Form with the Name frmUDCapture and Close it.

Event Capturing Form Module VBA Code Review.

Public WithEvents ofrm As Form_frmUserDefined

Private Sub Form_Load()
	On Error Resume Next
	Set ofrm = Forms("frmUserDefined")
End Sub

  1. The line of code Public WithEvents ofrm As Form_frmUserDefined is a crucial global declaration in the second Form's Class Module. It declares a new instance of the first Form, frmUserDefined, as an Event Listener Object variable named ofrm.

    This is important because it enables the second Form to capture and respond to events that are raised by the first Form. By the above declaration, the Listener Object in the second Form can intercept and handle user-defined events raised by the frmUserDefined.

    Without this declaration, the second Form would be unable to capture and respond to events raised by the first Form, and the user-defined event functionality would not work as intended.

    To tell a long story short, we are declaring an Instance of the frmUserDefined Form's  Class Module Object Variable name ofrm. The Form_  prefix to the Form Name indicates that we are referring to the Form's Class Module and creates an Instance of the Class Module.

    The Class Module Object Instance will be assigned to the declared ofrm Object Variable through the frmUDCapture Form's Form_Load() Event Procedure.

    The WithEvents keyword declares the ofrm as a Listener Object to Capture the Events Raised from its original Class Module-based user-defined Events.

    Note: Remember the WithEvents classification can be made only when an instance of any Object, like Form, TextBox, ComboBox, etc., is created.

    The general rule of Object Instance creation as a Listener Property is that:

    • The WithEvents keyword is used in the parent Class Module to create a connection between the object and its events. This connection allows the Class Module to capture events fired by the object and call the corresponding event procedure in response.

      The event procedure name must start with the object instance name, followed by an underscore, and then the event name. This naming convention ensures that Access can properly map the event to the event procedure.

      For example, if we have a TextBox object named "txtName" and we want to capture its LostFocus event, we would declare it in the parent Class Module like this:

      Private WithEvents txtName As Access.TextBox
      
      Private Sub txtName_LostFocus()
          ' Do something when the txtName TextBox loses focus
      End Sub
      

      In this example, the object instance name "txtName" is prefixed to the event name "LostFocus" to create the event procedure name "txtName_LostFocus".

    • To capture the user-defined event QtyUpdate() from the frmUserDefined form module, the parent form module instance should be declared with the WithEvents keyword. This allows the frmUDCapture form to listen to the event transmission from the frmUserDefined form and execute the corresponding event procedure.

    • When you add a TextBox (or any other control) to a form in Access, the Access system creates an instance of the TextBox object and adds it as a control to the form. The Access system also creates a property for the TextBox in the form's module, which is declared with the WithEvents keyword. The name of this property is the default name of the TextBox control with "As TextBox" appended to it. For example, if the default name of the TextBox control is "Text0", the property declaration in the form's module will be:

      Private WithEvents Text0 As TextBox

      You can change the name of the TextBox control to something more meaningful, and the name of the property declaration in the form's module will automatically update to reflect the new name.

    • Check the last two lines at the bottom of the Object Browser image below and see how the TextBox Object Instance name OrderQty declaration appears on our Form frmUserDefined. Check the declaration starting with the Keyword WithEvents OrderQty As TextBox.

      To inspect the Property of a TextBox in the Object Browser or any other control on the Form, you can follow these steps:

      1. Right-Click on VBA Editing Window and select Object Browser Option, or Click on the Object Browser Button from the Toolbar above to Open the Object Browser.
      2. In the Object Browser, select the Database Name in the <All Libraries> Control.
      3. The Object Browser will display the Properties and Methods of the selected control, including the declaration for the control's Object Instance.
      4. Select the Form Name Form_frmUserDefined from the left panel of the Browser Window.
      5. The Form's Property List appears in alphabetical order in the right-side Panel.
      6. Scroll down and find the OrderQty Property of the TextBox and select it.
      7. For example, the declaration for a TextBox with the name OrderQty on a form named frmUserDefined would look like this:

        WithEvents OrderQty As TextBox
        

        At the bottom panel of the Object Browser in the detail view of the selected Property.

      8. You may look for the user-defined Event QtyUpdate() declaration and how it appears in the right panel with a lightning icon and check the details view of the event declaration in the bottom panel.
    • Both user-defined events and built-in events of Access objects require an event procedure to be created on their parent VBA module. For built-in events, the event procedure name will be based on the name of the Access object and the event name. For example, if the Access object is a Textbox named "Text0" and the Event is "LostFocus", the event procedure name will be "Sub Text0_LostFocus()".

      For user-defined events, the event procedure name will be based on the name of the event and the parent VBA module name. For example, if the user-defined event is named "QtyUpdate" and the parent Form VBA module Instance is named "objFrm", the event procedure name will be "Sub objFrm_QtyUpdate()".

    • The LostFocus event is an inbuilt event of the TextBox control in Access. When you add a TextBox control to a form, Access creates an instance of the TextBox object and assigns it a default name such as Text0. This TextBox instance is then declared as a property of the form, with the WithEvents keyword, which allows it to capture and handle its inherent events.

      As I mentioned, the Text0 object's parent object is the class module of the form, which means that the event handling code in the LostFocus event (or any other inbuilt event) should be written in the form's class module. The naming convention for event procedures for controls is that they start with the name of the control instance, followed by an underscore, and then the name of the event. In the case of the TextBox control named Text0, the event procedure for the LostFocus event would be named Sub Text0_LostFocus().

      Several Textbox object instances can be created in the same Form with appropriate names, and their Event Subroutines are written in the same Form's Class Module with TextBox names as the prefix. And this is what we do normally on Form Module.

    • The QtyUpdate() event is a user-defined event and is not part of any built-in object like the LostFocus event of a TextBox. It is defined in the Class Module of frmUserDefined Form and the parent object of the event is the form itself.

      In the frmUDCapture form, we have declared an instance of the frmUserDefined form as a listener object using the WithEvents keyword. Since the instance is named "ofrm", the event procedure for the QtyUpdate() event will have the format "Sub ofrm_QtyUpdate()". This allows the frmUDCapture form to capture and respond to the QtyUpdate() event raised by the frmUserDefined form.

    The statement Set ofrm = Forms("frmUserDefined") statement within the Form_Load() Event Procedure of the frmUDCaptured Form Module, if the Form is in an open state, then assigns it to the ofrm Class Module object. If this form is not in the open state, then it will run into Error.  The On Error Resume Next statement will suppress the error message, but the Event capturing will not work as expected. 

    Ensure that you open the frmUserDefined Form first, then the second form frmUDCapture next.

  2. We enter some Numeric Quantity Value into the OrderQty Textbox on the Form frmUserDefined and press the Enter Key to complete the data entry. The RaiseEvent QtyUpdate(Me!OrderQty) is fired from within the OrderQty_AfterUpdate() Event Procedure.

  3. This Event is captured in the frmUDCapture Form's Event Procedure Subroutine Code given below: 

    Private Sub ofrm_QtyUpdate(sQty As Single)
    Dim Msg As String
    
      If Nz(sQty, 0) < 1 Or Nz(sQty, 0) > 5 Then
         Msg = "Valid Qty Range: 1 - 5 Only"
      Else
         Msg = "Order Qty: " & sQty & " Approved."
      End If
      
      Me.Label1.Caption = Msg
      
      MsgBox Msg, vbInformation, "QtyUpdate()"
    
    End Sub
    
  4. The Valid value Range accepted in the TextBox is 1 to 5 only. Normally we write this validation check procedure in the same Form Module Event Subroutine only. Here, we are running the validity check in the second Form Module, and an appropriate message is written on the Msg String Variable and displayed in the MsgBox. At the same time, it will update the same message text in the caption of Label Control on the second Form also, to view the message after closing the Message Box.

  5. Check the Event Subroutine line: Private Sub ofrm_QtyUpdate(sQty As Single)

    The frmUserDefined Form's Class Module Object Instance name ofrm is used as the Subroutine name prefix combined with the User-Defined Event name QtyUpdate(), both separated with an underscore character. 

  6. Similarly, the FormClose() Event is captured and runs the Private Sub ofrm_formClose(txt As String) Event Procedure and it displays a message to the User, whether to close the Form frmUserDefined (first form) now, or not. If the response is Yes then it closes the First Form.

    Private Sub ofrm_formClose(txt As String)
    Me.Label2.Caption = txt
    
    If MsgBox(txt, vbYesNo, "FormClose()") = vbYes Then
      DoCmd.Close acForm, ofrm.Name
    Else
      Me.Label2.Caption = "Close Action = Cancelled."
    End If
    
    End Sub 
  7. In the formClose() Event Procedure header line you can see that the ofrm object prefix is appearing. In this subroutine, the parameter text is shown in the message box and the user responds whether the frmUserDefined Form (the first Form) is to be closed now or not.  The Message text is displayed from the second Form's Event Procedure. If the response is Yes then it runs the Code to close the first Form.

    When we issue Docmd.OpenForm  "FormName" Command, a series of Events takes place in a certain order, Form Open, Form Load, Form Current in that sequence. Similarly, when we run the DoCmd.Close acForm, "FormName"the Form_Unload Event runs first before the Form_Close event, if both events are run in VBA.  

  8. So when we run the above Form_Close Event Procedure to close the first Form, the first Form prepares to close down. Since we have written the Form_Unload Event Procedure in the first Form, to Close the second Form, it runs first and closes the second Form, before closing the first Form.

The User-Defined Events Trajectory View.

Events Trajectory View.

From the above graphical image, we can easily see at a glance how the user-defined Event is declared with the Event Keyword, how the Events are Announced with the RaiseEvent Keyword, and how the Event Listener is declared with the Keyword WithEvents that Captures the announced Event, identify it with the Event Procedure, and executes the correct Event Procedure Code. 

If we learn the inner workings of the Events and Event Procedure we can write Event Procedures of one Form in another Form's Class module, or Event Procedures can be in stand-alone Class Modules, rather than writing all the Event Procedures on the same Form Module. We are really heading for that.  But before touching the tricks at that level, we need to know a few basics of Event handling on Forms. How we could do things differently on Form, other than the traditional method we follow now.

Once we know how these tricks work together, our target is to take all the VBA codes out of the Form Module and organize them in such a way that Coding becomes very easy.  We will use the Forms for Visual Designing only. 

Besides that, once we streamline and adopt the new Coding procedure, we can easily take the backbone of the new procedure to other Projects and coding will become easier, enabling faster completion of new Projects.

Download 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
Share:

No comments:

Post a Comment

Comments subject to moderation before publishing.

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 Android App 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