Learn Microsoft Access Advanced Programming Techniques, Tips and Tricks.

Streamlining Form Module Code Part 14

All Frequently Used Controls on a Form.

We have learned how to capture events fired by Form or Report controls and handle them through Event Subroutines in their respective Wrapper Classes. The control Wrapper Class Instances created are assigned with the References of the actual controls on the Form, allowing us to interact with them seamlessly. Beyond event handling, any other control on the Form can also be accessed from the Wrapper Class to read or update its values. This flexibility opens the door to creative features such as running animations, simulating a digital clock on the Form, implementing a countdown timer before closing the Form, and much more. 

For such activities, every Wrapper Class includes a Form object instance, to which the active Form’s reference is assigned. This allows the Wrapper Class to interact directly with the Form. When you need to handle events such as the Form_Timer() Event or the MouseMove Event on the Form’s Detail Section, the Form instance must be declared with the WithEvents keyword inside the Wrapper Class. Since the Form instance is always included, any of the above-mentioned features can be triggered from within any control’s Wrapper Class. However, if capturing Form-level events is not required, the instance can be declared without WithEvents.

The Form Detail Section declaration is as given below:

Private frm as Access.Form
Private WithEvents SecDetail As Access.Section

Set SecDetail = frm.Section(acDetail)
	SecDetail.OnMouseMove = "[Event Procedure]"

Report Detail Section Declaration and Reference assignments are like the following:

Private WithEvents Rpt as Access.Report
Private WithEvents RptSec as Access.[_SectionInReport]

Set RptSec = Rpt.Section(acDetail)

By moving Event Subroutine VBA Code out of the Form and Report modules and placing it in standalone Class Modules, we can work with the code independently while keeping the design surface clean. The Collection object plays a key role here, as it allows multiple instances of the same control type (such as TextBox controls on a Form) to remain in memory, each enabled with the required Events. This approach keeps the Form design process separate from the coding task, making both development and maintenance more efficient.

Episode Seven is a classic example that demonstrates the strength of this new approach—shifting Form/Report module code into standalone Class Modules. With this method, repetitive manual coding is greatly reduced. For instance, a single pair of GotFocus() and LostFocus() routines can manage any number of TextBoxes on a Form, without writing separate code for each one. Likewise, if ten TextBoxes require AfterUpdate() Event Subroutines, each with different validation rules, all of them can be handled within a single AfterUpdate() procedure in the standalone Class Module. This principle is not limited to TextBoxes—it applies uniformly to all types of controls on the Form.

Event Procedure code becomes better organized when placed in Wrapper Classes, making it easier to maintain and debug—without interfering with the Form’s design.

Private Sub Txt_AfterUpdate()
  Select Case Txt.Name
  	 Case "Quantity"
     	'Code
     Case "UnitPrice"
     	'Code
     Case "TaxRate"
     	'Code
     Case . . .
     
  End Select
End Sub

If any TextBox’s AfterUpdate event code needs modification, or if a new TextBox requires an AfterUpdate event subroutine, there is no need to open the Form in Design View, locate the TextBox control, open the Property Sheet, find the event property, and then click the Build… button to access the event procedure. Instead, we can simply open the Class Module directly and make the necessary changes. Compare this with the traditional approach, where developers often waste considerable time repeating the same steps for each control event, sometimes multiple times for the same control.

I am confident you will recognize the difference once you adopt this new coding approach. While it may take a little time to become familiar with the concept—especially if you are new to VBA development—the benefits in efficiency and maintainability are well worth the effort.

The Form shown below incorporates several controls that were designed in earlier episodes, with their corresponding Wrapper Class Modules imported to handle the control event procedures. In addition, animations such as scrolling text, a digital clock, and a countdown timer (displayed before the form closes) have been implemented within the Class Module to run seamlessly on the Form.

Everything you can do in a Form Module can also be achieved from a standalone Class Module. The real advantage comes when you shift your mindset to this approach—working differently, more effortlessly, and in a well-organized manner, with the added benefit of reusing your VBA code across projects, often with little or no modification. Once you experience this, the difference becomes clear.

Anything attempted for the first time feels difficult—that’s how most of us began our journey with VBA coding, spending countless minutes or even hours solving problems through trial and error. I retired from service 13 years ago, yet I continue to learn new things every day and share my experiences with others. Learning never truly stops; in fact, it becomes even more meaningful when we pass it on to others.

Technology is advancing at an incredible pace, and it is inspiring to see children as young as 10 to 15 years old already practicing coding and building apps. What once took us years to discover, they now explore at such an early age—truly fortunate learners of this generation!

I was first introduced to computer-related automation at the age of 27, during my service in the Indian Navy Defence Establishment. My initial exposure came through the Key Punch Machine, where I worked on panel wiring to automate tasks such as skipping or duplicating specific columns of an 80-column punch card—the standard input medium for programs and data at that time.

The mainframe system we used was an ICL 1901, equipped with disk drives, and the programming languages of choice were COBOL and FORTRAN. I even managed to learn a bit of COBOL back then, in 1975, which became my earliest step into the world of programming. 

If you need some Class Module beginner lessons, they are available starting with this Link: MS-Access Class Modules and VBA series of a few Articles.

The Demo Database With All Frequently Used Controls.

The attached sample Demo Database contains the following Form; the download link is available at the end of this page, and the main Form (frmControls_All) Image is given below.

On the top-left side, the form features a TabControl-based menu designed with three layers of options, each hosted on separate Tab Pages. To achieve a clean interface, the Tab Page style is hidden by setting its property to None. Navigation between these menu pages is handled through dedicated command button clicks. Each button triggers a TabPage Change event, which in turn displays a message showing the corresponding Page Index number.

The three available menus are Tables, Forms, and Reports. The TabControl, along with its associated menu selection command buttons, was copied from the earlier episode’s form and pasted into this one. Some menu options were modified to replace them with other Forms and Reports introduced in previous episodes. The corresponding Class Modules were also imported into this database to ensure proper execution of these features.

Imported Class Modules of TabControl and the Menu changing Command Buttons.

The following Class Modules were imported into this Database, from an earlier episode, to run the Menu options with a few changes:

TabLst_Object_Init
TabLst_CmdButton
TabLst_ListBox
TabLst_TabCtl

The Tab Page with the name TABLES has three options: Employees, Orders, and Customer Tables. Double-click on an Item to open it. Before opening the Table, a Female Voice will announce the Table Name before it appears on the Screen.

Employees and Orders Form.

On the FORMS TabPage, the first option is the Employees Form with an embedded Orders SubForm. In the footer section of the Employees Form, you will find a Search and Find TextBox that allows you to look up records using the EmployeeID as the search key. When a search is performed, a label will briefly flash for a few seconds to indicate whether the search was successful or not. For testing, try entering an EmployeeID greater than 9—this will demonstrate the “not found” result.

Note: You may try to implement the Female Voice to announce the success or failure of the search operation.

The above Form and its VBA Codes are running from the following Class Modules, with the Emp Prefixes:

EmpObject_Init
EmpCmdButton
EmpTextBox
EmpCombo

The Employee-wise Orders Freight Sales Analysis.

The second option on the Forms Menu is the Employee-wise Orders Freight-Sales Analysis Form (frm_OptionGroup2), which includes a Graph Chart for visual analysis. This form features an Option Group Control with three selectable options:

  1. Highest Freight Sales Value

  2. Lowest Freight Sales Value

  3. Total Freight Sales Value

Based on the selected option, the corresponding result is displayed in a TextBox, accompanied by an animated label caption—a scrolling text that moves from right to left for added visual effect.

The above form is powered by two standalone Class Modules, as shown below. Both the Form and the Class Modules have been imported from one of the earlier episodes.

Opt_Object_Init
Opt_Frame2 

The Option Group Subroutine VBA code is executed from the Opt_Frame2 Class Module. The Employee ComboBox, the Freight Value display TextBox, and the Command Button Click Event Subroutines are handled through the Opt_Object_Init Class Module. Since each of these controls has only a single instance on the form, separate Wrapper Classes were not created for them. The Graph Chart on the Form.

The Graph Chart’s source data comes from the OrderSummaryQ2 query, which in turn is based on the OrderDetailQ2 query. The OrderDetailQ2 query filters data from the Orders table using the EmployeeID selected in the cboEmp ComboBox. A hidden TextBox named EID contains the expression =[cboEmp], which copies the EmployeeID selected in cboEmp. This value is then used in the Link Master Field property of the Graph Chart to dynamically update the chart whenever a different EmployeeID is chosen from the ComboBox.

The third TabControl page contains three report options:

  1. Employee Records

  2. Students Exam – Failed Cases (highlighted)

  3. Students Passed/Failed Listing (from the same report)

The last two reports, along with their corresponding Class Modules, have been imported into this database from earlier episodes. The following stand-alone Class Modules handle the Event Subroutine VBA code for these reports:

ClsStudentHighlight0
ClsStudentsList

The ClsStudentHighlight0 Class Module runs the Detail_Print Event Subroutine Code for the Report StudentsHighlight_Class0. 

The Report Image is given below:

The ClsStudentsList Class Module runs the Detail_Format() and Report_Page() event subroutines. Additionally, the PageBorder() function is called from the Standard Module to draw two border lines on each page of the report. An ellipse is also drawn around the TextBox in the Detail_Print() event of the report.

The Students Exam Passed List Image is given below.

The Exam Failed Cases Listing is generated from the same report. Both listings are created by hiding the Report Detail section for records that do not meet the specified criteria, controlled through the Detail_Format() event.

The SubForm on the Main Form.

Next, a SubForm contains three TextBoxes. The Quantity TextBox accepts values only in the range of 1 to 10, while the Unit Price TextBox must contain a non-zero value. These entries are validated in the OnExit() event subroutine of the ClsText Class Module before being accepted. The TotalPrice TextBox uses its OnGotFocus() event subroutine to calculate the total price (Quantity * UnitPrice) and populate the result into the TextBox. This calculated value is also displayed in the Label control positioned above the SubForm.

The SubForm is introduced here to demonstrate how to scan its controls and enable the required events within the Class_Init() subroutine of the ClsControls_All Class Module. This process is handled through two For...Next loops. The first loop begins with the statement:

For Each ctl In fom.mySubForm.Form.Controls

For the SubForm scanning, and the other starts with:

For Each ctl In fom.Controls

to scan for the required controls and enable their Event Procedures. 

Note: If you place any TextBox on the TabControl pages, they are still treated as part of the main Form. Ensure that their names do not conflict with other TextBox names already present on the Form.

The Form contains a ComboBox with a list of country names. When a country is selected, its list index number is used to automatically select the corresponding capital city in the ListBox. Additionally, clicking an item in the ListBox will display the selected value in a message box, confirming that the Click event is being captured in the Class Module.

The Option Group Control.

The Option Group control is not linked to any specific function. However, when a radio button is clicked, it displays the index number of the selected option. This serves as confirmation that the event has been fired and successfully captured in the Option Group Class Module ClsOption.

The Employee Orders command button opens the form containing the Graph Chart (included in the TabControl menu) directly. The next button, labeled Heading Color, changes the ForeColor property of the form’s heading label.

The Close button triggers a 10-second countdown on the label positioned above the SubForm. Once the countdown reaches zero, the form frmControls_All automatically closes.

The Class Modules associated with the main form controls are named with the prefix Cls….

You can view the complete list of these Class Modules in the declaration section of the ClsControls_All Class Module.

Private tx As ClsText
Private cmd As ClsCmdButton
Private cbo As Clscombo
Private Lst As ClsListBox
Private Opt As ClsOption

Demo Database Download Link:


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:

Streamlining Report Module in Class Module-2

 Introduction.

Hiding Report Lines Conditionally.

Last week, we demonstrated that the code for highlighting Report lines in the Report Print Event can be moved into a standalone Class Module and executed from there whenever the Report is PrintPreviewed or printed.

To visually emphasize specific Report line records that do not meet the required marks criteria, a red ellipse is drawn around the corresponding TextBox. Traditionally, this procedure is implemented directly in the Detail_Print Event Subroutine within the Report’s Class Module. The Report Detail_Format, Report_Page Events.

The Report Detail Section Format Event runs before the Print Event. During this phase, the Report Detail Format Event arranges the data records line by line within the Detail section.

Other events, such as the Report Header, Report Footer, Page Header, Page Footer, and Report Page Events, also occur during the formatting phase. When these Section events are enabled, code can be written within them to control or manipulate the layout and appearance of the Report.

In this example, we will explore some new techniques in the Detail_Format Event of the Report. Instead of highlighting the failed cases with a red ellipse around the obtained marks, as we did in the previous episode, we will use this method to print the Report in two separate categories from the same data set.

  1. Exam Passed Students Listing.

  2. Exam Failed Students Listing. 

Both listings will be generated from the same Report, based on one of the option selections mentioned earlier.

Of course, this could also be achieved more easily by other means, such as passing a value via OpenArgs to filter the Report records, or by setting the Query criteria based on a Form-based TextBox value when launching the Report. However, using the Detail Section Format Event to accomplish this allows you to see an alternative approach. Exploring different methods is both educational and practical—you’ll learn that there’s often more than one way to achieve the desired result.


The Report Listing image for the Exam Passed Cases Option above is given below:

The Exam Failed Cases Listing.

The Report Launching Form Image.

The left-side Command Button launches the Report using the traditional coding within the Report Module itself. In this mode, the Report page is drawn with a double-lined border.

Since there are three Command Buttons on the Main Form, we created a Wrapper Class  ClsCmdButton to handle the launching of Reports. The VBA code for the ClsCmdButton class is shown below:

Option Compare Database
Option Explicit

Private cmdfrm As Form
Private WithEvents cmdNor As CommandButton 'For Normal Report Module Coded Report launching
Private WithEvents cmdCls As CommandButton 'Report Module Code in Class Module
Private WithEvents cmdQuit As CommandButton'Close the Main Form
Dim Opt As Variant
Dim param As String

'------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Command Button Events
'Author: a.p.r. pillai
'Date  : 22/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------

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

Public Property Set cmd_Frm(ByRef cfrm As Form)
    Set cmdfrm = cfrm
    Call class_Init
End Property

Private Sub class_Init()
Const EP = "[Event Procedure]"
    Set cmdNor = cmdfrm.cmdNormal
        cmdNor.OnClick = EP
        
    Set cmdCls = cmdfrm.cmdClass
        cmdCls.OnClick = EP
        
    Set cmdQuit = cmdfrm.cmdClose
        cmdQuit.OnClick = EP
End Sub

Private Sub cmdNor_Click()
Dim RptOpt As Integer

RptOpt = ReportOption()

    param = cmdfrm!Pass 'PassPercentage
    param = param & "," & RptOpt ' and report option
    
    If Nz(cmdfrm.Pass, 0) = 0 Then
        MsgBox "Enter pass-Mark Percentage?" & vbCr & "e.g.: default 60"
    Else
        DoCmd.OpenReport "StudentsPassFail_Normal", acViewPreview, , , , param
    End If
End Sub

Private Sub cmdCls_Click()
Dim RptOpt As Integer

RptOpt = ReportOption()
    param = cmdfrm!Pass 'PassPercentage
    param = param & "," & RptOpt ' and report option
    
    If Nz(cmdfrm.Pass, 0) = 0 Then
        MsgBox "Enter pass-Mark Percentage?" & vbCr & "e.g.: default 60"
    Else
        DoCmd.OpenReport "StudentsPassFail_Normal", acViewPreview, , , , param
    End If
End sub

'Event Subroutines
Private Sub cmdQuit_Click()
    If MsgBox("Close " & cmdfrm.Name & " Form?", vbYesNo + vbQuestion, "cmd_Click") = vbYes Then
        DoCmd.Close acForm, cmdfrm.Name
        Exit Sub
    End If
End Sub    
    

In the global declaration area, the Main Form and Command Button instances are declared, along with two additional variables. The Opt variable stores the Report type option selected, which is returned from the ReportOption() function in a Standard Module. The param string variable is used to hold the OpenArgs input values passed to the Report.

The Report option selection menu is generated by the ReportOption() function in the Standard Module.

Within the Form object Set Property procedure, the Class_Init() subroutine is called. Inside Class_Init(), the Click Events for the Command Buttons are enabled, allowing the wrapper class to handle Report launching.

In the CmdNor_Click() subroutine, the ReportOption() function is called, and the returned value is stored in the RptOpt variable. The PassPercentage value is retrieved from the Form and stored in the param variable. The Report option selection is also appended to param, with both values separated by a comma.

The Report is then opened in Print Preview, with the combined parameter values passed through OpenArgs.

The CmdCls_Click() subroutine follows the same steps to open the StudentsPassFail_Class Report.

Finally, the CmdQuit_Click() event subroutine simply closes the Form.

ReportOption Function VBA Code.

Option Compare Database
Option Explicit

'Report Option Selection
Public Function ReportOption() As Integer
Dim msg As String

  msg = "1. Passed List" & vbCr & "2. Failed List"
  
  Opt = 0
  Do While Opt < 1 Or Opt > 2
    Opt = InputBox(msg, "Report Options", 1)
    If Opt = "" Then Opt = 0
  Loop
  
ReportOption = Opt
End Function

The Report StudentsPassFail_Normal Module VBA Code.

The following VBA code in the Report Module demonstrates how this functionality is implemented using the traditional coding approach. It is provided here for reference.

Option Compare Database
Option Explicit

Dim Opt As Variant

'---------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'---------------------------------------------------------
'Sreamlining Report Module Code in Standalone Class Module
'Author: a.p.r. pillai
'Date  : 22/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'---------------------------------------------------------
Private Sub Report_Load()
Dim x As Variant
x = Split(OpenArgs, ",")

Me!PassPercentage = Val(x(0))
Opt = Val(x(1))

End Sub

Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
'Report Format Pass Calls this Subroutine
Dim Curval As Double
Dim pf As Double
Dim yn As Boolean

On Error GoTo Detail_Format_Err

pf = Me!PassPercentage
Curval = Me!Percentage 'Student's Marks percentage
yn = (Curval >= pf) 'check for pass/fail case

'Detail Section Detault Setting hidden
'During the Report Formatting phase the Items which meets
'the criteria only visible on the Report Detail Section when PrintPreviewed

Detail.Visible = False
If FormatCount = 1 Then 'The Report's Formatting Count=1 
       If yn Then ' yn=True - student Passed
          With Me
            .lblRemarks.Caption = "PASSED"
            .lblRemarks.ForeColor = RGB(0, 255, 0) 'Green Color
                If Opt = 1 Then 'Report Option Passed Students List
                    Detail.Visible = True 'Make the Detail Section Visible
                End If
          End With
        Else 'yn=False an Option=2 Cases
          With Me
            .lblRemarks.Caption = "FAILED"
            .lblRemarks.ForeColor = RGB(255, 0, 0) 'Red Color
                If Opt = 2 Then
                    Detail.Visible = True
                End If
          End With
        End If
End If


Detail_Format_Exit:
Exit Sub

Detail_Format_Err:
MsgBox Err.Description, , "Detail_Format()"
Resume Detail_Format_Exit
End Sub

Private Sub Report_Page()
    PageBorder Me.Name
End Sub

The StudentPassFail_Normal Report Module Segmentwise Code Review.

In the global declaration area, the Opt variable of type Variant is declared to hold the option selected for printing the Exam Result: the Passed or Failed list will be displayed in Print Preview.

The Report_Load Event Subroutine.

 
Option Compare Database
Option Explicit

Dim Opt As Variant

'---------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'---------------------------------------------------------
'Sreamlining Report Module Code in Standalone Class Module
'Author: a.p.r. pillai
'Date  : 22/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'---------------------------------------------------------
Private Sub Report_Load()
Dim x As Variant
x = Split(OpenArgs, ",")

Me!PassPercentage = Val(x(0))
Opt = Val(x(1))

End Sub

In the Form_Load() event procedure, the PassPercentage and the Report selection option—both passed as a single OpenArgs string separated by a comma—are split into a two-element array. The first element, x(0), containing the PassPercentage value, is assigned to the passpercentage TextBox on the Report. The second element, x(1), representing the Report type option, is stored in the Opt variable.

The PassPercentage value is first copied into the local variable pf. The next two lines retrieve the first student’s Obtained Marks Percentage from the Percentage TextBox and store it in the variable CurVal. The student’s score is then compared with the pass mark, and the result of this comparison is stored in the Boolean variable yn.

The following code segment validates whether the current student is in the Passed category within the Detail section of the Report.

Detail.Visible = False 'Hide the Detail Section
If FormatCount = 1 Then 'The first Format Pass on this Page.
       If yn Then
            lblRemarks.Caption = "PASSED"
            lblRemarks.ForeColor = RGB(0, 255, 0)
                If Opt = 1 Then 'Passed Students Listing
                    Detail.Visible = True
                End If
        Else
            lblRemarks.Caption = "FAILED"
            lblRemarks.ForeColor = RGB(255, 0, 0)
                If Opt = 2 Then
                    Detail.Visible = True
                End If
        End If
End If

The statement

Detail.Visible = False

initially hides the Detail Section of the Report. The next step ensures that the OnFormat event is executed for the Detail Section, for the first time, to lay out the student record. Note that the Report may go through multiple formatting passes per page in preparation for printing or Print Preview.

If the student is in the Passed category and the selected Report option is 1, then the Detail Section is made visible for that record, allowing it to appear in the Report.

Otherwise, if the student’s Obtained Marks Percentage is below the pass threshold and the selected option equals 2, those student records in the Detail Section are made visible. In both scenarios, the Boolean variable yn and the option selection must align correctly—TRUE & 1 for the Passed list, or FALSE & 2 for the Failed list—to ensure that only the relevant records appear on the Report.

Next, the Report_Page() subroutine calls the PageBorder() function from the Standard Module to draw the page border on the Report.

Private Sub Report_Page()
    PageBorder Me.Name
End Sub

The PageBorder() Function Code In the Standard Module.

Public Function PageBorder(ByVal strName As String)
Dim Rpt As Report, lngColor As Long
Dim sngTop As Single
Dim sngLeft As Single
Dim sngwidth As Single
Dim sngheight As Single

'------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Draw Report PageBorder
'Author: a.p.r. pillai
'Date  : 18/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------

lngColor = RGB(0, 0, 255)
On Error GoTo DrawBox_Err

    Set Rpt = Reports(strName)
    ' Set scale to pixels.
    Rpt.ScaleMode = 3
    
    'outer Border
       sngTop = Rpt.ScaleTop        'Top Value After Margin
      sngLeft = Rpt.ScaleLeft       'Left Value After Margin
     sngwidth = Rpt.ScaleWidth - 7  ' Right Margin -7 pixels
    sngheight = Rpt.ScaleHeight - 7 'Bottom Margin -7 pixels
       
    ' Draw line as a box.
Rpt.Line (sngTop, sngLeft)-(sngwidth, sngheight), lngColor, B

  'Draw Box inside the outer Box
   sngTop = Rpt.ScaleTop + 5
  sngLeft = Rpt.ScaleLeft + 5
 sngwidth = Rpt.ScaleWidth - 13
sngheight = Rpt.ScaleHeight - 13

'Draw second Box within the Borders of the First Box
Rpt.Line (sngTop, sngLeft)-(sngwidth, sngheight), lngColor, B

DrawBox_Exit:
Exit Function

DrawBox_Err:
MsgBox Err.Description, , "DrawBox"
Resume DrawBox_Exit
End Function

The PageBorder() function accepts the open Report’s name as a parameter. It draws two concentric border lines inside the four sides of the Report’s margin area.

The function uses the LINE command with the X and Y coordinates of the top-left corner, offset from the left and top margins, and the bottom-right corner, positioned just before the right and bottom margins, to draw a diagonal line. The Color parameter, specified as an RGB value, determines the color of the line. The final parameter, B, instructs Access to draw a box using the diagonal coordinates, effectively creating the border around the page.

The Rpt.ScaleTop and Rpt.ScaleLeft properties determine the coordinates of the top-left corner of the page, adjusted to account for the Left and Top margin areas. Additional offset values are applied to ensure that when the box is drawn, it does not overlap the outer border or create an unintended second border inside it.

The Streamlining Procedure of the Report VBA Code.

Let us create a standalone Wrapper Class Module to transfer the Report Module code into it. The code for the ClsStudentsList Wrapper Class is shown below:

Option Compare Database
Option Explicit

Private WithEvents Rpt As Access.Report
Private WithEvents SecDetail As Access.[_SectionInReport]

Private RequiredMarks As Access.TextBox
Private Obtained As Access.TextBox
Private lblRem As Access.Label

Private Opt As Variant

'---------------------------------------------------------
'Streamlining Form Module Code
'in Stand-alone Class Modules
'---------------------------------------------------------
'Sreamlining Report Module Code in Standalone Class Module
'Author: a.p.r. pillai
'Date  : 22/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'---------------------------------------------------------

Public Property Get mRpt() As Access.Report
   Set mRpt = Rpt
End Property

Public Property Set mRpt(RptNewVal As Access.Report)
  Set Rpt = RptNewVal

Call class_Init
End Property

Private Sub class_Init()
On Error GoTo Class_Init_Err:
Dim msg As String
Dim x As Variant

Const strEvent = "[Event Procedure]"
x = Split(Rpt.OpenArgs,",")

  With Rpt
    !PassPercentage = Val(x(0)) ' Pass Mark% save on Report
    Set RequiredMarks = .PassPercentage 'Assign this Control Reference to pct
    Opt = Val(x(1))
    Set Obtained = .Percentage     'Student's Obtained Percentage
    Set lblRem = .lblRemarks 'Passed/Failed Display Label
    Set SecDetail = .Section(acDetail) 'Detail Section Reference
    
     SecDetail.OnFormat = strEvent 'Enable Detail Section Format Event
     .OnPage = strEvent 'Enable Report_Page Event
  End With
  
Class_Init_Exit:
Exit Sub

Class_Init_Err:
MsgBox Err & ": " & Err.Description, vbCritical + vbOK, "Class_Init()"

Resume Class_Init_Exit
End Sub

Private Sub secDetail_Format(Cancel As Integer, FormatCount As Integer)
'Report Format Pass Calls this Subroutine
Dim Curval As Double
Dim pf As Double    'pass/fail
Dim yn As Boolean

On Error GoTo secDetail_Format_Err

Curval = Nz(Obtained.Value, 0)
pf = Nz(RequiredMarks.Value, 0)
yn = (Curval >= pf)

'Start Laying the Detail Section items from Format Count 1 onwards.
'All the Records are placed on the Detail Section, but the
'Lines which meets the Criteria only made visible on the Report.

SecDetail.Visible = False 'Hide the Detail Section
If FormatCount = 1 Then 'The first Format Pass on this Page.
       If yn Then
            lblRem.Caption = "PASSED"
            lblRem.ForeColor = RGB(0, 255, 0)
                If Opt = 1 Then 'Passed Students Listing
                    SecDetail.Visible = True
                End If
        Else
            lblRem.Caption = "FAILED"
            lblRem.ForeColor = RGB(255, 0, 0)
                If Opt = 2 Then
                    SecDetail.Visible = True
                End If
        End If
End If

secDetail_Format_Exit:
Exit Sub

secDetail_Format_Err:
MsgBox Err & ": " & Err.Description, , "secDetail()"
Resume secDetail_Format_Exit
End Sub

Private Sub Rpt_Page()
    PageBorder Rpt.Name
End Sub 

In the global declaration area, a Report object named Rpt is declared and qualified with the WithEvents keyword, allowing the class to capture Report-level events (such as Report_Page()) and execute their corresponding event subroutines. Additionally, a Report Section object, SecDetail, is also declared with WithEvents to capture Detail Section Format events of the Report.

Two TextBox controls and a Label control are declared. The first TextBox, RequiredMarks, stores the pass mark percentage entered on the Form used to launch the Report and passed via OpenArgs. The second TextBox, Obtained, reads the student’s obtained marks percentage directly from the Report.

The lblRem Label control corresponds to the Remarks column on the Report and will display “PASSED” or “FAILED” based on the Report type option selected. The Opt variable of type Variant stores the Report type option returned from the ReportOption() function.

Next, the Report object property Get, Set procedures assign the Report reference to the Rpt object in the ClsStudentsList Wrapper Class. Within the Set m_Rpt() property procedure, the Class_Init() subroutine is called to initialize the class. The relevant Class_Init() code segment is shown below:

Private Sub class_Init()
On Error GoTo Class_Init_Err:
Dim msg As String
Dim x As Variant

Const strEvent = "[Event Procedure]"
x = Split(Rpt.OpenArgs,",")

  With Rpt
    !PassPercentage = Val(x(0)) ' Pass Mark% save on Report
    Set RequiredMarks = .PassPercentage 'Assign this Control Reference to pct
    Opt = Val(x(1))
    Set Obtained = .Percentage     'Student's Obtained Percentage
    Set lblRem = .lblRemarks 'Passed/Failed Display Label
    Set SecDetail = .Section(acDetail) 'Detail Section Reference
    
     SecDetail.OnFormat = strEvent 'Enable Detail Section Format Event
     .OnPage = strEvent 'Enable Report_Page Event
  End With
  
Class_Init_Exit:
Exit Sub

Class_Init_Err:
MsgBox Err & ": " & Err.Description, vbCritical + vbOK, "Class_Init()"

Resume Class_Init_Exit
End Sub

At the beginning of the Class_Init() subroutine, a local variable msg and a constant variable strEvent, assigned the text "[Event Procedure]", are declared.

The next segment of VBA code initializes the previously declared object variables by assigning them references to the corresponding controls on the Report.

x = Split(Rpt.OpenArgs,",")

  With Rpt
    !PassPercentage = Val(x(0)) ' Pass Mark% save on Report
    Set RequiredMarks = .PassPercentage 'Assign this Control Reference to pct
    Opt = Val(x(1))
    Set Obtained = .Percentage     'Student's Obtained Percentage
    Set lblRem = .lblRemarks 'Passed/Failed Display Label
    Set SecDetail = .Section(acDetail) 'Detail Section Reference
    
     SecDetail.OnFormat = strEvent 'Enable Detail Section Format Event
     .OnPage = strEvent 'Enable Report_Page Event
  End With
 

The statement

x = Split(Rpt.OpenArgs, ",") 

splits the OpenArgs string into two parts. The first element, x(0), containing the Pass Percentage Marks, is assigned to the corresponding TextBox on the Report. The second element, x(1), representing the Report category option, is stored in the Opt variable.

 The student’s Obtained Marks Percentage is assigned to the Obtained TextBox instance in the class module. The lblRem Label object is assigned the reference of the lblRemarks Label control under the Remarks column on the Report. Finally, the SecDetail object is assigned the reference of the Report’s Detail Section.

Note: What does “reference” mean in the context of a Form, Control, or Object?

When a Form or Control is loaded into memory, it occupies a specific area of the computer’s memory with an addressable reference (or memory pointer). Although we don’t know the exact memory address, it is directly associated with the object’s name, such as Text0 or Quantity.

When this reference is assigned to an instance of the same object type—for example:

Set Txt = myForm.Quantity

—we can manipulate the physical object indirectly through the instance. For example, setting

Txt.Value = 20

stores the value 20 in the Quantity TextBox on the Form.

Similarly, when we pass a Form or TextBox to a function, actually we are passing the reference to that object, not a copy of the object itself.

This concept of working with object references is the foundation of streamlining Form and Report module code using standalone class modules.

Here’s a simple conceptual diagram (by ChatGPT) to illustrate how object references work in VBA:

+----------------------+ +----------------------+ | Form: myForm | | Memory (Objects) | |----------------------| |----------------------| | TextBox: Quantity | ---> | [Memory Address 101] | <- Physical TextBox in memory | TextBox: Name | | [Memory Address 102] | | CommandButton: OK | | [Memory Address 103] | +----------------------+ +----------------------+ | | Set Txt = myForm.Quantity v +----------------------+ | Txt (Variable) | |----------------------| | Reference -> 101 | <- Points to the Quantity TextBox in memory +----------------------+ Usage Example: Txt.Value = 20 ' Updates the actual Quantity TextBox on the Form

Explanation:

  1. Each control on the Form occupies memory when the Form is loaded.

  2. Assigning a control to a variable with Set does not create a new control; it creates a reference pointing to the same memory location.

  3. Any changes made via the reference (Txt.Value = 20) directly affect the original control.

  4. Functions that accept a Form or Control as a parameter actually receive the reference, allowing the function to work directly with the original object.


@@@Next, the Report Detail Section Format and Report_Page() Events are enabled. Once these are active, the Report Type Option Menu is displayed, allowing the user to select the required report.

After the Report Type Option is chosen, the report formatting process begins, with special attention to the requirements of the Detail Section. We have already covered the code written in the Detail Section’s Format Event Procedure and explained its functionality earlier.

At the Page level, the Report_Page Event Procedure calls the PageBorder() Function in the Standard Module to draw the border around each Report Page.

Finally, to execute the code in the ClsStudentsList Class Module, we must explicitly load this module into memory through the Report_Load() Event Procedure. A Class Module cannot load itself into memory. The streamlined VBA code for the Report Module is given below.

Option Compare Database
Option Explicit

Private Marks As New ClsStudentsList

Private Sub Report_Load()
  Set Marks.mRpt = Me
End Sub

The above four lines of code load the Standalone Class Module into memory and start the entire process to run from within the Class Module.

I hope you enjoy working with VBA in Standalone Class Modules and recognize their potential to streamline your development process. By separating Report Design from VBA coding, you gain the flexibility to easily reuse and transport code across projects, saving valuable time while ensuring cleaner, more maintainable solutions.

Demo Database Download Link. Change Database.

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:

Streamlining Report Module Code in Class Module.

 Introduction.

Report Module VBA Code in Standalone Class Module.

In the earlier episodes of this series, Streamlining Form Module Code in the Standalone Class Module, we explored how to handle Event Subroutines for frequently used Access controls within a standalone Class Module. This approach allows for managing event handling in Standalone Class Module(s),  without interfering with the design task of the Form, resulting in cleaner, more maintainable code.

If you’re new to handling Form/Report Module Event Procedures in standalone Class Modules, I suggest starting with the earlier episodes in this series. These articles provide the necessary background and step-by-step explanations to help you understand and apply this approach effectively. Links to all previous articles in the series are provided at the end of this page.

The Report Section Print Event Subroutines.

This is a repeat of the two Blog Posts published earlier on Highlighting Report Lines conditionally, as part of the new concept of Streamlining Form/Report Module Code in the Standalone Class Module. This demonstrates how to transfer the existing Event Subroutine Codes from the Report Module to the Standalone Class Module. Generally speaking, this is double work, and nobody would like to go into the Code again, which was written for a particular task after incorporating several refinements and final touches. 

But, I think if you would like to do some trial runs and try to convert a few existing Form/Report Module Codes to the new way of coding, you will know how much extra work you put into the traditional method, and it will give you more insights into the new way of coding, in comparison with the traditional way.

Organize the Existing Form Module Code for the Standalone Class Module.

  1. Before moving forward with this approach, it is essential to first organize the existing code in the Form Module Code by grouping it by the Control types.
  2. Create a copy of the Form you want to experiment with and give it a new name. Then, plan to transfer all of its TextBox Event Subroutine code into a dedicated TextBox Wrapper Class as the first step.

  3. In preparation, move the TextBox Event Subroutines next to each other on the Form Module itself first. If a single TextBox has more than one Event Subroutine, then arrange them next to each other too.
  4. Create a TextBox Wrapper Class.

  5. Create the Intermediate Class Module and enable the Events of the TextBoxes in the Class_Init() Subroutine.
  6. Copy and paste the TextBox Event Subroutines from the Form Module to the Wrapper TextBox Class and modify the Event Subroutine Names with the correct TextBox Object Instance name as Subroutine name Prefix. Use the Select Case . . . End Select structure inside each category of Event to position the Code that belongs to a particular TextBox on the Form.

  7. Delete the Copied Subroutines from the Form Module.
  8. At the Form Module global declaration area, define the Intermediate Class Object Instance and in the Form_Load() Event Procedure, pass the Me Form object to the Set Frm() Property Procedure.

  9. Save the Form after changes.
  10. Before we try this new Form with the Wrapper Class-based Event Subroutines in place, rename the original Form with a new name with a suffix of X or Y.

  11. Rename the new Form to the Original name.
  12. Open the Form as you do from your Main Menu, or whatever method is built into your Project, and ensure that all the Subroutines of all the TextBoxes on the Form function as before. If any errors, track, find, and correct them, and ensure everything works normally as before.

  13. Repeat this process for all other controls on the Form. 

The Traditional Way of Coding.

The traditional method of Coding is the best way for beginners to learn VBA, and that is how we all started learning VBA. My first PC was the Spectravideo Model SV-328 with built-in Microsoft Extended Basic, and I started learning the BASIC Language from there, writing programs and saving them to the audio Cassette Tapes, the cheapest mode of storage at that time.

It is interesting now to look at the Bootup Screen Image of this Computer, given below (courtesy of Wikipedia.org):

Then came the GW-Basic, QuickBASIC, Visual Basic, and VBA.

There is an interesting Computer Buying Guide that I have from 1986, published by the Consumer Guide Magazine, for Computers with a Price Range of $299 (Commodore 128) to $4550 (Kaypro 286i, with 512K RAM, Speed 6 MHz, 80286 microprocessor, compatible with IBM PC AT).

The configuration of the machines, their speed, and the Prices of the machines in those days are interesting to read and compare with the present-day machines.

The following two links to earlier Blog Posts on Report Module Coding highlight some Report lines on certain conditions during the Printing cycle of the Report. 

1. Highlighting Reports. - August 16, 2007

2. WithEvents and Report Line Highlighting - June 21, 2019

Let us see how we can run the Report Print Event Subroutines Code in the Standalone Class Module.

 The Students' Examination Result Report Image.

Note: If you attempt to run the Report (StudentHighlight_Normal) directly by double-clicking on the Report, the Report may open in the ReportPreview Mode and not in PrintPreview Mode. In that case, you will not find the display as shown above. Right-click on the Report, and select the PrintPreview option from the displayed Menu to view the Report correctly.

The Students’ Examination Report displays the final results. The minimum pass requirement is set at 60% (360 marks out of 600). For students who fall short of this threshold, their percentage scores are visually highlighted with a red ellipse around the marks. Additionally, a legend label is included in the report footer, drawn within a circle to indicate the meaning of the highlight.

To experiment with different pass mark percentages, a Form is provided with a TextBox where you can enter the desired pass percentage before launching the Exam Result Report in Print Preview mode. The specified percentage is passed to the Report through the OpenArgs parameter when the Report is opened. The sample Form image is shown below for reference.

The VBA Code run earlier in the Report Class Module is listed below for reference. This Code will be transferred to the Standalone Class Module to run the procedure in the new Coding method.

Option Compare Database
Option Explicit

Private Curval As Double
Private MinTarget As Double

Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
Dim intPrintCircle As Boolean

On Error GoTo secRpt_Print_Err

MinTarget = Me!PassPercentage
Curval = Me!Percentage

If (MinTarget > 0) And (Curval < MinTarget) Then
  intPrintCircle = True
Else
  intPrintCircle = False
End If

DrawCircle intPrintCircle, Me.Percentage

secRpt_Print_Exit:
Exit Sub

secRpt_Print_Err:
MsgBox Err.Description, , "secRpt_Print()"
Resume secRpt_Print_Exit
End Sub

Private Sub ReportFooter_Print(Cancel As Integer, PrintCount As Integer)
Dim yn As Boolean, lbl As Control

On Error GoTo secFutr_Print_Err

yn = True 'set the flag false to draw oval shape
Set lbl = Me.Legend 'pass label control in Page Footer

DrawCircle yn, lbl 'draw circle in legend label

secFutr_Print_Exit:
Exit Sub

secFutr_Print_Err:
MsgBox Err.Description, , "secFutr_Print"
Resume secFutr_Print_Exit

End Sub

Private Sub DrawCircle(ByVal bool As Boolean, ovlCtl As Control)
Dim ctl As Control
Dim bolPrintCircle As Boolean
Dim sngAspect As Single
Dim intShapeHeight As Integer
Dim intShapeWidth As Integer
Dim sngXCoord As Single
Dim sngYCoord As Single

On Error GoTo DrawCircle_Err

If bool Then 'if pass no highlighting, change logic for pass cases
    bolPrintCircle = True
Else 'highlight failed cases
    bolPrintCircle = False
End If

Set ctl = ovlCtl
        
    If Not IsNull(ctl) Then
        If bolPrintCircle Then
           ' change this value to adjust the oval shape of the circle.
            sngAspect = 0.25
   
            ' Determine coordinates of ctl and to draw ellipse.
            ' Determine height and width of ellipse.
            intShapeHeight = ctl.Height
            intShapeWidth = ctl.Width
    
            'calculate circle vertical Y coordinate
            sngYCoord = ctl.Top + (intShapeHeight \ 2)

            'calculate horizontal X coordinate of circile
            sngXCoord = ctl.Left + (intShapeWidth \ 2)
            
            'draw an ellipse around the Total TextBox
            Report.Circle (sngXCoord, sngYCoord), intShapeWidth \ 2, RGB(255, 0, 0), , , sngAspect
            bolPrintCircle = False
        End If
    End If

DrawCircle_Exit:
Exit Sub

DrawCircle_Err:
MsgBox Err.Description, , "DrawCircle()"
Resume DrawCircle_Exit
End Sub

Review of the VBA Code in the Report Class Module.

The Report uses two Event Subroutines: Detail_Print() and ReportFooter_Print(). In both cases, the DrawCircle() Subroutine is invoked to highlight specific areas. Within the Detail_Print() event, it draws an ellipse around the percentage marks in the TextBox when the student’s score falls below the pass criteria. The threshold value is supplied through the OpenArgs parameter passed from the Form’s TextBox at the time the Report is opened. Similarly, in the ReportFooter_Print() event, the same Subroutine is used to draw the legend circle around the Label in the Report Footer.

The Print Event Subroutines are written directly in the Report Module StudentHighLight_Normal, as is normally done in Access. This demonstration Report can be executed directly from the sample database provided at the end of this page. Simply open the Form shown above and click the Command Button labeled “Normal Report” to view the result.

The Changed VBA Code in the Standalone Class Module.

The same Event Subroutines and the DrawCircle() Subroutine Codes are removed from the Report Class Module and placed in the Standalone Wrapper Class Module as given below:

Option Compare Database
Option Explicit

Private WithEvents Rpt As Access.Report
Private WithEvents secRpt As Access.[_SectionInReport]
Private WithEvents secFutr As Access.[_SectionInReport]

Private pct As Access.TextBox  'Percentage
Private lgnd As Access.Label
Private Pass As Integer 'OpenArgs Value
'------------------------------------------------------
'Streamlining Report Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Streamlining Report Module Code
'Author: a.p.r. pillai
'Date  : 12/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------
Public Property Get mRpt() As Access.Report
   Set mRpt = Rpt
End Property

Public Property Set mRpt(RptNewVal As Access.Report)
  Set Rpt = RptNewVal
  
  Call Class_Init
End Property

Private Sub Class_Init()
On Error GoTo mRpt_Err
Const strEvent = "[Event Procedure]"

'Pass Percentage value
  Pass = Nz(Rpt.OpenArgs, 0) 'OpenArgument
  Rpt.PassPercentage.Value = Pass
  
  With Rpt
     Set secRpt = .Section(acDetail)
     Set secFutr = .Section(acFooter)
     
     secRpt.OnPrint = strEvent  'Enable Event
     secFutr.OnPrint = strEvent '    "
  End With
  
  Set pct = Rpt!Percentage
  Set lgnd = Rpt.Legend
 
mRpt_Exit:
Exit Sub

mRpt_Err:
MsgBox Err.Description, , "mRpt()"
Resume mRpt_Exit

End Sub

'Draw ellipse around controls that meet specified criteria.
Private Sub secRpt_Print(Cancel As Integer, PrintCount As Integer)

Dim obtPct As Double
Dim yn As Boolean

On Error GoTo secRpt_Print_Err

    obtPct = pct.Value         'read Pass Percentage TextBox value
    yn = (obtPct < Pass)      'Passed or Not (TRUE/FALSE)

'call the DrawCircle Subroutine with Fail flag
'and the Control as parameters
    Call DrawCircle(yn, pct)

secRpt_Print_Exit:
Exit Sub

secRpt_Print_Err:
MsgBox Err.Description, , "secRpt_Print"
Resume secRpt_Print_Exit

End Sub

'Draw oval shape around Label in Report_Footer Section
Private Sub secFutr_Print(Cancel As Integer, PrintCount As Integer)
Dim yn As Boolean, lbl As Control

On Error GoTo secFutr_Print_Err

Set lbl = lgnd 'Fail label control in Page Footer
yn = True 'set the flag True to draw oval shape

Call DrawCircle(yn, lbl) 'draw circle in legend label

secFutr_Print_Exit:
Exit Sub

secFutr_Print_Err:
MsgBox Err.Description, , "secFutr_Print"
Resume secFutr_Print_Exit

End Sub

Private Sub DrawCircle(ByVal bool As Boolean, ovlCtl As Control)
Dim ctl As Control
Dim bolPrintCircle As Boolean
Dim sngAspect As Single
Dim intShapeHeight As Integer
Dim intShapeWidth As Integer
Dim sngXCoord As Single
Dim sngYCoord As Single

On Error GoTo DrawCircle_Err

If bool Then 'Highlight Failed Cases
    bolPrintCircle = True
Else
    bolPrintCircle = False
End If

Set ctl = ovlCtl
    If Not IsNull(ctl) Then
        If bolPrintCircle Then
        ' change this value to adjust the oval shape of the circle.
            sngAspect = 0.25
   
        ' Determine coordinates of ctl and to draw ellipse.
        ' Get height and width of Control.
            intShapeHeight = ctl.Height
            intShapeWidth = ctl.Width
    
        'calculate circle vertical Y coordinate
            sngYCoord = ctl.Top + (intShapeHeight \ 2)

        'calculate horizontal X coordinate of circile
            sngXCoord = ctl.Left + (intShapeWidth \ 2)
            
        'Draw an ellipse within the Percentage TextBox Boundaries
        'in Red Color
            Rpt.Circle (sngXCoord, sngYCoord), intShapeWidth \ 2, RGB(255, 0, 0), , , sngAspect
            bolPrintCircle = False
        End If
    End If

DrawCircle_Exit:
Exit Sub

DrawCircle_Err:
MsgBox Err.Description, , "DrawCircle()"
Resume DrawCircle_Exit

End Sub

Review of the Report Wrapper Class VBA Code.

To run the Standalone Class Module Code with the Report, we first need to obtain a reference to the open Report and assign it to the Report object instance Rpt within our Standalone Class Module. Once this is set, the Event procedures can be captured and executed outside the Report’s own module. Let us now walk through the code from the beginning up to the Class_Init() segment.

Option Compare Database
Option Explicit

Private WithEvents Rpt As Access.Report
Private WithEvents secRpt As Access.[_SectionInReport]
Private WithEvents secFutr As Access.[_SectionInReport]

Private pct As Access.TextBox  'Percentage
Private lgnd As Access.Label
Private Pass As Integer 'OpenArgs Value
'------------------------------------------------------
'Streamlining Report Module Code
'in Stand-alone Class Modules
'------------------------------------------------------
'Streamlining Report Module Code
'Author: a.p.r. pillai
'Date  : 12/09/2023
'Rights: All Rights(c) Reserved by www.msaccesstips.com
'------------------------------------------------------
Public Property Get mRpt() As Access.Report
   Set mRpt = Rpt
End Property

Public Property Set mRpt(RptNewVal As Access.Report)
  Set Rpt = RptNewVal
  
  Call Class_Init
End Property

Private Sub Class_Init()
On Error GoTo mRpt_Err
Const strEvent = "[Event Procedure]"

'Pass Percentage value
  Pass = Nz(Rpt.OpenArgs, 0) 'OpenArgument
  Rpt.PassPercentage.Value = Pass
  
  With Rpt
     Set secRpt = .Section(acDetail)
     Set secFutr = .Section(acFooter)
     
     secRpt.OnPrint = strEvent  'Enable Event
     secFutr.OnPrint = strEvent '    "
  End With
  
  Set pct = Rpt.Percentage
  Set lgnd = Rpt.Legend
 
mRpt_Exit:
Exit Sub

mRpt_Err:
MsgBox Err.Description, , "mRpt()"
Resume mRpt_Exit

End Sub
 

The first three lines, immediately following Option Explicit, declare a Report object instance named Rpt, qualified with the WithEvents keyword to capture the Report’s events. In addition, two Report Section objects (Access.[_SectionInReport]) are declared, followed by two Report Control objects and an Integer variable.

Next, the Set and Get Property Procedures for the Report object are defined. Once the Report object is assigned to the Rpt instance variable, the Set mRpt() Property Procedure calls the Class_Init() Subroutine to initialize the class.

The strEvent A constant variable is assigned the string value "[Event Procedure]". The statement

Pass = Nz(Rpt.OpenArgs, 0) 

reads the Pass Mark percentage passed through the DoCmd.OpenReport call from the Form and stores it in the Integer variable Pass. The next line assigns this value to the PassPercentage TextBox on the Report.

Next, the Report Detail Section and Footer Section References are assigned to the secRpt and secFutr Section Objects, respectively. The next two lines of Code enable printing events.

The statement Set pct = 'Rpt.Percentage' assigns the Obtained Marks Percentage TextBox reference, and the 'lgnd' Label Object is assigned with the Reference of the Legend label control at the Report Footer.

These initialisation steps run in the standalone Class Module when the Report is opened from the cmdClass Click Event Subroutine.

Private Sub cmdClass_Click()
    If Nz(Me.Pass, 0) = 0 Then
        MsgBox "Enter pass-Mark Percentage?" & vbCr & "e.g.: 60"
    Else
        'Me.Pass - Pass Mark Percentage value passed as OpenArgs
        DoCmd.OpenReport "StudentsHighlight_Class", acViewPreview, , , , Me.Pass
    End If
End Sub 

Report Class Module VBA Code.

In the Report Class Module, declare an Instance of the Standalone Class Module Object ClsStudentHighlight with the name Marks in the global area. The New keyword in  Private Marks As New ClsStudentHighlight statement creates an instance of the Standalone Class Module in memory.

In the Report_Load() Event Subroutine, the current Report Object Me is passed to the Set mRpt() Property Procedure in the ClsStudentHighlight Class Module Object.

Option Compare Database
Option Explicit

Private Marks As New ClsStudentHighlight

Private Sub Report_Load()
  Set Marks.mRpt = Me
End Sub

The Report Detail_Print() Event.

When the Report is open, the ClsStudentHighlight standalone Class Module Instance is also open in memory by the Report Module global declaration statement. The current Report Reference is passed to the Set mRpt() Property Procedure, and the standalone Class Module Code starts executing.

'Draw ellipse around controls that meet specified criteria.
Private Sub secRpt_Print(Cancel As Integer, PrintCount As Integer)

Dim obtPct As Double
Dim yn As Boolean

On Error GoTo secRpt_Print_Err

    obtPct = pct.Value         'read Pass Percentage TextBox value
    yn = (obtPct < Pass)      'Passed or Not (TRUE/FALSE)

'call the DrawCircle Subroutine with Fail flag
'and the Control as parameters
    Call DrawCircle(yn, pct)

secRpt_Print_Exit:
Exit Sub

secRpt_Print_Err:
MsgBox Err.Description, , "secRpt_Print"
Resume secRpt_Print_Exit

End Sub 

When the Report is open, two major events occur: the Report Format Event and the Report Print Event. The Report Format Event organizes the data line by line and determines how many lines of data can fit on a single page, taking into account the paper size, the design height of the Detail section, and the page orientation.

In contrast, the highlighting of Exam Failed cases is performed exclusively during the Report Print Event, ensuring that the visual emphasis is applied at the time of printing or print-previewing the Report. There are two Variables declared for reading the Obtained Percentage of Marks from the Percentage TextBox on the Report into the Variable obtPct. If the obtained mark is less than the Pass Mark percentage (received in the OpenArgs), then the Boolean Variable yn value is set as TRUE.

Next, the DrawCircle() Subroutine is called, with the yn Test Result and pct Percentage TextBox object as parameters, to draw a red oval shape around the Percentage TextBox on the Report, indicating that the student is not successful in their examination.

The DrawCircle() Subroutine Code.

Private Sub DrawCircle(ByVal bool As Boolean, ovlCtl As Control)
Dim ctl As Control
Dim bolPrintCircle As Boolean
Dim sngAspect As Single
Dim intShapeHeight As Integer
Dim intShapeWidth As Integer
Dim sngXCoord As Single
Dim sngYCoord As Single

On Error GoTo DrawCircle_Err

If bool Then 'Highlight Failed Cases
    bolPrintCircle = True
Else
    bolPrintCircle = False
End If

Set ctl = ovlCtl
    If Not IsNull(ctl) Then
        If bolPrintCircle Then
        ' Increase/Decrease this value to adjust the oval shape of the circle.
            sngAspect = 0.25
   
        ' Calculate the coordinates of the ctl to draw the ellipse.
        ' Get height and width of Control.
            intShapeHeight = ctl.Height
            intShapeWidth = ctl.Width
    
        'calculate circle vertical Y coordinate
            sngYCoord = ctl.Top + (intShapeHeight \ 2)

        'calculate horizontal X coordinate of circle
            sngXCoord = ctl.Left + (intShapeWidth \ 2)
            
        'Draw an ellipse within the Percentage TextBox Boundaries
        'in Red Color
            Rpt.Circle (sngXCoord, sngYCoord), intShapeWidth \ 2, RGB(255, 0, 0), , , sngAspect
            bolPrintCircle = False
        End If
    End If

DrawCircle_Exit:
Exit Sub

DrawCircle_Err:
MsgBox Err.Description, , "DrawCircle()"
Resume DrawCircle_Exit

End Sub

The DrawCircle() Subroutine VBA Code Review.

At the beginning of the routine, the essential variables are declared. If the first parameter bool is TRUE, the local variable bolPrintCircle is set  TRUE to indicate that an ellipse should be drawn.

 The sngAspect  variable is initialized with the value 0.25, which creates an oval shape around the TextBox. This value can be adjusted—either increased or decreased—to ensure that the ellipse fits neatly within the TextBox’s dimensions without overflowing. The actual Width and Height of the TextBox are captured and stored in the variables intShapeWidth , and intShapeHeight respectively, to guide the drawing of the ellipse.

The next two statements calculate the center point (X and Y coordinates) of the ellipse (oval shape).

The Rpt.Circle() statement Draws the Circle in red using the value intShapeWidth\2 as the Radius Value of the Circle.

The ReportFooter_Print() Event Subroutine.

'Draw oval shape around Label in Report_Footer Section
Private Sub secFutr_Print(Cancel As Integer, PrintCount As Integer)
Dim yn As Boolean, lbl As Control

On Error GoTo secFutr_Print_Err

Set lbl = lgnd 'Fail label control in Page Footer
yn = True 'set the flag True to draw oval shape

Call DrawCircle(yn, lbl) 'draw circle in legend label

secFutr_Print_Exit:
Exit Sub

secFutr_Print_Err:
MsgBox Err.Description, , "secFutr_Print"
Resume secFutr_Print_Exit

End Sub 

The ReportFooter_Print() Subroutine Calls the DrawCircle() Subroutine to draw the ellipse within the Legend Label Control.

Demo Database Download Link.


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:

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