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

Showing posts with label Form Instances. Show all posts
Showing posts with label Form Instances. Show all posts

WithEvents Ms-Access Class Module Tutorial

Object Event, Event Firing, Capturing. 

MS Access Forms and Reports each have their own Class Modules. Nearly all controls on a form—such as CommandButtons, TextBoxes, ComboBoxes, ListBoxes, and others—support event-driven programming, which is one of the key features that make a Microsoft Access Application a powerful Database Management System.

For example, the TextBox control provides several events, such as GotFocus, LostFocus, BeforeUpdate, and others. We typically write VBA code in these event procedures to perform tasks controlling data entry, validating data, or updating modified data.

Similarly, the OnClick event of a Command Button can be used to open forms or reports, or to run procedures or macros that perform specific tasks. Normally, code for such predefined events is written directly within the form’s or report’s class module.

This time, however, we will take a different approach — by redirecting event handling to an external location, instead of the normal procedure of writing Code directly in the Form's Class Module.

From MS Access’s perspective, these event procedures can be categorized into two groups: Built-in and User-Defined Events.

  1. Built-in events triggered by controls on a form or report can be captured in a stand-alone class module, allowing you to execute code there to perform any desired actions, rather than writing the code directly in the form or report’s class module.

  2. In addition, we can define our own custom events within form or report modules and capture them either in another form’s class module or in an independent class module object. The required code to handle the event can then be written in the target module to perform the necessary actions for the form. This approach only requires a single line of code in the built-in event procedure to transmit the event to the target location, where the actual handling logic can be implemented.

First, we will explore the second option — defining custom events — and learn how to invoke a custom event from one form and capture it in another form or a class module object as it occurs, then execute the required task there.

We are starting with custom events because they demonstrate all the fundamental elements of this powerful programming feature. This approach involves a few basic statements (listed below), their correct placement in different modules, and the proper naming of events in both the source and target modules, which is crucial for the process to work correctly.

The WithEvents, Event, and RaiseEvent Statements.

We will start with a simple example, but it is essential to understand the placement of the key elements of a custom event and how they work together in a synchronized manner.

--- form1 ---

Private WithEvents obj as Form_Form2

Private Sub obj_eventName(parameter) ‘Capture the Event, from Form2 in obj

‘write Code here

End Sub

--- Form2 ---

Public Event eventName(parameter) ‘Declare Event

RaiseEvent eventName(parameter) ‘Invoke the Event

Capturing built-in events, such as button clicks or ComboBox selections, in a class module object is much simpler than defining a custom event in one class module and capturing it from another.

Events and event trapping are strictly handled through class modules and cannot be implemented in standard modules. However, you can still call subroutines or functions in Class Modules from a Standard module.

Demo Run of User-Defined Events.

Let’s try an example using two simple forms — each containing a TextBox and a Command Button. This trial run will help you understand the basic steps involved in this procedure. The sample design of Form1 is shown below:

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

  2. Insert a Command Button on the Detail Section of the Form.

  3. Display the Property Sheet (F4) and change the Name Property Value to cmdOpen.

  4. Change the Caption Value to Open Form2.

  5. Insert a Label Control above the Command Button and change the Name Property Value to Label1 and the Caption to Label1.

  6. Change Form1’s Has Module Property value to Yes.

  7. Display the Module of Form1.

  8. Copy and paste the following Code into the Class Module of the Form and save the Form.

    Option Compare Database
    Option Explicit
    
    Private WithEvents frm As Form_Form2
    
    Private Sub cmdOpen_Click()
    Set frm = Form_Form2
    frm.Visible = True
    
    End Sub
    
    Private Sub frm_QtyUpdate(sQty As Single)
    Dim Msg As String
    
      If sQty < 1 Then
         Msg = "Negative Quantity " & sQty & " Invalid."
      ElseIf sQty > 5 Then
         Msg = "Invalid Order Quantity: " & sQty
      Else
         Msg = "Order Quantity: " & sQty & " Approved."
      End If
      MsgBox Msg, vbInformation, "frm_QtyUpdate()"
      
    End Sub
    
    Private Sub frm_formClose(txt As String)
       MsgBox "Form2 Closed", vbInformation, "farewell()"
       Me.Label1.Caption = txt
    End Sub
    
  9. Create a second Form with the name Form2 and open it in Design View.

  10. A sample image of Form2 is given below:

  11. Create a Text Box on the Form and change its Name property value to Qty.

  12. Change the child-label Caption to Order Qty (1-5):

  13. Create a Command Button below the Text Box and change the Name Property value to cmdClose and change the Caption Property value to Close.

  14. Display the Form’s VBA Module, Copy and Paste the following Code into the Module, and save the Form.

Option Compare Database
Option Explicit

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

Private Sub Qty_AfterUpdate()
  RaiseEvent QtyUpdate(Me!Qty)
End Sub

Private Sub cmdClose_Click()
  DoCmd.Close
End Sub

Private Sub Form_Unload(Cancel As Integer)
  RaiseEvent formClose("Form2 Closed")
End Sub

Form1 Class Module Code

Let us take a closer look at Form1’s Class Module Code.

  • The first line: Private WithEvents frm As 'Form_Form2' declares a Form Object Variable of Form2, enabled with the WithEvents capturing feature.  Events originating from the Form2 Class Module are captured here in the Form1 VBA Module, and the corresponding Subroutine is executed, depending on the Event Raised.

  • On the cmdOpen_Click event procedure, the Form2 object is instantiated in the frm Object Variable and made Form2 visible in the Database Window.

  • The Private Sub frm_QtyUpdate() Subroutine is executed when the Qty TextBox on Form2 is updated with a value, and from within the AfterUpdate Event of the TextBox, the RaiseEvent QtyUpdate() is executed if an invalid quantity value is entered.

  • The Private Sub frm_formClose() is executed when the Command Button on Form2 is clicked to close Form2.

Form2 Class Module Code

Now, let us go through the Form2 Module’s Code.

  • In the global declaration area of the Form2 Class Module, two Public Event Procedure names are declared, with parameters.

  • When some value is entered into the TextBox and the Tab Key is pressed, the AfterUpdate Event Procedure is run, and the QtyUpdate() Event is raised with the Statement RaiseEvent QtyUpdate(Me!Qty).  The Qty value is passed as a parameter to the QtyUpdate() Function.

  • The frm_QtyUpdate() Sub-routine runs from the Form1 Class Module and performs validation checks on the passed value and displays an appropriate message.

  • When the Command Button on Form2, with the Caption Close, is clicked, the formclose() Event is raised, a message is displayed, and the Label control on Form1 is updated with the same info.

  • The sequence of actions between Form1 and Form2 is illustrated in the diagram below. You can use this as a guide when experimenting with your own examples.

How does it work?

It works like a Radio Transmitter and Receiver, tuning to the frequency, like a setup.

The global declaration Public WithEvents frm as Form_Form2 on Form1’s VBA Module is, like a Radio-Receiver, states that whatever action is transmitted from Form2 (from the frm Property) will be received in Form1 and executed in the related Sub-routine.

In Form2’s Module at the global declaration section, you will find the statement Public Event QtyUpdate(mQty As Single). You may compare this statement with the transmission Frequency (or Event Name: QtyUpdate()) with data from the TextBox as the parameter.

The transmission starts only when you call the RaiseEvent Statement, and it fires the Event declared at the Class Module level of Form or Report, with parameter value (if defined). 

Example: RaiseEvent QtyUpdate(Me!Qty)

The same name QtyUpdate() is a Subroutine Name – not to declare as a Function  - (on Form1 Class Module, like we tune in to the same transmission frequency to receive the radio broadcast) where we write code to run a validation check on the passed data as a parameter and display a message based on the validity of the value passed from Qty textbox on Form2.

The subroutine name is always prefixed with Form2’s Class Module instance Property name: frm, and the subroutine header line is written as Private Sub frm_QtyUpdate(sQty As Single), to tune into the correct frequency of transmission.

The subroutine name (QtyUpdate) in the event declaration on Form2 must match the subroutine name on Form1. On Form1, the subroutine name will be prefixed with Form2’s instance variable and an underscore, for example: frm_QtyUpdate(sQty As Single), as described above.

Note: It means that the actual Code, for the Event Procedure declaration done on Form2 Class Module is written in Form1 Module, by addressing the subroutine directly with the object name (frm_) prefix.

Armed with the above background information, let us try out the Forms to see how they work.

The Demo Run.

  1. Open Form1.

  2. Click on the Command Button.  Form2 is instantiated in memory and made visible.

  3. If Form2 is overlapping Form1, then drag it to the right side so that both Forms remain side by side.

    On Form2, there is a text box named Qty (Quantity); the valid value range is 1 to 5. 

    Any value outside this range is invalid, and an appropriate error message is displayed.

  4. Enter a value in the text box and press the Tab Key. 

    The Text Box’s  AfterUpdate Event is run, and within this Event, the RaiseEvent QtyUpdate(Me!Qty) statement fires the Custom Event and passes the TextBox Value as the parameter.

    Public Sub frm_QtyUpdate(sQty as Single) Subroutine on Form1 Class Module runs and validates the parameter value and displays an appropriate message.

    Try this out by entering different values into the TextBox and pressing the Tab Key.

  5. When you are ready to close Form2, click on the Command Button.

The formClose() Event is fired, and a message is displayed. The Label control Caption on Form1 is updated with the same message stating that Form2 is closed.

I am sure you know how the whole thing works, and try something similar in your own way.  When in doubt, use this page as a reference point.

More on this next week.

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:

MS-Access Base Class and Derived Objects

Object Encapsulation.

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

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

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

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

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

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

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

The Volume Class: ClsVolume.

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

  1. Insert a new Class Module.

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

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

Option Compare Database
Option Explicit

Private p_Area As ClsArea
Private p_Height As Double

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

Private Sub Class_Terminate()
    Set p_Area = Nothing
End Sub

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

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

Public Function Volume() As Double

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

End Function

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

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

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

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

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

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

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

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

Volume = p_Area.Area * p_Height

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The ClsVolume Derived Class Module Code.

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

Option Compare Database
Option Explicit

Private p_Area As ClsArea
Private p_Height As Double

Private Sub Class_Initialize()

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

End Sub

Private Sub Class_Terminate()

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

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

Public Property Let dblHeight(ByVal dblNewValue As Double)

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

End Property

Public Function Volume() As Double

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

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

End Function

‘ClsArea Class Property Procedures and Method are exposed here

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

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

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

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

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

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

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

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

That’s a fair question. But the purpose here is to demonstrate how a base class object becomes part of the design of another Class Object. This pattern shows the power of reusability and encapsulation in VBA class design.

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

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

The Main Program that Uses the ClsVolume Class.

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

Public Sub TestVolume()
Dim vol As ClsVolume

Set vol = New ClsVolume

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

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

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

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

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

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

Warehouse      25            30            10            750           7500 

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

Validation Checks Performance Tests.

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

Let’s test them one at a time.

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

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

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

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

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

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

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

The Changed Code of Main Programs.

The changed Code for both Programs is given below:

Public Sub TestVolume()
Dim Vol As ClsVolume

Set Vol = New ClsVolume

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

Call CVolPrint(Vol)

Set Vol = Nothing

End Sub
Public Sub CVolPrint(volm As ClsVolume)

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

End Sub

Next week, we will build the Volume Class Object.

The Links of All Pages on this Series.

  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:

Opening Multiple Instances of Form in Memory

Introduction.

  1. Over the past few weeks, we have been learning about the dot (.) separator and the exclamation (!) symbol in VBA object references. Now, we will explore some interesting techniques with Forms in VBA, specifically:

    • How to call a function procedure embedded in a form’s module (class module) from the code outside that form?

    • How to open multiple instances of a single Microsoft Access Form in memory, each displaying different information?

    For example, the Screenshot below shows two instances of the Employees Form. The first instance is behind the second, displaying employee details for IDs 4 and 5. Click the image to enlarge for a clearer view.


    Calling the Form's Class Module Public Function.

  2. How to call a Function Procedure on the Form's Class Module, from outside the Form?

    Call the Function from a Standard Module,  from the Module of another form, or from the VBA Debug Window (Immediate Window).  The target form must be opened in memory to call the function procedure of the form from outside.

Function procedures in a form module are useful for avoiding code duplication. They can be called from subroutines in different parts of the same form, from a command button click, or from other event procedures within the form. A function procedure in a form module can perform calculations, validation checks, update information, or act as a standalone operation—such as the one we will use in our sample Employees Form.

All event procedures in a form module are automatically declared as Private Subroutines, and they begin and end with standard statements, as shown in the sample below. Any custom VBA code that performs specific tasks should be placed within this block:

Private Sub Command8_Click()
.
.
End Sub

A Private subroutine or function is limited in scope to the module in which it is declared and cannot be called from outside that module. Declaring procedures as private is essential to avoid name conflicts with procedures of the same name in other Class Modules or Standard Modules.

To call a function procedure from outside a Form, the Function must be declared as Public in the Form’s module.

To perform a trial run of the above trick, you need the Employees Table and a Form.

  1. Import the Employees Table from the Northwind sample database.

  2. Click on the Employees Table to select it.

  3. Click on Create Ribbon.

  4. Select the Form option and create a Form for Employees Table, in the format shown above.

  5. Save the Form with the name frmEmployees.

  6. Open the frmEmployees Form in Design View.  Set the Form Property 'Has Module' Value to Yes.

  7. Select the Design Menu and select VBA Code from the Tools button group to open the Form Module.

  8. Copy the following VBA code and paste it into the VBA Module of the Form.

    Public Function in Form Class Module.

    Public Function GetFirstName(ByVal EmpID As Integer) As String
    Dim rst As Recordset, crit As String
    Dim empCount As Integer
    
    'get total count of employees in the Table
    empCount = DCount("*", "Employees")
    
    'validate employee code
    If EmpID > 0 And EmpID <= empCount Then
    
        crit = "ID = " & EmpID
        Set rst = Me.RecordsetClone
        rst.FindFirst crit
        
        If Not rst.NoMatch Then
              
         	Me.Bookmark = rst.Bookmark
            GetFirstName = rst![First Name]
        End If
        
           rst.close
           Set rst = Nothing
        
      Else
        MsgBox "Valid Employee IDs: 1 to " & empCount
    End If
    
    End Function
    
  9. Save and Close the Form.

Notice that the starting line of the above function is declared as Public.

The function GetFirstName() accepts EmployeeID as a parameter, locates the corresponding record on the form, and makes that record the current record. If the search is successful, the function returns the employee’s first name to the calling procedure. If the search fails, it displays a warning message indicating that the EmployeeID provided is not within the range of IDs in the Employees table.

Next, we need a program in a Standard Module that calls the GetFirstName() function from the frmEmployees form module. This program will also demonstrate how to create multiple instances of a Microsoft Access form, allowing you to open them in memory and access their properties, methods, or control contents independently.

  1. Open VBA Editing Window (Alt+F11).
  2. Select the Module option from the Insert Menu and add a new Standard Module.

  3. Copy and paste the following VBA Function code into the new Module.

    Call GetFirstName() from the Standard Module.

    Public Function frmInstanceTest()
    
          Dim frm As New Form_frmEmployees '1st Form instance
    
          Dim frm2 As New Form_frmEmployees '2nd instance declaration
    
          Dim Name1 As String, Name2 As String
    
      frm.Visible = True 'make the instance visible in Application Window
      frm2.Visible = True '2nd instance visible
    
      Name1 = frm.GetFirstName(4) 'Call the GetFirstName of Employee ID 4
      
      Name2 = frm2.GetFirstName(5) ''Call the GetFirstName of Employee ID 5
    
    'pause execution of this code to view
    'the Employees Form instances in Application Window.
    
    Stop
    
      MsgBox "Employees " & Name1 & ", " & Name2
      
    End Function

Trial Run of Function frmInstanceTest()

Let us run the code and view the result in the Application Window.

  1. Click somewhere within the body of the frmInstanceTest() function and press the F5 key to run the code.

    The program will pause at the Stop statement, allowing you to view the Access application window, where multiple instances of the frmEmployees form are open in Normal View, with one instance overlapping the other.

  2. Press Alt+F11 to display the Application Window, where both instances of the Form are visible, the second form overlapping the first one.

  3. Click and hold the Title Bar area at the top and drag it to the right to make part of the form behind visible.

    Observe the employee records on both forms—they are different: one displays Employee ID 4, and the other displays Employee ID 5. Notice the title bar of both forms; they both show the same form name, frmEmployees. Now, return to the program and continue running the code to complete the task.

  4. Press Alt+F11 again to switch back to the VBA Window and press the F5 key one more time to continue executing the remaining lines of code.

    The Message Box appears in the Application Window, displaying the Employee names Mariya and Steven together.  When you click the OK MsgBox Button, the frmEmployee form instances disappear from the Application Window.

  5. Click the OK button on the MsgBox.

Note: Pay special attention to the Stop statement placed above the MsgBox() function at the end of the code. The Stop statement halts VBA execution at that point. While it is typically used during debugging to trace logical errors or make corrections, here it serves a different purpose: it pauses the program so that we can switch to the Access application window and view both open instances of the frmEmployees form.

If we relied on the MsgBox() function alone, the code would still pause, but the message box would remain on top of the forms. This would prevent us from dragging the front form aside to view the one behind it. By using the Stop statement, we gain the flexibility to examine both instances directly in the application window.

If we don't create a pause in the code execution, both instances of the form are closed immediately when the program ends.  In that case, we will not be able to view the forms.  Since it is a trial run, we would like to know what is happening in the program. It is not necessary to make the Form instances visible before calling the Function GetFirstName ().

The VBA Code Line by Line.

Let’s take a closer look at each line of code in the frmInstanceTest() function. While brief hints are already included within the code, explaining a few points in detail will make them clearer. We’ll begin with the first two Dim statements.

Dim frm As New Form_frmEmployees

Dim frm2 As New Form_frmEmployees

In the above Dim statement, you can see that the New keyword is followed by the object reference. The object name is our frmEmployees prefixed by the direct Object Class name FORM, followed by an underscore character separation (Form_) to the frmEmployees Form name (Form_frmEmployees).  These Dim statements themselves open two instances of the frmEmployees in memory.   Form instances opened in this way are not immediately visible in the Application Window.  If we need them to be visible, then make them visible with another statement.

Next, we declared two String Variables: Name1 & Name2 to hold the names returned by the GetFirstName() method.

Next two statements: frm.Visible=True and frm2.Visible=True, makes both instances of the frmEmployees Form visible in the Application Window, for information purposes only.

In the next two lines of code, we are calling the GetFirstName() method of the first and second instances of the frmEmployees to search, find, and return the First Names of employee codes 4 and 5.

Default Instance and Other Instances.

The default instance of a Form is opened in the following manner in programs for accessing their Properties, Methods, and Controls.  These styles of statements are always used to open a form in programs. The default instance of the Form will be automatically visible in the Application Window.

Dim frm as Form 'define a Form class object
DoCmd.OpenForm "frmEmployees", vbNormal 'open frmEmployees in Memory
Set frm3 = Forms!frmEmployees ' attach it to the frm3 object

Assume that we have opened frm & frm2 instances first in memory before the default instance through the above code.  How do we address those three instances in a program to do something?  Let us forget about the frm, frm2, and frm3 object references, for now, we will go with the straight method, like the one given below:

name3 = Forms![frmEmployees].GetFirstName(5) 'target form in memory is the default instance
'OR
name3 = Forms("frmEmployees").GetFirstName(5) 
'OR
name3 = Forms(2).GetFirstName(5) ' this is the third and default instance

The other two instances in memory cannot be referenced like the first two default methods, using the name of the form. You have to use only the index number of the Forms collection to address the other two instances.

name1 = Forms(0).GetFirstName(3)
name2 = Forms(1).GetFirstName(6)

A Shortcut Method.

There is a shortcut method you can use to run the GetFirstName() Method of the frmEmployees Form from the debug window (Ctrl+G).  Type the following command in the Debug Window and press Enter Key:

? form_frmEmployees.GetFirstName(5)
'Result: Steven
'OR
X = form_frmEmployees.GetFirstName(5) 

When we execute the above command, it opens an instance of the frmEmployees form in memory and calls the GetFirstName() function with Employee Code 5 as the parameter. The GetFirstName() function searches for the record, finds it, returns the employee’s first name to the calling program, and then closes the form.

Tip: Even after the form is closed, the current record—Employee ID 5 in this case—remains the active record of the closed form’s last session.

You can verify this behavior by typing the following shortcut command in the Debug Window and pressing Enter:

? Forms!frmEmployees!EmployeeID

? form_frmEmployees![First Name]

'Result: Steven

A Fancy Approach.

In the above command, we didn't run the GetFirstName() method, but the current record's First Name field value is printed. If you want to get a little fancy with the command, then try this by typing it in the debug window and pressing the Enter Key:

MsgBox "First Name: " & form_frmEmployees.GetFirstName(8)
'OR
MsgBox "First Name: " & form_frmEmployees![First Name]

Or try the above command from a Command Button Click Event Procedure from another Form's Module, as given below.

Private Sub Command8_Click()
  MsgBox "First Name: " & Form_frmEmployees.GetFirstName(8)

End Sub
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