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

MS-Access and Mail Merge-3

Continuation of MS Access and Mail Merge-2

Continued from the previous post, MS Access and Mail Merge-2.

Mail Merge in MS Access.

We have already designed and tested the Form Letter preparation process in the earlier article. All the objects created during that process will also be required for implementing Mail Merge in Access, which means there is now less work involved. I hope you have understood the intricacies of the procedure and how the various objects and methods work together to generate the Form Letter. Since the major design tasks are already complete, we can now focus on implementing the Mail Merge functionality with just a few minor adjustments in two or three places.

Below are a few examples demonstrating how to insert field values from the report's source query or table and incorporate built-in functions directly into the body of the letter using the editing form. To use the Mail Merge method effectively, the user simply needs to follow a few basic rules. 

I will insert the Main Program Codes at the end of this Article. You may copy and paste them into the appropriate locations in your Project as suggested there.

Usage of Field Values to merge with the Text:

  • Data Field Names must be enclosed in square brackets [], and there should not be any typographical errors in Field Names.

  • If field names are placed next to each other, use at least one space between them, but they can be placed without a space too.

  • Field Names must be picked from the Report Source Data Table/Query only and can be inserted anywhere in the text.

  • Data field values from another Table/Query can be inserted using built-in Functions (like DLOOKUP()); see the usage of built-in functions given below.

  • If Field Names are joined with & + ' * / (for character or numerical values), it will not be treated as an expression; instead, the symbol will appear between the field values. (See Usage of built-in functions for expressions involving Field Values).

Usage Example -1: A Statement of Year-wise special Sales Incentive, credited to [TitleofCourtesy] [Firstname] [LastName]'s personal Account, is given below for information. His request for transfer to his hometown [City]&[Zip] has been approved.

Output: A Statement of Year-wise special Sales Incentive, credited to Dr. Andrew Fuller's personal Account, is given below for information. His request for transfer to his hometown, Tacoma 9801, has been approved.

NB: The inserted text will not appear in bold as shown above; it is used here for highlighting the results only.

Usage of Built-in Functions to Merge the result with the Letter text:

  • Built-in Functions must be enclosed in { } brackets.

  • Nested Functions should have only the outermost pair of {} brackets, like in {Format(Date(),"mmm-yy")}

  • Data Field Names enclosed in [ ] can be used as parameters to Built-in Functions, like DLOOKUP().

  • Expressions involving data field names enclosed in square brackets ([ ]) can be used with arithmetic operators (+, -, *, /) in numerical calculations, and with the ampersand (&) for text concatenation. When using built-in functions, the field references must be passed as parameters within the function.

Example: To calculate the expiry date of a 90-day credit period, use the following expression:

{Format([InvoiceDate] + 90, "dd/mm/yyyy")}

Usage Example-2:
A statement of year-wise special sales incentive for [TitleOfCourtesy] [FirstName] [LastName], amounting to ${DLOOKUP("TotalAmt", "Yearwise_Incentive", "EmployeeID = " & [EmployeeID])}, has been credited to his personal account during {Format(DateAdd("m", -1, Date()), "mmm-yyyy")}. His request for transfer to his hometown, [City], has been approved effective {Date() + 15}.

Output: A statement of Year-Wise special Sales incentive for Dr. Andrew Fuller, amounting to $8696.41, has been credited to his personal Account during Sep-2007. His request for transfer to his hometown, Tacoma, has been approved effective 10/30/2007.

Don'ts:

Note: Square brackets [ ] and curly braces { } should only be used in the letter body to enclose field names and built-in functions, respectively. They should not be used elsewhere in the text.

Note: A basic validation check is performed on the input text to ensure matching pairs of square brackets [ ] and curly braces { }. However, if you accidentally leave out a closing bracket ']' in one field and an opening bracket '[' In another, the validation may not catch the error, resulting in unexpected issues during processing. In such cases, carefully review and correct the text before rerunning the report.

Always print a trial copy of the letter and thoroughly verify the output for accuracy before proceeding with the final print.

As a final step, we will make a few adjustments to the following components:

  • The Crosstab Query

  • The Letter Editing Form

  • The Report Design

Update the SQL of the Yearwise_FreightQ1 Query:

  1. Open the Yearwise_FreightQ1 Query in SQL View, delete the existing SQL string, and copy and paste the updated SQL string provided below into the SQL window. This modification will add a new column that calculates the Total Amount by summing the sales figures (Freight values) for the years 1996, 1997, and 1998.

    After pasting the SQL, save the query.

  2. Note: Ensure you overwrite the old SQL code completely to avoid syntax errors.

  3. TRANSFORM Sum(Yearwise_FreightQ0.Sales) AS SumOfSales
    SELECT Yearwise_FreightQ0.EmployeeID,
     Sum(Yearwise_FreightQ0.Sales) AS TotalAmt
    FROM Yearwise_FreightQ0
    GROUP BY Yearwise_FreightQ0.EmployeeID
    PIVOT Yearwise_FreightQ0.Year;

    After saving the 'Yearwise FreightQ1', run the Make-table Query: Yearwise_IncentiveQ by double-clicking on it to create the output table 'Yearwise_incentive' with the new column TotalAmt, which we have used within the DLOOKUP() Function in the usage Example-2 above.

  4. Open the Mail editing Form (Letter) in design view, and create a Combo Box to display the Report Source Data Field Names, which the Users can reference and type correctly in the Body Text. See the image below:

    Inserting a Combo Box for Field Name Reference

    To help users insert field names accurately into the letter body text, follow these steps:

    1. Turn Off Control Wizards
      On the Toolbox, make sure the Control Wizards (the magic wand icon) is turned off.

      Click the wand icon to toggle it off before inserting the Combo Box.

    2. Insert the Combo Box

      • Click on the Combo Box control in the Toolbox.

      • Draw the Combo Box on the form — place it anywhere convenient, such as to the left of the body text area (Para1 and Para2).

    3. Set Combo Box Properties
      With the Combo Box selected, open the Property Sheet (F4 If not visible) and update the following properties:

    • Name = cboFields

    • Row Source Type = Field List

    • Row Source = LetterQ

    • Column Count = 1

    • Column Width = 1"

    • List Rows = 8

    • List Width = 1"

  5. Copy and paste the following revised Code into the VB Module of the above Form (Letter) for the cmdPreview Button:

    Private Sub cmdPreview_Click()
    On Error Resume Next
    Me.Refresh
    DoCmd.OpenReport "Letter", acViewPreview
    
    If Err > 0 Then
      MsgBox "Errors in Report source Data."
      Err.clear
    End If
    
    End Sub
  6. Save the Form with the above change.

  7. Open the Report named Letter in design view. Click the Para1 text box control and remove the field name Para1 from the Control Source property. Change the Name property to Para1x.

  8. Remove the Field Name Para2 from the second Text Box's Control Source property and change its Name Property to Para2x.

    Both Controls now show as Unbound Text Boxes.

  9. Copy and paste the following VBA Code into the VBA Module of the above Report:

    Private Sub Report_Open(Cancel As Integer)
    Dim xPara1, xPara2, ErrFlag1 As Boolean
    Dim ErrFlag2 As Boolean, x
    
    On Error Resume Next
    
    xPara1 = DLookup("Para1", "LetterQ")
    xPara2 = DLookup("Para2", "LetterQ")
    
    'submit para1 for parsing
    ErrFlag1 = False
    x = MailMerge(xPara1)
    Me![Para1x].ControlSource = x
    
    If Err > 0 Then
        ErrFlag1 = True
        Err.Clear
    End If
    
    'submit para2 for parsing
    ErrFlag2 = False
    x = MailMerge(xPara2)
    Me![Para2x].ControlSource = x
    
    If Err > 0 Then
        ErrFlag2 = True
        Err.Clear
    End If
    
    If ErrFlag1 Or ErrFlag2 Then
       MsgOK "Errors Found, Correct them and re-try."
    End If
    
    End Sub
  10. Save the Report after the changes.

  11. Open a new Global VBA Module in your Project. Copy and paste the following Main Programs and save the Module. The lines of code above the Function MailMerge() are Global Declarations and must appear at the topmost area of the Module.

    Type ParaTxt
        text As Variant
        status As Boolean
    End Type
    
    Type SpecRec
        LsStart As Integer
        Lsend As Integer
        LfStart As Integer
        Lfend As Integer
        Str As String
        fun As String
    End Type
    
    Dim V As ParaTxt, DatF() As SpecRec, DatF2() As SpecRec
    
    Public Function MailMerge(ByVal inpara) As String  
    '------------------------------------------------------  
    'Author : a.p.r. pillai  
    'Date   : 01-10-2007  
    'Remarks: Scan and Parse Text  
    '------------------------------------------------------
    Dim i As Integer, k As Long, L As Long
    Dim i2 As Integer, xpara, ypara, yxpara
    Dim j As Integer, xchar As String
    Dim qot As String, size As Long
    Dim curlbon As Boolean
    
    On Error GoTo MailMerge_Err
    
    yxpara = inpara
    
    V.text = inpara
    V.status = True
    
    qot = Chr$(34)
    
    strValidate 'run validation check
    
    If V.status Then
      MailMerge = yxpara
      Exit Function
    End If
    
    'scan for Merged Fields
    'ignore if embedded within built-in Function
    
    xpara = V.text
    
    i = 0
    For j = 1 To Len(xpara)
      xchar = Mid(xpara, j, 1)
      If xchar = "{" Then
         curlbon = True
      End If
      If xchar = "[" And curlbon = False Then
         i = i + 1
      ElseIf xchar = "}" And curlbon = True Then
          curlbon = False
      End If
    Next
    
    If i > 0 Then
      i = i + 1
      ReDim DatF2(1 To i)
    Else
      GoTo chkFunction
    End If
    
    'Parse embedded fields
    L = 1: curlbon = False
    For j = 1 To Len(xpara)
      If j = 1 Then
        DatF2(L).LsStart = 1
      End If
    
      xchar = Mid(xpara, j, 1)
      If xchar = "{" Then
         curlbon = True
      End If
      If xchar = "[" And curlbon = False Then
        DatF2(L).Lsend = j - 1
           size = DatF2(L).Lsend - DatF2(L).LsStart + 1
           DatF2(L).Str = Mid(xpara, DatF2(L).LsStart, size)
           DatF2(L).LfStart = j
      End If
      If xchar = "]" And curlbon = False Then
           DatF2(L).Lfend = j
           size = DatF2(L).Lfend - DatF2(L).LfStart + 1
           DatF2(L).fun = Mid(xpara, DatF2(L).LfStart, size)
           L = L + 1
           DatF2(L).LsStart = j + 1
      End If
      If xchar = "}" And curlbon = True Then
          curlbon = False
      End If
    
    Next
    DatF2(L).Str = Mid(xpara, DatF2(L).LsStart)
    DatF2(L).fun = ""
    
    'create output from parsed string
    ypara = ""
    For j = 1 To L - 1
      If j = 1 Then
        ypara = DatF2(j).Str & qot & " & " & DatF2(j).fun
      Else
        ypara = ypara & " & " & qot & DatF2(j).Str & qot & " & " & DatF2(j).fun
      End If
    Next
    
    ypara = ypara & " & " & qot & DatF2(j).Str
    If Len(DatF2(j).fun) > 0 Then
       ypara = ypara & qot & " & " & DatF2(j).fun
    End If
    
    xpara = ypara
    
    chkFunction:
    
    'scan for embedded built-in functions
    i2 = 0
    For j = 1 To Len(xpara)
      If Mid(xpara, j, 1) = "{" Then
        i2 = i2 + 1
      End If
    Next
    
    If i2 > 0 Then
      i2 = i2 + 1
      ReDim DatF(1 To i2)
    Else
      GoTo Finish
    End If
    
    'parse built-in functions
    L = 1
    For j = 1 To Len(xpara)
      If j = 1 Then
        DatF(L).LsStart = 1
      End If
      If Mid(xpara, j, 1) = "{" Then
        DatF(L).Lsend = j - 1
           size = DatF(L).Lsend - DatF(L).LsStart + 1
           DatF(L).Str = Mid(xpara, DatF(L).LsStart, size)
        DatF(L).LfStart = j + 1
      End If
      If Mid(xpara, j, 1) = "}" Then
        DatF(L).Lfend = j - 1
           size = DatF(L).Lfend - DatF(L).LfStart + 1
           DatF(L).fun = Mid(xpara, DatF(L).LfStart, size)
        L = L + 1
        DatF(L).LsStart = j + 1
      End If
    Next
    DatF(L).Str = Mid(xpara, DatF(L).LsStart)
    DatF(L).fun = ""
    
    'format the paragraph
    ypara = ""
    For j = 1 To L - 1
      If j = 1 Then
        ypara = DatF(j).Str & qot & " & " & DatF(j).fun
      Else
        ypara = ypara & " & " & qot & DatF(j).Str & qot & " & " & DatF(j).fun
      End If
    Next
    
    ypara = ypara & " & " & qot & DatF(j).StrIf
     Len(DatF(j).fun) > 0 Then
       ypara = ypara & qot & " & " & DatF(j).fun
    End If
    
    Finish:
    
    'if there is no value for merging then
    If i2 = 0 And i = 0 Then
      ypara = yxpara
    End If
    
    xpara = "=" & qot & ypara & qot
    
    MailMerge = xpara
    
    MailMerge_Exit:
    Exit Function
    
    MailMerge_Err:
    MsgBox Err.Description, , "MailMerge()"
    MailMerge = ""
    Resume MailMerge_Exit
    End Function

    Public Function strValidate()  
    '------------------------------------------------------  
    'Author : a.p.r. pillai  
    'Date   : 01-10-2007  
    'Remarks: Pre-parsing validation check  
    'Returned Valule = False, if no errors in Expressions  
    '------------------------------------------------------   
    Dim xpara, j As Long, xchar As String   
    Dim msg As String, flag As  Boolean   
    Dim SBopen As Integer, SBCIose As Integer   
    Dim CBopen As Integer, CBclose As Integer   
    Dim str1 As String, str2 As String
    
       On Error GoTo strValidate_Err
    
        xpara = V.text
        xpara = Trim(xpara)
    
        SBopen = 0: SBCIose = 0
        CBopen = 0: CBclose = 0
        str1 = "missing for built-in Function(s)."
        str2 = "missing for Fieldname(s)."
    
        For j = 1 To Len(xpara)
            xchar = Mid(xpara, j, 1)
           Select Case xchar
                Case "["
                    SBopen = SBopen + 1
                Case "]"
                   SBCIose = SBCIose + 1
                Case "{"
                    CBopen = CBopen + 1
                Case "}"
                    CBclose = CBclose + 1
            End Select
        Next
        msg = ""
        If SBopen = SBCIose Then
           GoTo nextstep
       Else
           If SBopen > SBCIose Then
             msg = "1. Closing ] " & str2
             flag = True
           Else
             msg = "1. Opening [ " & str2
             flag = True
          End If
       End If
    nextstep:
        If CBopen = CBclose Then
           GoTo FinalStep
        Else
           If CBopen > CBclose Then
             If flag Then
              msg = msg & vbCr & "2. Closing } " & str1
             Else
               msg = "1. Closing } " & str1
             flag = True
             End If
           Else
            If flag Then
                msg = msg & vbCr & "2. Opening { " & str1
            Else
                msg = "1. Opening { " & str1
               flag = True
             End If
           End If
       End If
    
    FinalStep:
       If flag Then
          msg = "Errors found in field/function definitions." & vbCr & vbCr & msg & vbCr & vbCr & "Program Aborted. " & vbCr & "Correct the errors and re-try."
          MsgBox msg
          V.status = True
        Exit Function
       End If
    
      V.status = False
    
    strValidate_Exit:
    Exit Function
    
    strValidate_Err:
    MsgBox Err.Description, , "strValidateQ"
    strValidate = True
    Resume strValidate_Exit
    End Function
  12. In Case of Errors

    Note: If you encounter errors while compiling or running the program for the first time, it may be due to missing library references. To fix this, ensure all essential library files are correctly linked to your project.

    For a list of required library files and step-by-step guidance on linking them, visit the Page Command Button Animation article.

    The validation program performs a basic check on the input data and displays warnings if any inconsistencies or errors are detected.

    Open the letter editing form (Letter) and test the examples described earlier by inserting field names, built-in functions, or expressions using field values as parameters. Click the Preview command button to generate the report and verify that the merged output appears correctly.

    Although simple in design, this program is highly effective within its controlled environment. It is user-friendly and serves as a powerful tool for enhancing your projects.

    Any suggestions for improvement of the program are welcome.

    Downloads



Share:

MS-Access and Mail Merge-2

Design Form Letters.

A form letter can serve various purposes, such as an invitation to club members, a notice to company shareholders, a circular to department heads, or a forwarding memo for departmental telephone expense statements. In all these cases, the body of the letter remains the same, while the recipient’s details vary from page to page.

When planning a form letter in Microsoft Access, it's essential to organize the following five elements:

  1. Design a Letter Table.
    Create a table to hold the main letter content. This table should include fields such as Memo Reference, Date, Subject, Body Text, Footer information (e.g., Department Head's name), and other relevant details. Enter a single record into this table to serve as the letter's template.

  2. Create a Letter Editing Form.
    Design a form based on the Letter Table to allow easy editing of the body text and other details. This makes updating the letter content straightforward whenever changes are needed.

  3. Prepare a Sub-Report (if applicable).
    If the letter requires inserting dynamic content, like a department-wise telephone expenses statement, design and process the necessary sub-report. If no such statement is needed, this step can be skipped.

  4. Join Letter and Recipient Address.
    Use a query to join the Letter Table with the Address Book or recipient list. This will allow you to merge personalized address data with the letter content.

  5. Design the Main Form Letter Report.
    Finally, design the report that serves as the form letter. Insert the editable letter fields, recipient address fields, and any sub-reports (if used). Format the layout to suit your printing requirements.

    A sample image of the completed form letter we're aiming to create is shown below. Take a moment to review it—it will give you a clear understanding of the final result and how each step in the process contributes to building the letter.


The Design Task

We need two sample tables, Employees and Orders, from the Northwind.mdb Database. (Refer to my earlier post 'Saving Data on Forms not in Table' for the exact location reference to the sample file). Import the above Tables from the Northwind.mdb database.

As you can see in the form letter above, the addressee information is drawn from the Employees table. The statement shown in the middle of the letter is generated from the Orders table. Both tables share a common field, EmployeeID, to link the embedded statement to the corresponding employee. This ensures that the statement displayed in the letter pertains to the correct individual. All other content—including the memo reference, date, subject, body text, and footer details—is sourced from the Letter table mentioned earlier.

  1. Design a Table with the following Field Structure and save it with the name Letter:
  2. Open the Table in the datasheet view, enter the text HRD/ABCD/001 in the FRefNo Field to add a record, and close it.

  3. Select the Letter Table, select Form from the Insert Menu, and put the Form in Design View.

  4. Design a Form, like the sample Form image shown below.

  1. Create a macro to close the form by following these steps:

  2. Select the Close button, open its Property Sheet, change its Name property to 'cmdClose', and assign a macro to its On Click event to close the form.

  3. Next, select the Preview button, display its Property Sheet, and rename it to 'cmdPreview'. We will add a small VBA subroutine for this button later.

  4. For the Subject text box, open its Property Sheet and set the Scroll Bars property to Vertical. Make the same change for the Para1  Para2 text boxes to allow scrolling for longer text.

  5. Finally, save the form as Letter.

  6. The Letter Image is given below with the sample data filled in:

    Inserting a Statement.

  7. To prepare the embedded statement for the Form Letter (as shown in the earlier image), we will use three columns from the Orders table: EmployeeID, OrderDate, and Freight. For demonstration purposes, we will treat the Freight value as the Sales Figure for each employee and extract the year from the OrderDate field to represent the Sales Year.

    We will create three queries to build a Report Table. Follow these steps:

    1. Open a new query in SQL View (do not select any table when prompted).

    2. Copy and paste the SQL string provided for each query.

    3. Save each query with the name indicated after the code block.

    This setup will help organize the sales summary data by employee and year for use in the form letter.

    • Query Name: Yearwise_FreightQ0

      SELECT Orders.EmployeeID,
       Val(Format([OrderDate],"yyyy")) AS [Year],
       Sum(Orders.Freight) AS Sales
      FROM Orders
      GROUP BY Orders.EmployeeID, Val(Format([OrderDate],"yyyy"));
    • Query Name: Yearwise_FreightQ1

      TRANSFORM Sum(Yearwise_FreightQ0.Sales) AS SumOfSales
      SELECT Yearwise_FreightQ0.EmployeeID
      FROM Yearwise_FreightQ0
      GROUP BY Yearwise_FreightQ0.EmployeeID
      PIVOT Yearwise_FreightQ0.Year;
    • Query Name: Yearwise_IncentiveQ

    SELECT YearWise_FreightQ1.* INTO Yearwise_Incentive
    FROM YearWise_FreightQ1;

    Note: Be careful when naming your Make-Table Query and the Target Table it creates. Do not give them the same name — doing so can cause errors or unexpected behavior when the query is executed.

    For example, in the query named Yearwise_IncentiveQ I’ve added a “Q” at the end to distinguish it from the table it creates. You are free to use any naming convention you prefer, as long as the query name and the target table name are not identical.

    In the first query, we extract data from the Orders table and create a year-wise summary of the Freight values, renaming it as Sales. The second query is a Crosstab Query, which transposes the data so that the years appear as column headers.

    However, since Crosstab Queries cannot be used directly in Reports, we use a Make-Table Query to store the transposed results in a new table named Yearwise_Incentive. This allows us to use the data easily in reports or for further processing.

  8. After you have created and saved the third query, double-click on it to run. This will generate the output table named Yearwise_Incentive. If you receive any warning messages during the process, simply click OK to proceed.

  9. Design a Report using the Table Yearwise_Incentive  as the record source, following the layout shown in the sample image (refer to your earlier example). The report width should be approximately 6.5 inches.

    • Do not use the Page Header/Footer sections.

    • Place all field heading labels in the Report Header section.

    • Arrange the fields and their Labels in the Detail section.

    • Once completed, save the report with the name: Yearwise_Incntv.

  10. We need to create one final query to join the Employees table with the Letter table (created earlier to store the memo reference, subject, and body text).

    To do this:

    1. Open a new query in Design View, but do not add any tables yet.

    2. Switch to SQL View.

    3. Copy and paste the following SQL string into the SQL window:

      SELECT Letter.*,
       Employees.EmployeeID,
       Employees.TitleOfCourtesy,
       Employees.FirstName,
       Employees.LastName,
       Employees.Title,
       Employees.Address,
       Employees.City,
       Employees.Region,
       Employees.PostalCode,
       Employees.Country
      FROM Employees, Letter;

    4. Save the query with the name: LetterQ.

  11. After saving the query, open it in Design View and examine how the Employees and Letter tables are arranged on the design surface. Note that the Letter table contains only one record, while the Employees table has nine. Since we haven't defined a join between the two tables, Access performs a Cartesian product, repeating the single record from the Letter table for each record in the Employees table. When you open the query in Datasheet View, you'll see the Letter content duplicated for every employee.

    NB: If there are more records in the Letter Table, those records are repeated for each record in the Employees table, which will print more than one letter for each employee.

  12. Here’s why this happens:

    • In your query LetterQ No JOIN condition is specified between the Employees table and the Letter table.

    • Because of that, Access performs a Cartesian Product (also called a cross join):

      Each record in the Employees table is combined with every record in the Letter table.

    • Since the Table Letter contains only one record with some common standard text, the end result is that this single record content is repeated for each employee in the Employees Table.

    What This Means in Practice:

    When you use this query as the Record Source for your form letter report:

    • Each page (or section) of the report will show one employee’s information,
      along with the same shared letter content from the Letter table.

    • This is exactly what we want when preparing a bulk personalized form letter
      Each employee gets a copy of the same letter, with their name, address, and employee details.

    Next Steps: You can now use the LetterQ query as the Record Source for your final report — the actual Form Letter Report.

  13. Design the Main letter using the LetterQ query as shown below.

    Take a close look at the image. The Report does not use the Report Header/Footer or Page Header/Footer sections. Instead, the memo’s header is designed within the FirstName Header section. To create this section, click the Sorting and Grouping button on the toolbar, or choose Sorting and Grouping from the View menu. In the dialog that appears, select FirstName in the Field/Expression column. Then, in the Group Properties section below, set Group Header and Group Footer to Yes. Once the FirstName Header section appears in the report design, position your mouse over the top edge of the Detail section (the horizontal bar). When the pointer changes to a double-headed arrow, click and drag downward to create enough space for designing the memo's header layout.

    The design is complete when we finish the following few more steps:

    Inserting the Statement

    • To insert the Yearwise_Incntv statement into the main report, place it between Para1 and Para2, and link it to the EmployeeID field that was added separately in the FirstName Header section.

      To do this:

      1. Move the main report slightly to the right side of the screen, next to the Database Window, so both are visible side by side.

      2. If the Yearwise_Incntv report is not visible, click its tab to bring it into view.

      3. Click and drag the Yearwise_Incntv report into the Detail section of the main report, positioning it between the Para1 and Para2 text boxes.

      4. Align and size the inserted report properly to fit the layout as shown in the reference image.

      5. If the main report’s width has expanded to the right during this process, resize it appropriately to maintain the original 6.5" width.

      Make sure the Subreport control is properly linked using EmployeeID as the master and child field so that the correct data appears for each employee.

    • Click on the Yearwise_incntv statement, display the Property Sheet, and insert EmployeeID in the Link Child Field and Link Master Field properties.

    • Display the Property Sheet of the EmployeeID control in the Header section and set its Visible Property Value to  False.

    • Display the Property Sheet of the control and set the 'Can Grow', 'Can Shrink' properties to Yes.

    • Change the 'Can Grow', 'Can Shrink' properties of Para1 and Para2 also to Yes.

    • Click the FirstName Footer, select the property sheet, and change the 'Force New Page' property value to After Section.

    • Name the Report as Letter and Save it.

    Preview of Report.

  14. Open the report in Print Preview and use the Page Navigation controls at the bottom to move through the pages. As you advance, verify that the Employee Name displayed in the address section at the top of each page changes appropriately. Also, ensure that the Employee Name shown in the statement inserted between the paragraphs matches the one in the address section. Once you've confirmed that the data is displaying correctly, close the report.

  15. Open the Letter Form in the design view.

  16. Click on the Preview button and display the Property Sheet. Select [Event Procedure] on the On Click Event property and write the following code in the Form Module and save the Report:

    Private Sub cmdPreview_Click()
       Me.Refresh
       DoCmd.OpenReport "Letter", acViewPreview
    End Sub
  17. Open the Letter Form in a normal view. Make some changes in the body text. Click on the Preview button. The changes that you have made in the text should also be reflected in the previewed letter.

  18. Your Report display should match the sample image given at the top of this page.

    With this article, we have laid the groundwork for merging data field values and built-in functions in the body text of a letter. In the next step, we will explore how to incorporate these elements and print the final document.

    Downloads.



Share:

MS-Access and Mail Merge

Design Address Labels.

When we think of address labels, form letters, and mail merge tasks, Microsoft Word is usually the first tool that comes to mind, thanks to its wide range of features designed for these purposes. However, Word requires a data source to supply the content for address labels, form letters, and similar documents. You can either create a table directly within a Word document for recording address data or connect to an external source, such as a Microsoft Access table or another database.

Instead of using Microsoft Word to create address labels, form letters, and perform mail merge operations in Microsoft Access. Yes, you read that right; we’ll explore how to handle Mail merge directly within Access itself.

While Access may not offer the advanced paragraph formatting features of Word, it remains highly effective for generating form letters, particularly for agency agreement renewals, Bank Guarantee renewal reminders, or department-wise monthly reports on stationery or telephone expenses. These letters usually consist of one or two standard paragraphs combined with actual data, which Access can quickly assemble and print with minimal effort.

Designing the Address Labels.

We’ll begin with a simple task: designing address labels. MS Access includes a built-in Label Wizard that makes it easy to create address labels of various sizes and layouts. It offers a wide selection of predefined label sizes from different manufacturers, making it ideal for producing hundreds or even thousands of labels on continuous stationery in standard formats.

Ordinary people like me who would like to print on plain paper or cut and paste on envelopes can try the manual method.

A sample image of the output created from a manually designed Address Label is shown below, and we will go through the procedure needed to prepare them:


The Design Task.

  1. Import the Employees table from the Northwind.mdb sample database if you haven’t already done so in the earlier sessions. For guidance on locating the sample database, refer to my previous post.

    Saving Data on Forms, not in a Table, for the exact location reference of the sample file.
  2. Select the Employees Table.

    Select Report from the Insert Menu and select Design View.
  3. Go to the View menu and remove the checkmark from Page Header / Footer and Report Header / Footer options to remove them from the report design, if they are visible.

  4. Draw a Text Box approximately 3.25" wide at the top and left area of the Detail Section, leaving enough space for the border as shown in the design given below:

  5. Display the TextBox Property Sheet from the View menu and set the Special Effect Property = Chiseled. If you don't like the underlined design, stay with the Flat property setting.

  6. Write the expression as shown above in the Control Source Property. The partially visible field is [LastName]; don't forget the closing brackets.

  7. Copy the same Text Box, paste it four times down, and write the expression as shown above.

  8. Select all five Text Boxes together. Select Vertical Spacing -> Decrease from the Format Menu. Repeat the process for all the Text Box controls and see that all come close together.

  9. Draw a Rectangle around the TextBoxes. If the Rectangle hides the TextBoxes, then select Transparent from the Fill/Back Color Toolbar Button. When the Label is cut from plain paper, the Border will give the label a proper shape, even if the cutting is not.

  10. Now, we must go to the final settings on the Page Setup Control. Select Page Setup from the File Menu.

  11. Select the Columns Tab.

  12. Change the Number of Columns to 2.

  13. Change Column Spacing to 0.15"

  14. In the Column-Size control, insert a checkmark in Same as Detail if it is not appearing there.

  15. Column Layout: Across, then Down.

  16. Click OK and save the Report as Address Labels.

  17. Open the report in Print Preview; it should resemble the sample image shown at the top of this page.

Next, we will look into setting up and Printing Form Letters.

Share:

MS-Access Object Documenter

Take a detailed printout of any Access Object,

Typically, we prepare reports based on processed or raw data and print them for sharing or documentation purposes.
However, there are times when we need detailed information about the database objects themselves, such as Tables, Queries, Macros, VBA Modules, and other elements.
In such cases, we may require comprehensive listings that include:

  • Table fields along with their data types, sizes, and properties

  • Control names and properties on Forms or Reports

  • User-level and group-level permission settings

  • Macro and Module content summaries

These object-level reports can be invaluable for documentation, auditing, or troubleshooting purposes.

Table Properties Listing.

We will generate a table's detailed listing that includes field names, data types, field sizes, and index information.

In this example, we've used the Employees table, but you can apply the same procedure to any table in your database.

A sample listing format is shown below for reference:

  1. Select Tools - - > Analyze - - > Documenter.

  2. Click on the Tables Tab.

  3. Put a Check Mark on any of your Tables.

  4. Click on the Options button to open the Options Dialog Control.

  5. Change the settings as shown above and click OK to close it.

  6. Click OK on the first Dialog control to format the Listing based on selected options and to open it in Print Preview.

Note: Each type of Object has its own option settings that produce detailed information based on their selections.

Share:

Useful Report Functions

Page Number Function: 'PageNo()'

The following are standard functions that you can use in the Report Header or Footer sections when designing reports.

To use them, simply copy and paste the VB code into a global module in your database and save it.

Write the Function Formula in Text Boxes as shown in the Syntax.

Function to display formatted Page Numbers.

Function: 'PageNo()'.

Syntax: =PageNo([page],[pages])

Result: Page: 1 / XXX, where XXX is the maximum number of pages of the Report.

Note: [page],[pages] are MS Access built-in Report Variables and must be used as shown.

Code:

Public Function PageNo(ByVal pg As Variant, _ByVal pgs As Variant) As String
'----------------------------------------------------------
'Output   : Page: 1/25
'         : Call from a Report Text Box control
'Author   : a.p.r. pillai
'Date     : 01/09/2007
'Remarks  : The Formatted Text takes up 15 character space
'----------------------------------------------------------
Dim strPg As String, k As Integer

On Error GoTo PageNo_Err

pg = Nz(pg, 0): pgs = Nz(pgs, 0)
strPg = Format(pg) & "/" & Format(pgs)
k = Len(strPg)

If k < 15 Then
   strPg = String(15 - k, "*") & strPg
End If

strPg = "Page: " & strPg

For k = 1 To Len(strPg)
  If Mid(strPg, k, 1) = "*" Then
    Mid(strPg, k, 1) = Chr(32)
Next

PageNo = strPg

PageNo_Exit:
Exit Function

PageNo_Err:
Msgbox Err.Description,, "PageNo()"
PageNo = "Page : "
Resume PageNo_Exit
End Function


Report Period Function: Period()

Function to print Period with formatted Start-Date and End-Date in Report Header or Footer.

Function : Period()

Syntax : =Period([StartDate], [EndDate])

Result : Period: 15/09/2007 To 30/09/2007

Note: The format string in the Code may be modified to a country-specific date format.

Code:

Public Function Period(ByVal prdFrm As Date, _ByVal PrdTo As Date) As String
'-----------------------------------------------------------------
'Output   : Period: dd/mm/yyyy To dd/mm/yyyy
'         : Call from Report control to insert date
'Author   : a.p.r. pillai
'Date     : 01/09/2007
'Remarks  : Modify Format String for Country specific date format.
'-----------------------------------------------------------------

On Error GoTo Period_Err

Period = "Period: " & Format(prdFrm, "dd/mm/yyyy") & " To " & Format(PrdTo, "dd/mm/yyyy")

Period_Exit:
Exit Function

Period_Err:
MsgBox Err.Description,, "Period()"Resume Period_Exit
End Function


Report Date Function: Dated()

Function to print formatted System Date in the Header or Report Footer.

Function : Dated()

Syntax: Dated()

Result : Dated: 15/09/2007

Code:

Public Function Dated() As String
'----------------------------------------------------------------
'Output  : Dated: 20/08/2007
'        : Call from Report Text Box control
'Author  : a.p.r. pillai    Date    : 01/09/2007
'Remarks  : Change Format String for Country specific Date Format
'----------------------------------------------------------------
On Error GoTo Dated_Err

Dated = "Date: " & Format(Date, "dd/mm/yyyy")

Dated_Exit:
Exit Function

Dated_Err:
MsgBox Err.Description,, "Dated()"
Resume Dated_Exit
End Function 

You can design the full Page Footer of a Report with the Date and Page Number at one go with a single Function. Read my earlier Article: Reports ... Page Border. Use the Function DrawPageFooter() Code & procedure explained there.

You can add frequently used Expressions or Routines as Public Functions into a Global Module of your Database and run them wherever you need them (Forms, Reports, Query Expressions, etc.) rather than repeating the code everywhere.

Library Database with User-Defined Functions.

You can further enhance the use of commonly used functions by organizing them into a separate library database and linking it to your new projects. This way, any shared functions or even reusable forms—such as a custom Form Wizard—can be centrally maintained in the library database.
When you call a library function that references a form, Microsoft Access will first look for the form in the library database. If it’s not found there, it will then check in the current project and open it from there.

Follow the procedure outlined in the earlier post, Command Button Animation, to link the essential library files to your project. If your library database does not appear in the list of installed references, use the Browse button to locate it manually. Once found, attach it to the references list and select it to make its functions and objects available in your project.

You can save this Library file in a compiled state by converting it into an MDE File. Select Tools -> Database Utilities -> Make MDE File to convert and save the current database into MDE Format. You cannot edit the code in the MDE database. Preserve the MDB file for future changes and compilation if it becomes necessary.

When installing your project, ensure that the common library database is included and properly linked to your project in the new location. The same rule applies to any other built-in library files used by your application. Keep in mind that the version of certain built-in libraries on the target machine may differ from those used during development. This can occur if other Visual Basic–based applications were previously installed on the system. In such cases, these libraries may appear as MISSING in the References list. You must then reattach the correct or available version of the library from the installed location to resolve the issue.

If your Project is shared by different Versions of MS Office Applications, then it is a good idea to attach an older version of the built-in library file (if available) to the project.

Refer to the earlier Post on Sharing an Older Version Database under the topic: MS Access Security.

Earlier Post Link References:

Share:

Reminder Report POPUPs

Reminders and Report Pop-up.

The Reminder Popup is a specially designed report that opens automatically, displays important content, and plays a background sound to draw the user's immediate attention. It is exported in Access Snapshot File Format (available in Microsoft Office 2000 and later), allowing it to open in a separate window independent of Microsoft Access.

Reports in Snapshot format can be viewed without Access, making them ideal for sharing via email or transporting as standalone files.

This reminder pop-up is useful for upcoming events—such as appointments, conferences, or birthdays—that fall within the next 7 days and require advance notice. The pop-up functionality is triggered through the Form_Timer() event procedure is coded in the Control Screen module.

The Demo Project.

Let’s try an example using the Employees table from the Northwind.mdb sample database. Begin by importing the Employees table into your current database.

If you're unsure about the location of the sample Northwind.mdb file, refer to the instructions provided on the page Saving Data on Forms Not in Table for guidance on locating it.

After importing the Employees table, create a new query using the SQL string provided below.
To do this, open a new query in Design View—when prompted, do not select any tables or queries from the list.

Instead, switch to the SQL View and paste the SQL string into the window.
Save the query as Birthday_Reminder.

SELECT Employees.EmployeeID,
 [TitleofCourtesy] & " " & [FirstName] & " " & [LastName] AS Name,
 Employees.BirthDate,
 DateValue(Format([birthdate],
 "dd-mm") & "-" & Year(Date())) AS BirthDay,
 DateDiff("yyyy",[birthdate],
 [BirthDay]) AS age
FROM Employees
WHERE (((DateValue(Format([birthdate],"dd-mm") & "-" & Year(Date()))) Between Date() And Date()+30));

Open the Birthday_Reminder query in Datasheet View to check if any records are returned based on the specified criteria.
If no records appear, open the Employees table and modify a few records by changing the Birth Date field so the month falls within the current month or within the next 30 days.

At the end of the query’s SQL string, you'll notice a condition like Date() + 30 used for testing.
You can adjust this value in Design View to suit your needs—for example, +7 or +15—depending on how many days in advance you want to be notified of upcoming birthdays or events.

Design a Report using Birthday_Reminder Query as Record Source, like the sample given below, and save the Report with the name Birthday_Reminder:

Copy and paste the following Code into the VB Module of the Control Screen or the Main Screen of your Application.

'Global Declaration
Dim T As Long
'-------------------------------------------------------
Private Sub Form_Load()
Dim RCount

On Error GoTo Form_Load_Err
DoCmd.Restore

T = 0
RCount = DCount("*", "BirthDay_Reminder")
'if no records in BirthDay_Reminder Query then
'control is not passed into the Timer Event procedure

If RCount > 0 Then
    Me.TimerInterval = 250
End If

Form_Load_Exit:
Exit Sub

Form_Load_Err:
MsgBox Err.Description, , "Form_Load"
Resume Form_Load_Exit
End Sub


Private Sub Form_Timer()
On Error GoTo Form_Timer_Err

T = T + 1
Select Case T
    Case 20
        REMPOPUP
        'Me.Timerinterval = 0
    Case 260
        REMPOPUP
    Case 261
      T = 21
End Select

Form_Timer_Exit:
Exit Sub

Form_Timer_Err:
MsgBox Err.Description, , "Form_Timer"
Resume Form_Timer_Exit
End Sub

Private Function REMPOPUP()
Dim strPath As String, i As Integer, mplayerc As String
Dim mplayer As String, soundC As String
On Error GoTo REMPOPUP_Err

mplayerc = "C:\Program Files\Windows Media Player\mplayer2.exe " 'WindowsXP

soundC = "C:\Windows\Media\notify.wav" 'WindowsXP

'if media player2 not found then don't play sound
If Len(Dir(mplayerc)) > 0 Then
     mplayer = mplayerc & soundC
    Call Shell(mplayer, vbMinimizedNoFocus)
End If

strPath = "C:\Windows\Temp\BirthDay_Reminder.snp"

DoCmd.OutputTo acOutputReport, "BirthDay_Reminder", _"SnapshotFormat(*.snp)", strPath, True, ""

'if snapshot format is not available 
'disable the previous line and enable next line

'DoCmd.OpenReport "BirthDay_Reminder", acViewPreview

REMPOPUP_Exit:
Exit Function

REMPOPUP_Err:
MsgBox Err.Description, , "REMPOPUP"
Resume REMPOPUP_Exit
End Function

The Trial Run

When the main form containing the code is opened, the Form_Load() event procedure checks whether the Birthday_Reminder query returns any records.

If records are found, the Form_Timer() event is activated, running at an interval of 250 milliseconds ( quarter of a second), incrementing a globally declared variable T.

When the value of T reaches 20 (after 5 seconds), the REMPOPUP() procedure is executed. This opens the Birthday_Reminder report in Access Snapshot Format. At the same time, Windows Media Player plays the notify.wav sound file to alert the user.

After that, the Reminder Popup opens at hourly intervals. If this repetition is not required, then enable the statement (remove the ' character at the beginning).

Me.Timerinterval = 0

During the Form_Timer() event procedure, the Popup opens only once per Session.

Download Demo Database.

Share:

MS-Access and Graph Charts2

Continuation of Last Week's Discussion

If you have landed straight on this page, please go through the earlier Post MS-Access & Graph Charts and then continue.

Sample Data for Pie Chart
Desc Veh Sales Parts Sales Service Sales
Total Sales 450000 645000 25000
  1. Create a Table with the above structure and sample data, and save the Table as pie_Table.
  2. Open a new Report in the design view. Select the Object option from the Insert Menu, select Microsoft Graph-Chart, and then click OK. A Chart Object with default values is inserted into the Report.
  3. Click outside the chart to deselect and disable the Edit Mode. Click again on the chart to select it, display the property sheet, and change the following values:
    • Size Mode = Zoom
    • Row Source Type = Table/Query
    • Row Source = Pie_Table
    • Column Heads = Yes
    • Left = 0.3"
    • Top = 0.3"
    • Width = 6.0"
    • Height = 4.0"
  4. Creating and Formatting a 3-D Pie Chart.

    1. Double-click on the chart control in your report or form to open the Chart Formatting Toolbar.

    2. From the Chart Type Toolbar, select 3-D Pie Chart.
      Alternatively:

      • Right-click on a blank area inside the chart (not on the pie itself).

      • Choose Chart Type from the shortcut menu.

      • Select 3-D Pie Chart and click OK.

    3. Open the Pie_Table:

      • Click on the top-left corner of the datasheet (grid) to select all data.

      • Select Edit > Copy from the menu.

    4. Go back to your chart:

      • Click the top-left corner of the chart's datasheet grid.

      • Select Edit > Paste to paste the data.

    5. Remove extra sample data:

      • If any extra rows or columns remain after pasting, select them and use Edit > Cut to delete.

    6. Resize the Pie:

      • Click on the shaded area surrounding the pie to select it.

      • Drag the bottom-right sizing handle outward to enlarge the pie slightly.

    7. Format the Plot Area:

      • Right-click on the shaded area around the pie.

      • Select Format Plot Area from the shortcut menu.

      • Set Area Options to None.

      • Set Border Options to None, then click OK.

    8. Set Chart Title and Data Labels:

      • Right-click on an empty area of the chart.

      • Select Chart Options.

      • On the Titles tab, enter "Total Revenue" in the Chart Title box.

      • Switch to the Data Labels tab, check the Percentage option under "Label Contains".

      • Click OK to apply your changes.

A chart with more than one set of Bars.

Table1
Desc Qtr1 Qtr2 Qtr3 Qtr4
A_Revenue 25000 35000 20000 40000
B_Expenses 15000 20000 13000 17000
C_Income 10000 15000 7000 23000

Create a table using the field structure and data provided above, and save it as Table1. Then, follow the same steps outlined in the earlier post, MS-Access and Graph Charts, starting from Step 4, to create the chart shown below. In Step 5, set the Row Source property to Table1.

The completed Bar Chart, created using the sample data above, illustrates the quarterly performance of individual areas—Revenue, Expenses, and Income—and is shown below.

Customizing Chart Y-axis Scale Values

The Y-axis scale of the chart, along with the major unit intervals (e.g., 0, 5000, 10000), is automatically calculated and displayed by MS Access. However, when the chart data contains smaller values, it may be necessary to adjust these intervals for better visibility. In such cases, you can manually customize the Y-axis scale to use smaller unit intervals as needed.

To adjust the Y-axis scale manually, double-click on the chart to enter edit mode. Then, right-click on the Y-axis (the vertical line displaying the scale values) and select Format Axis from the shortcut menu. In the dialog box that appears, go to the Scale tab and modify the values as needed. For example, you can change the Minimum, Maximum, or Major Unit settings to better suit your chart data.

  • Minimum = 0
  • Maximum = 51000
  • Major Unit = 3000

Leave the other values unchanged. Click OK to update the new scale settings on the Chart.

Once you manually change the Y-axis scale settings, they will remain fixed, even if the actual chart values exceed the defined maximum. In such cases, you must update the maximum value manually to ensure all data points are displayed correctly. Alternatively, you can enable automatic scaling by checking all relevant options in the Scale tab, allowing MS Access to recalculate and adjust the scale values dynamically as the data changes.

Formatting Data Labels.

You can adjust the alignment of the chart’s data labels to improve readability. Right-click on any label and choose Format Data Labels from the shortcut menu. Go to the Alignment tab and select one of the diamond-shaped icons under Orientation to change the label direction. Feel free to experiment with the other alignment options to find the best fit for your chart layout.

You can display the actual data table used to generate the chart alongside the chart itself. To do this, double-click on the chart to enter edit mode. Then, right-click in an empty area outside the plot area and select Chart Options. Navigate to the Data Table tab and check the Show Data Table option.

Secondary Y-axis Usage.

Sometimes, we need to display smaller values alongside much larger ones on the same chart. For example, if the income values for all four quarters are below 3000, their bars or lines (in a line chart) may appear too small, making it difficult to compare them effectively.

In such scenarios, using a Secondary Y-axis allows you to scale smaller values independently, enhancing the visibility of bars or lines representing those values. Including data labels further improves clarity. The sample image below illustrates this, with Income values (colored bars) plotted on the secondary Y-axis.

Adjusting Bar Width.

To reduce the thickness of the blue bars and make them as narrow as the other bars, we need to increase the gap between them. Double-click the chart to enter edit mode, then right-click one of the blue bars and select Format Data Series. On the Options tab, set the Gap Width value to 340, and click OK to apply the changes to the chart.

The Image of a Chart plotted with the same values in Custom Chart Type Tubes is given below:

  1. MS-Access and Graph Charts
  2. MS-Access and Graph Charts-2
  3. Working With Chart Objects in VBA
  4. Column Chart and VBA
  5. Pie Chart Object and VBA
  6. Missing Lines in Line Chart
  7. Pie Chart and Live Data on Form
  8. Scaling Chart Object
  9. Cross-Tab Union Queries for Chart
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