Access and SQL
Part 6: Dynamic Reports
An Access Report is similar to an Access Form. It displays data from a record source you specify (a table or query) and you can customize the way the data is displayed through its design. In fact designing a report uses the many of the same techniques as designing a form. Forms are chiefly used for entering and editing data whereas reports are used for viewing and printing data.
Reports are often seen as inflexible, with the decisions about which data is to be displayed being made at design time. Access users often think that if they want to see a different set of data they need to build a new report, or change the query on which the report is based. Not true!
When you look at data in a table or form Access provides you with tools for filtering and sorting...
But when you view a report in Print Preview these tools are absent, so you could be forgiven for thinking that it's too late to change what's on display.
This tutorial will show you how to make use of a report's Filter and OrderBy properties to create dynamic reports which the user can filter and sort using a simple dialog, and see the result appear immediately in the report's Print Preview window. To see a different set of data all they have to do is specify a different filter, click the button, and the report will change to display the new data.
In the example below the user filtered the report for a list of all the Female staff working in the Production and Shipping departments of the Birmingham andCardiff offices [click the thumbnail to see a demonstration in a new window]:
You can download a fully working sample file containing all the examples described here from the link at the bottom of this page.
Building the Report
Nothing special here, and no code required! You can use an existing report or create a new one for this project. You should bear in mind that the tool you are going to build will filter the report's data so it might be appropriate for you to base your report on an unfiltered table rather than on a query where the data is already filtered by the time it reaches the report. It's up to you. Familiarize yourself with the names of the fields displayed in the report and their data types.
Building the Dialog Box
The job of the dialog box is to allow the user to specify criteria for filtering and, in the more sophisticated examples sorting, the records displayed by the report. This means that you must choose which fields you want to offer the user and, in most of these examples, be able to provide a list of suitable search criteria to populate a combo box or list box for each field.
To turn an Access form into a dialog box only requires you to change a few settings on the form's properties sheet. If you haven't built an Access form as a dialog box before click here to find out what you need to do.
I have created several different dialog boxes, offering different levels of complexity. You can choose from simple examples using combo boxes, or more complex ones using multi-select list boxes:
A simple dialog using combo boxes.
|
Combo boxes and an option group.
|
Multi-select list boxes.
|
List boxes and sorting options.
|
Or free-text filtering where the user can search on any text string:
In my combo box and list box examples, the fields that I have chosen for filtering contain a known range of entries. One is a list of company Offices and another is a list of Departments. My database has tables containing lists of these entries and I am using these tables for the Row Source of the combo boxes and list boxes. Later in this tutorial I will show how to use wildcards to allow users to filter records using string fragments (such as records starting with a particular letter, or containing a particular group of letters).
The important parts of the VBA code for each dialog box are described and explained below. To see the full code module for each dialog box follow the links marked "Code Listing 1" etc. These will display all the code used in the dialog box in a form that you can copy and paste into your own code window if you wish.
A Simple Dialog Using Combo Boxes
This is the simplest of the designs. The dialog has two combo boxes, one for each of the fields that I am allowing the user to filter. You can add as many combo boxes as you like providing you modify the code accordingly. There are also two command buttons: one for applying the filter and another for removing it.
The illustration below shows the layout of the dialog and the names I used for the various objects...
The aim of the code behind the Apply Filter button is to construct an SQL WHERE clause using the user's combo box choices. The SQL will then be applied to the report's Filter property and the FilterOn property of the report will be set to True so that it displays the filtered recordset.
If the user makes a choice from a combo box the report should display only the records which have that value in the field. So if, for example, they choose New York from the Office combo box the code will have to construct SQL like this:
[Office] = 'New York'
But if the user leaves a combo box empty, I want the report to display all the records for that field, which would require SQL like this:
[Office] Like '*'
Coding the "Apply Filter" Button
My code starts by declaring three string variables, one each to hold the SQL from the combo boxes and one to hold the combined criteria for the filter:
Private Sub cmdApplyFilter_Click()
Dim strOffice As String
Dim strDepartment As String
Dim strFilter As String
Dim strOffice As String
Dim strDepartment As String
Dim strFilter As String
If a combo box is empty its value is Null so I can use an If Statement to check whether or not the user made a choice and then construct the appropriate SQL:
If IsNull(Me.cboOffice.Value) Then
strOffice = "Like '*'"
Else
strOffice = "='" & Me.cboOffice.Value & "'"
End If
strOffice = "Like '*'"
Else
strOffice = "='" & Me.cboOffice.Value & "'"
End If
There is a similar If Statement for each combo box:
If IsNull(Me.cboDepartment.Value) Then
strDepartment = "Like '*'"
Else
strDepartment = "='" & Me.cboDepartment.Value & "'"
End If
strDepartment = "Like '*'"
Else
strDepartment = "='" & Me.cboDepartment.Value & "'"
End If
Next comes a line which combines the criteria to form a WHERE clause for the filter:
strFilter = "[Office] " & strOffice & " AND [Department] " 
& strDepartment
& strDepartment
And finally the filter is applied to the report and switched on:
With Reports![rptStaff]
.Filter = strFilter
.FilterOn = True
End With
End Sub
.Filter = strFilter
.FilterOn = True
End With
End Sub
Anticipating Errors
Before completing any project like this you should consider what might go wrong. All but the most basic of your VBA procedures should contain a simple error handler, but you can help things a great deal by anticipating what problems might arise. Changes in the design of the report or the fields included might result in an error, but if you are fairly confident that this won't happen you can leave it for the error handler to take care of. This project requires the report to be open before the filter is applied. If it isn't then you get an error:
Notice that the error doesn't distinguish between the report not being open and not existing at all. It can only "see" an object if it is open. It may be that the user simply forgot to open the report, or perhaps its name has been changed or someone might have deleted it.
You could have the report open automatically when the dialog opens (or vice versa) by including the procedure:
Private Sub Form_Load()
DoCmd.OpenReport "rptStaff", acViewPreview
End Sub
DoCmd.OpenReport "rptStaff", acViewPreview
End Sub
But this would still need to take account of a missing or renamed report. My preferred method of dealing with this possibility is to check whether or not the report is open and advise the user accordingly. This code goes at the beginning of the cmdApplyFilter_Click procedure after the variable declarations:
If SysCmd(acSysCmdGetObjectState, acReport, "rptStaff") <> 
acObjStateOpen Then
MsgBox "You must open the report first."
Exit Sub
End If
acObjStateOpen Then
MsgBox "You must open the report first."
Exit Sub
End If
If a report with the name specified is not found in an "Open" state the If Statement displays a message to the user then terminates the procedure to prevent an error occurring.
Coding the "Remove Filter" Button
All that is required to remove the filter is a line which sets the FilterOn property of the report to False:
Private Sub cmdRemoveFilter_Click()
On Error Resume Next
Reports![rptStaff].FilterOn = False
End Sub
On Error Resume Next
Reports![rptStaff].FilterOn = False
End Sub
The line On Error Resume Next tells Access to ignore any error which might occur if, for example, the report has already been closed by the user.
You can review the complete code for the dialog box in Code Listing 1.
Adding an Option Group
Combo boxes are not the only way to offer the user a choice of predefined options. This dialog box, otherwise exactly the same as the previous example, makes use of an option group to allow the user to filter the records by gender.
Option groups are useful when there are just a few items to choose from. Here there are three choices: Female, Male or Both...
When you design your dialog box you can create an option group manually or with the help of the wizard. An Option Group consists of a Frame containing a collection of Option Buttons (sometimes called Radio Buttons). Alternatively you can use check boxes or toggle buttons.
If you use the wizard to create the option group all the work is done for you. If you prefer to create the option group manually, first draw a frame on the form then name it and add a suitable caption to the label. Then add the individual buttons inside the frame. As you do this each button is automatically assigned a value. The first button you add has the value 1, the next has the value 2 and so on. In my example the values are: Female (1), Male (2), Both (3). You can assign any value you like to each button by changing its Option Value property. You must use whole numbers (data type Long Integer) and normally they should all be different.
When option buttons are used as a group like this, only one member of the group can be selected at a time. The selected button passes its value to the group itself. It is a good idea to set a starting value for the group by specifying the frame's Default Value property. In my example the default value is 3, so that when the dialog box opens the Both button is already selected.
Detecting and programming the user's choice is simple and is best done with a Case Statement. First it is necessary to declare an additional variable to hold theGender criteria:
Dim strGender As String
Then the Case Statement can place the appropriate criteria string into the variable depending on the value of the option group:
SelectCase Me.fraGender.Value
Case 1
strGender = "='F'"
Case 2
strGender = "='M'"
Case 3
strGender = "Like '*'"
End Select
Case 1
strGender = "='F'"
Case 2
strGender = "='M'"
Case 3
strGender = "Like '*'"
End Select
The only other change to the code is the addition of the criteria for the Gender field to the filter string:
strFilter = "[Office] " & strOffice & " AND [Department] " 
& strDepartment & " AND [Gender] " & strGender
& strDepartment & " AND [Gender] " & strGender
You can review the complete code for the dialog box in Code Listing 2.
Using Multi-Select List Boxes
The combo boxes used in the previous examples have the disadvantage that the user can select only one item from each. This dialog box uses list boxes. These offer a multi-select facility so that the user can make several choices from each if they wish.
Designing a list box is similar to designing a combo box in that is needs a Row Source from which to make its list. As before I have used tables for this. You must enable the multi-select feature by choosing a suitable mode for the Multi Select property of the list box. Choose Simple or Extended depending on how you want the user to make their choices.
If you want to know more about list box basics read my tutorial Making Sense of List Boxes.
In this example the code has the job of creating a criteria string from the user's choices in each of the list boxes. If, for example, the user chooses London,Brussels and Paris from the Office list box it means that they want to see records for the London OR Brussels OR Paris offices.
There are different ways this can be represented in SQL. One way is to use the keyword "OR" like this:
[Office] = 'London' OR [Office] = 'Brussels' OR [Office] = 'Paris'
but this can result in very long criteria strings, so I prefer to use an SQL "IN" clause like this:
[Office] IN('London','Brussels','Paris')
To do this I use a code statement that loops through the selected items in the list box and strings them together, separated by commas:
Dim varItem As Variant
For Each varItem In Me.lstOffice.ItemsSelected
strOffice = strOffice & ",'" & Me.lstOffice.ItemData(varItem) _
& "'"
Next varItem
For Each varItem In Me.lstOffice.ItemsSelected
strOffice = strOffice & ",'" & Me.lstOffice.ItemData(varItem) _
& "'"
Next varItem
If the resulting string has no length (i.e. it is empty because the user didn't select anything) the code creates criteria which shows all records. But if the user did make a selection my code creates an IN clause listing the selections, after first removing the leading comma from the front of the string:
If Len(strOffice) = 0 Then
strOffice = "Like '*'"
Else
strOffice = Right(strOffice, Len(strOffice) - 1)
strOffice = "IN(" & strOffice & ")"
End If
strOffice = "Like '*'"
Else
strOffice = Right(strOffice, Len(strOffice) - 1)
strOffice = "IN(" & strOffice & ")"
End If
Each list box requires a similar set of code statements. The code for the Gender option group and the code that creates the filter string are exactly the same as in the previous example.
You can review the complete code for the dialog box in Code Listing 3.
Offering the Option to Sort the Records
In many cases sorting the records in a report is just as important as filtering them. If you don't want to offer a sorting option to the user, you can predefine the sort order by basing the report on a query or use an SQL statement incorporating an ORDER BY clause as the report's Record Source. For example:
SELECT * FROM tblStaff ORDER BY [Office], [Department];
will display the records sorted first by the Office field then by the Department field, both in ascending order.
Adding sorting options to the dialog increases its power a great deal but to make it a really useful and user-friendly tool requires a bit of thought and quite a lot of code (although much of it is quite simple and repeated several times). Here's how the dialog box looks:
I have put the sorting tools (three combo boxes and three command buttons) inside a frame purely for design effect. It has no other practical implications. The features of the sorting tools are as follows:
- There are three combo boxes, allowing the user to sort by up to three fields at a time.
- The combo boxes have a hierarchy (1, 2, 3) by which the sort is prioritised. The records are sorted first by the choice in combo box 1, then by the choice in combo box 2, then by the choice in combo box 3.
- Each combo box contains a list of fields from which the user can choose to sort by, plus the option "Not Sorted".
- If the "Not Sorted" option is chosen any combo boxes lower in the hierarchy are automatically disabled and their values set to "Not Sorted" too.
- If the user chooses a sort field, and the value in the next combo box is disabled, then the next combo box becomes enabled with its value remaining as "Not Sorted".
- The user can not choose the same field in two different combo boxes.
- There are three command buttons, corresponding to the three combo boxes, by which the user can specify the sort direction for each field.
- When a field is chosen the corresponding command button is enabled, but when a combo box has the value "Not Sorted" the corresponding command button is disabled.
- Clicking the command button changes its caption from Ascending to Descending. Clicking it again changes it back again. The default caption isAscending.
As you can see, there is quite a lot of functionality here and that requires quite a lot of code, but it is fairly simple and much of it is repeated for each combo box so the task of creating an ORDER BY clause isn't as daunting as it might seem.
Filling The Combo Box Lists
To create the list, each combo box has its Row Source Type property set to Value List. This allows a list of items to be typed directly into the Row Source property textbox:
Separated by semicolons, each item becomes an option on the combo box list:
Coding the Combo Boxes
Each combo box has three jobs to do when the user makes a choice:
- It has to check that the choice is not the same as a choice that has already been made.
- It has to disable the combo boxes below it in the hierarchy if the user chose "Not Sorted" and set their values to "Not Sorted".
- If the user chose "Not Sorted" it has to disable its own sort direction command button and any below it in the hierarchy and set their captions to "Ascending".
The first job is best handled by the combo box's Before Update event. This is because, if the user's choice is not acceptable, the update can be cancelled easily. The After Update event doesn't offer this facility. Here's the code for the first combo box:
Private Sub cboSortOrder1_BeforeUpdate(Cancel As Integer)
If Me.cboSortOrder1.Value <> "Not Sorted" Then
If Me.cboSortOrder1.Value = Me.cboSortOrder2.Value _
Or Me.cboSortOrder1.Value = Me.cboSortOrder3.Value Then
MsgBox "You already chose that item."
Cancel = True
Me.cboSortOrder1.Dropdown
End If
End If
End Sub
If Me.cboSortOrder1.Value <> "Not Sorted" Then
If Me.cboSortOrder1.Value = Me.cboSortOrder2.Value _
Or Me.cboSortOrder1.Value = Me.cboSortOrder3.Value Then
MsgBox "You already chose that item."
Cancel = True
Me.cboSortOrder1.Dropdown
End If
End If
End Sub
There are two If Statements, one inside the other (this is referred to as "nested"). The first If Statement checks to see if the user chose "Not Sorted". If they did, nothing happens because it's OK if more than one combo box has this value. But if any other value has been chosen it checks to see if the chosen value matches any of the other combo boxes. If it does a message is shown to the user, their action is undone (by cancelling the event) and the combo box list is dropped to prompt the user to make a different choice.
The Before Update event procedure of each combo box has similar code with the numbers changed so that each combo box checks the other two.
The remaining jobs are handled by the combo boxes' Change event:
Private Sub cboSortOrder1_Change()
Dim i As Integer
If Me.cboSortOrder1.Value = "Not Sorted" Then
For i = 2 To 3
With Me.Controls("cboSortOrder" & i)
.Enabled = False
.Value = "Not Sorted"
End With
Next i
For i = 1 To 3
With Me.Controls("cmdSortDirection" & i)
.Enabled = False
.Caption = "Ascending"
End With
Next i
Else
Me.cboSortOrder2.Enabled = True
Me.cmdSortDirection1.Enabled = True
End If
End Sub
Dim i As Integer
If Me.cboSortOrder1.Value = "Not Sorted" Then
For i = 2 To 3
With Me.Controls("cboSortOrder" & i)
.Enabled = False
.Value = "Not Sorted"
End With
Next i
For i = 1 To 3
With Me.Controls("cmdSortDirection" & i)
.Enabled = False
.Caption = "Ascending"
End With
Next i
Else
Me.cboSortOrder2.Enabled = True
Me.cmdSortDirection1.Enabled = True
End If
End Sub
When the user changes the value of the combo box an If Statement checks the new value to see if it is "Not Sorted". If it is, a For...Next loop disables the other combo boxes and sets their values to "Not Sorted". Then another For...Next loop disables the command buttons and sets their captions to "Ascending".
If the user made a choice other than "Not Sorted" then its own sort direction command button is enabled and the combo box below is enabled.
As before, the code is similar for the remaining combo boxes. Each one referring to the appropriate controls.
NOTE: One of the aims of writing good code is to make it as brief as possible, and in the above example I have demonstrated this with the use of loops and With Statements. Using loops avoids having to repeat chunks of code. The variable "i" not only tells Access how many times to run the loop but also serves to identify which control is being manipulated each time. I can do this because I gave the controls names which included numbers. In the first loop, the first time it runs the value of "i" is 2 so Me.Controls("cboSortOrder” & i) means the same as Me.cboSortOrder2 and so on. Although there are only three combo boxes in this example the loop would require no additional code for any number of combo boxes. I would just have to change the upper limit of the value of "i". With Statements also help by removing the need to repeat the first part of a long code line when writing several consecutive statements all referring to the same thing.
Coding the Sort Direction Command Buttons
I want to make the selection of a sort direction (i.e. ascending or descending) as easy as possible. Since there are only two choices it isn't worth using combo boxes, and an option buttons would take up too much room. So I borrowed Microsoft's idea (take a look at the Query Wizard!) and used command buttons. Clicking one of the command buttons doesn't do anything other than change it's caption. The code which later builds the SQL statement for the report filter will read the button's caption to determine which way to sort the records. The code is simple and the same for each button (with the appropriate control names inserted):
Private Sub cmdSortDirection1_Click()
If Me.cmdSortDirection1.Caption = "Ascending" Then
Me.cmdSortDirection1.Caption = "Descending"
Else
Me.cmdSortDirection1.Caption = "Ascending"
End If
End Sub
If Me.cmdSortDirection1.Caption = "Ascending" Then
Me.cmdSortDirection1.Caption = "Descending"
Else
Me.cmdSortDirection1.Caption = "Ascending"
End If
End Sub
Coding the Apply Filter Command Button
The code used to create the filter string is exactly the same as in the previous example, but this time there is an additional task: to create an Order By string that will be applied to the OrderBy property of the report.
In the same way that the code generates a criteria string, applies it to the Filter property of the report and then activates it by setting the FilterOn property to True,it now has to generate a sort string, apply it to the OrderBy property of the report and activate it by setting the OrderByOn property to True.
If the user chose to sort the data by LastName then by FirstName, here's what a completed OrderBy string should look like:
[LastName], [FirstName]
Fields are simply listed in the desired order, separated by commas. I am in the habit of enclosing field names in square brackets as shown here but this is only absolutely necessary when the names contain spaces.
Unless specified otherwise, each field is sorted in ascending order (A-Z, 1-9) but if a field is to be sorted in descending order (Z-A, 9-1) the keyword DESC is used. The sort direction must be specified for each field. if it is omitted it is assumed that the field is to be sorted in ascending order. So, if the user chose to sort the data by LastName then by FirstName both in descending order the string should look like:
[LastName] DESC, [FirstName] DESC
Here is the code used to build the sort string:
If Me.cboSortOrder1.Value <> "Not Sorted" Then
strSortOrder = "[" & Me.cboSortOrder1.Value & "]"
If Me.cmdSortDirection1.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
If Me.cboSortOrder2.Value <> "Not Sorted" Then
strSortOrder = strSortOrder & ",[" &
Me.cboSortOrder2.Value & "]"
If Me.cmdSortDirection2.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
If Me.cboSortOrder3.Value <> "Not Sorted" Then
strSortOrder = strSortOrder & ",[" &
Me.cboSortOrder3.Value & "]"
If Me.cmdSortDirection3.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
End If
End If
End If
strSortOrder = "[" & Me.cboSortOrder1.Value & "]"
If Me.cmdSortDirection1.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
If Me.cboSortOrder2.Value <> "Not Sorted" Then
strSortOrder = strSortOrder & ",[" &
Me.cboSortOrder2.Value & "]"
If Me.cmdSortDirection2.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
If Me.cboSortOrder3.Value <> "Not Sorted" Then
strSortOrder = strSortOrder & ",[" &
Me.cboSortOrder3.Value & "]"
If Me.cmdSortDirection3.Caption = "Descending" Then
strSortOrder = strSortOrder & " DESC"
End If
End If
End If
End If
The code consists of a series of nested If Statements, each one depending on the result of the previous one. First of all, an If Statement checks to see if the value of the first combo box is something other than "Not Sorted". If the value is "Not Sorted" the If Statement finishes and the OrderBy string remains empty. But if the first combo box contains a field name then it is placed, surrounded by square brackets, into a variable named strSortOrder. A second If Statement then looks at the caption of the first command button and if it reads "Descending" the keyword DESC is added to the string in the strSortOrder variable.
The process is repeated for each combo box and finishes if the value "Not Sorted" is found, but if a field name is found its name and the sort direction is noted and the code moves to the next combo box.
The last section of the code applies and activates both the filter string and the sort string:
With Reports![rptStaff]
.Filter = strFilter
.FilterOn = True
.OrderBy = strSortOrder
.OrderByOn = True
End With
.Filter = strFilter
.FilterOn = True
.OrderBy = strSortOrder
.OrderByOn = True
End With
Coding the Remove Filter Command Button
In the earlier examples the this button has simply set the FilterOn property of the report to False. Now it has to do the same for the OrderByOn property:
With Reports![rptStaff]
.FilterOn = False
.OrderByOn = False
End With
.FilterOn = False
.OrderByOn = False
End With
But this time I have added some extra functionality. The following statement removes the selections from the Office list box (there is a similar one for theDepartment list box):
For Each varItem In Me.lstOffice.ItemsSelected
Me.lstOffice.Selected(varItem) = False
Next varItem
Me.lstOffice.Selected(varItem) = False
Next varItem
The option group is reset to its original value (Gender = Both):
Me.fraGender.Value = 3
Finally the following loop sets the value of each combo box to "Not Sorted" and disables it, then sets the caption of each sort direction command button to "Ascending" and disables it. The last line enables the first combo box ready for the user to make their next choice.
For i = 1 To 3
Me.Controls("cboSortOrder" & i).Value = "Not Sorted"
Me.Controls("cboSortOrder" & i).Enabled = False
Me.Controls("cmdSortDirection" & i).Enabled = False
Me.Controls("cmdSortDirection" & i).Caption = "Ascending"
Next i
Me.cboSortOrder1.Enabled = True
Me.Controls("cboSortOrder" & i).Value = "Not Sorted"
Me.Controls("cboSortOrder" & i).Enabled = False
Me.Controls("cmdSortDirection" & i).Enabled = False
Me.Controls("cmdSortDirection" & i).Caption = "Ascending"
Next i
Me.cboSortOrder1.Enabled = True
You can review the complete code for the dialog box in Code Listing 4.
Filtering with Free Text
Each example so far has offered the user a fixed range of choices for filtering the report's records. That is fine if the fields that are being filtered contain a known range of items. But you might need to offer the facility to filter using free text, i.e. allowing the user to enter anything they want.
This final example shows how you can allow the user to enter any string of text into a text box and choose how that string is used to filter the records...
For each available field the dialog box has a text box and a set of option buttons. The user can enter a letter or a string of text in the text box and make a choice from the option group to specify how the string is used in the filter. If the user leaves a text box empty then all records are returned for that field.
Supposing the user might enter the string mar into the FirstName textbox. The results they get will depend upon the choices they make from the option buttons:
Choosing Starts with... would return: Martin, Mark, Margaret, Mariana etc.
Choosing Contains... would return: Martin, Anne-Marie, Marlon, Omar etc.
Choosing Ends with... would return: Omar, Dagmar, Valdemar etc.
Choosing Equals... would return only: Mar
Choosing Contains... would return: Martin, Anne-Marie, Marlon, Omar etc.
Choosing Ends with... would return: Omar, Dagmar, Valdemar etc.
Choosing Equals... would return only: Mar
The method used by the filter is to combine the string with one or more asterisk wildcards. In SQL the asterisk (*) combined with a string and the keyword LIKE represents any string of text:
Like "g*" represents the letter g followed by any string of text.
Like "*g*" represents any string of text containing the letter g.
Like "*g" represents any string of text ending with the letter g.
Like "*g*" represents any string of text containing the letter g.
Like "*g" represents any string of text ending with the letter g.
The code used to create the filter string is quite simple:
If IsNull(Me.txtFirstName.Value) Then
strFirstName = "Like '*'"
Else
SelectCase Me.fraFirstName.Value
Case 1
strFirstName = "Like '" & Me.txtFirstName.Value & "*'"
Case 2
strFirstName = "Like '*" & Me.txtFirstName.Value & "*'"
Case 3
strFirstName = "Like '*" & Me.txtFirstName.Value & "'"
Case 4
strFirstName = "= '" & Me.txtFirstName.Value & "'"
End Select
End If
strFirstName = "Like '*'"
Else
SelectCase Me.fraFirstName.Value
Case 1
strFirstName = "Like '" & Me.txtFirstName.Value & "*'"
Case 2
strFirstName = "Like '*" & Me.txtFirstName.Value & "*'"
Case 3
strFirstName = "Like '*" & Me.txtFirstName.Value & "'"
Case 4
strFirstName = "= '" & Me.txtFirstName.Value & "'"
End Select
End If
An If Statement looks for an entry in the text box. If the text box is empty (i.e. its value is Null) a filter expression is created that will return all the records. If there is an entry in the text box a Case Statement creates the required filter expression depending on the value of the option group.
This is repeated for each field and the filter expressions are combined to create a filter string:
strFilter = "[FirstName] " & strFirstName & _
" AND [LastName] " & strLastName
" AND [LastName] " & strLastName
The filter string is applied to the report and activated in the same way as in the previous examples.
You can review the complete code for the dialog box in Code Listing 5.
Ideas for Further Improvements
The examples I have shown are each designed to illustrate particular techniques. Remember you can mix and match as many of the different tools as you need but remember to always think about the user. These tools are meant to make their lives easier - so keep it simple!
Opening the Report Automatically
The above examples include a few lines of code that look for the open report and warn the user if it isn't found:
If SysCmd(acSysCmdGetObjectState, acReport, "rptStaff") <> 
acObjStateOpen Then
MsgBox "You must open the report first."
Exit Sub
End If
acObjStateOpen Then
MsgBox "You must open the report first."
Exit Sub
End If
But you could save them the trouble by opening the report automatically as soon as they press the Apply Filter button:
If SysCmd(acSysCmdGetObjectState, acReport, "rptStaff") <> 
acObjStateOpen Then
DoCmd.OpenReport "rptStaff", acViewPreview
End If
acObjStateOpen Then
DoCmd.OpenReport "rptStaff", acViewPreview
End If
Or perhaps you could ask the user what they want to do:
Dim Response As VbMsgBoxResult 'For Access 97 use: As Variant
If SysCmd(acSysCmdGetObjectState, acReport, "rptStaff") <>
acObjStateOpen Then
Response = MsgBox("The report is not open." _
& vbCrLf & "Do you want to open it now?" _
, vbQuestion + vbYesNoCancel)
SelectCase Response
Case vbYes
DoCmd.OpenReport "rptStaff", acViewPreview
Case vbNo
Exit Sub
Case vbCancel
DoCmd.Close acForm, Me.Name
Exit Sub
End Select
End If
If SysCmd(acSysCmdGetObjectState, acReport, "rptStaff") <>
acObjStateOpen Then
Response = MsgBox("The report is not open." _
& vbCrLf & "Do you want to open it now?" _
, vbQuestion + vbYesNoCancel)
SelectCase Response
Case vbYes
DoCmd.OpenReport "rptStaff", acViewPreview
Case vbNo
Exit Sub
Case vbCancel
DoCmd.Close acForm, Me.Name
Exit Sub
End Select
End If
Automate the Report's Title
Most reports need some sort of descriptive title and you might like the title to change to reflect the records displayed. You can use the user's choices in the combo boxes, list boxes and other controls to construct a variety of labels to place in the header or footer section of the report.
For each title you need to place an empty control in the report's Report Header or Report Footer section. If these sections are not already shown on your report when you view it in Design View, go to View > Report Header/Footer to switch them on.
You can either add a Label control (and use the code to specify its Caption property) or an unbound TextBox control (and use the code to specify its Value). My preference is to use a TextBox because it has a Can Grow property which, when set to Yes, allows the control to increase in height to accommodate the amount of text it contains. Also, its value can be set when the report is in Preview view (unlike a label caption which requires the report to be switched into Design view for editing). This is useful if you aren't sure how long the title is going to be. Make sure you draw the control as wide as the report permits because the width doesn't change, only the height:
The text of your title can be made up of plain text combined with values extracted from the dialog box controls. It can be applied to the report at the same time as the filter. Here's an example of the sort of code you might use (the textbox control is named txtReportTitle):
With Reports![rptStaff]
.Filter = strFilter
.FilterOn = True
.txtReportTitle.Value = _
"Staffing Report - " & Format(Date, "dd mmm yyyy") _
& vbCrLf & "Office: " & Me.cboOffice.Value _
& vbCrLf & "Department: " & Me.cboDepartment.Value
End With
.Filter = strFilter
.FilterOn = True
.txtReportTitle.Value = _
"Staffing Report - " & Format(Date, "dd mmm yyyy") _
& vbCrLf & "Office: " & Me.cboOffice.Value _
& vbCrLf & "Department: " & Me.cboDepartment.Value
End With
Note the use of vbCrLf in the code to add line breaks to the finished text. Here's the result:
Download the File
You can download a fully-working database file containing this tutorial's completed project in either Access 97 or Access 2000 format. The database contains all five dialog boxes described in the tutorial and a sample report and tables. The files are provided in Access 97 and Access 2000 format.
Access 97 format. | |
Access 2000 format. |
No comments:
Post a Comment