473,591 Members | 2,842 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Cascaded Form Filtering

NeoPa
32,566 Recognized Expert Moderator MVP
Cascaded Form Filtering

CHAPTER 1 - TABLE OF CONTENTS (Including attached database)
CHAPTER 2 - INTRODUCTION
CHAPTER 3 - TABLE LAYOUT
CHAPTER 4 - FORM LAYOUT
CHAPTER 5 - FORM MODULE
CHAPTER 6 - CODE DISCUSSION (FILTER CONTROLS)
CHAPTER 7 - CODE DISCUSSION (THE REST)

--------------------------------------------------------------------------------

CHAPTER 2 - INTRODUCTION

The first thing I need to explain about this exercise is that, while I have tried to use data that has some meaning, this is fundamentally an exercise to illustrate the various concepts usable in the various scenarios that require filtering and cascaded filtering in particular.

This is a fairly basic example and works with some unbound selection controls in the Header Section of an otherwise bound form. No SubForms were used or abused in the creation of this exercise.

In this example I have a Form called frmAccount bound to table tblAccount. The form has all of the table fields shown as (bound) controls (Mainly TextBox - name preceded by 'txt', but also a ComboBox - cboAccountType) . It also has four extra (unbound) controls to enable the operator to select items by AccountName (or part thereof), CreationDate and SalesThisYear. These fields have been chosen for illustrative purposes only and are not supposed to reflect any real-world situation accurately.
To avoid confusion, it is a good idea to select names that have meaning, reflecting their contents, while avoiding special characters (including spaces) and names which are already in use for something else. A good example of the latter is 'Date'. Using this as a field or control name is a problem in waiting.

--------------------------------------------------------------------------------

CHAPTER 3 - TABLE LAYOUT

Table Name=[tblAccount]
Expand|Select|Wrap|Line Numbers
  1. AccountID; Autonumber; PK
  2. AccountCode; String
  3. AccountName; String
  4. CreationDate; Date/Time
  5. AccountType; Numeric; 0==>Control (Internal Usage); 1==>Supplier; 2==>Customer
  6. SalesThisYear; Numeric
--------------------------------------------------------------------------------

CHAPTER 4 - FORM LAYOUT

Form Name=[frmAccount]
Expand|Select|Wrap|Line Numbers
  1. cboByAccountType; ComboBox; Unbound
  2. txtByAccountName; TextBox; Unbound
  3. txtBySalesLastYear; TextBox; Unbound
  4. cboFromCreationDate; ComboBox; Unbound
  5. cboToCreationDate; ComboBox; Unbound
  6. txtAccountID; TextBox; Bound to [AccountID]
  7. txtAccountCode; TextBox; Bound to [AccountCode]
  8. txtAccountName; TextBox; Bound to [AccountName]
  9. txtCreationDate; TextBox; Bound to [Creation]
  10. cboAccountType; ComboBox; Bound to [AccountType]
  11. txtSalesThisYear; TextBox; Bound to [SalesThisYear]
--------------------------------------------------------------------------------

CHAPTER 5 - FORM MODULE

Expand|Select|Wrap|Line Numbers
  1. Option Compare Database
  2. Option Explicit
  3.  
  4. Private Const conDateSource As String = "SELECT DISTINCT [CreationDate] " & _
  5.                                         "FROM tblAccount " & _
  6.                                         "ORDER BY [CreationDate]"
  7.  
  8. Private Sub cboByAccountType_AfterUpdate()
  9.     Me.cboAccountType.DefaultValue = Nz(Me.cboByAccountType, "")
  10.     Call Cascade
  11.     Call ApplyFilter
  12. End Sub
  13.  
  14. Private Sub txtByAccountName_AfterUpdate()
  15.     Call Cascade
  16.     Call ApplyFilter
  17. End Sub
  18.  
  19. Private Sub txtBySalesThisYear_AfterUpdate()
  20.     Me.txtSalesThisYear.DefaultValue = Nz(Me.txtBySalesThisYear, "")
  21.     Call Cascade
  22.     Call ApplyFilter
  23. End Sub
  24.  
  25. Private Sub cboFromCreationDate_AfterUpdate()
  26.     Dim strDefault As String
  27.  
  28.     With Me
  29.         If IsNull(.cboFromCreationDate) Then
  30.             strDefault = ""
  31.         Else
  32.             strDefault = "#" & Format(CDate(.cboFromCreationDate), _
  33.                                       "m/d/yyyy") & "#"
  34.         End If
  35.         .txtCreationDate.DefaultValue = strDefault
  36.     End With
  37.     Call ApplyFilter
  38. End Sub
  39.  
  40. Private Sub cboToCreationDate_AfterUpdate()
  41.     Call ApplyFilter
  42. End Sub
  43.  
  44. 'Cascade filters the two unbound filter ComboBoxes cboFromCreationDate &
  45. 'cboToCreationDate by the values already entered/selected from the top level
  46. 'unbound controls in the Header Section (cboByAccountType; txtByAccountName &
  47. 'txtBySalesThisYear.  As cboFromCreationDate is always reset in here, the
  48. 'DelfaultValue for txtCreationDate must be also.
  49. Private Sub Cascade()
  50.     Dim strSQL As String, strFilter As String
  51.  
  52.     With Me
  53.         .cboFromCreationDate = Null
  54.         .cboToCreationDate = Null
  55.         .txtCreationDate.DefaultValue = ""
  56.         strSQL = conDateSource
  57.         strFilter = GetFilter()
  58.         If strFilter > "" Then _
  59.             strSQL = Replace(strSQL, "ORDER", "WHERE " & strFilter & " ORDER")
  60.         .cboFromCreationDate.RowSource = strSQL
  61.         .cboToCreationDate.RowSource = strSQL & " DESC"
  62.     End With
  63. End Sub
  64.  
  65. 'ApplyFilter applies new Filter from GetFilter().
  66. Private Sub ApplyFilter()
  67.     Dim strFilter As String, strOldFilter As String
  68.  
  69.     With Me
  70.         strOldFilter = .Filter
  71.         strFilter = GetFilter
  72.         If strFilter <> strOldFilter Then
  73.             .Filter = strFilter
  74.             .FilterOn = (strFilter > "")
  75.         End If
  76.     End With
  77. End Sub
  78.  
  79. 'GetFilter returns the new filter depending on the values currently in
  80. 'the unbound controls in the Header Section (cboByAccountType; txtByAccountName;
  81. 'txtBySalesThisYear; cboFromCreationDate & cboToCreationDate.
  82. Private Function GetFilter() As String
  83.     Dim strFilter As String
  84.  
  85.     With Me
  86.         'cboByAccountType - Numeric - Exact match
  87.         If Not IsNull(.cboByAccountType) Then _
  88.             strFilter = strFilter & " AND " & _
  89.                         "([AccountType]=" & _
  90.                         .cboByAccountType & ")"
  91.         'txtByAccountName - Text - Like / Wildcards
  92.         If Not IsNull(.txtByAccountName) Then _
  93.             strFilter = strFilter & " AND " & _
  94.                         "([AccountName] Like '*" & _
  95.                         .txtByAccountName & "*')"
  96.         'txtBySalesThisYear - Numeric - Greater or Equal
  97.         If Not IsNull(.txtBySalesThisYear) Then _
  98.             strFilter = strFilter & " AND " & _
  99.                         "([SalesThisYear]>=" & _
  100.                         .txtBySalesThisYear & ")"
  101.         'cboFromCreationDate - Date - Between or Equal
  102.         If Not IsNull(.cboFromCreationDate) Then
  103.             strFilter = strFilter & " AND ([CreationDate]"
  104.             If IsNull(.cboToCreationDate) Then
  105.                 strFilter = strFilter & "=#%F#)"
  106.             Else
  107.                 strFilter = strFilter & " Between #%F# And #%T#)"
  108.                 strFilter = Replace(strFilter, _
  109.                                     "%T", Format(CDate(.cboToCreationDate), _
  110.                                                  "m/d/yyyy"))
  111.             End If
  112.             strFilter = Replace(strFilter, _
  113.                                 "%F", Format(CDate(.cboFromCreationDate), _
  114.                                              "m/d/yyyy"))
  115.         End If
  116. 'Debug.Print ".Filter = '" & strOldFilter & "' - ";
  117. 'Debug.Print "strFilter = '" & strFilter & " '"
  118.         'Tidy up results and apply IF NECESSARY
  119.         If strFilter > "" Then strFilter = Mid(strFilter, 6)
  120.         GetFilter = strFilter
  121.     End With
  122. End Function
--------------------------------------------------------------------------------

CHAPTER 6 - CODE DISCUSSION (FILTER CONTROLS)

Each of the controls' filter part code starts with it checking that it is not empty. Any control will be Null when there is no data within.
Each starts with " AND " but this is stripped later, from the first one found, before being applied.
Each check built is surrounded by parentheses () to ensure the logic is not confused with other checks.

Each of the unbound filter controls has an AfterUpdate event procedure associated with it. There are fundamentally three tasks than can be required for each, though not all three are required for all of these controls :
  1. Set the relevant .DefaultValue property.

    Only cboByAccountTyp e, txtBySalesThisY ear and cboFromCreation Date are required to do this.
  2. Cascade the selected value to reflect in the cboFromCreation Date and cboToCreationDa te ComboBox lists.

    Only cboByAccountTyp e, txtByAccountNam e and txtBySalesThisY ear are required to do this.
  3. Filter the form to reflect all the filter controls.

    All controls do this.

GENERAL
All controls, when they add anything to the ongoing filter string at all, always start with the string " AND ". This is required between each element of the filter string to separate them and ensure all are taken into consideration. This will, of course, leave a single extraneous one at the beginning. It could easily have been arranged to have the extra one at the end instead, but the code to strip it from the start of a string is more straightforward than the code to strip it from the end as that requires determining first the length of the string.

To illustrate, I include below the two alternative lines of code :
Expand|Select|Wrap|Line Numbers
  1. If strFilter > "" Then strFilter = Mid(strFilter, 6)
  2. If strFilter > "" Then strFilter = Left(strFilter, Len(strFilter) - 5)
As is clear, line #1 is shorter and involves less function calls and arithmetic than line #2. Line #2 is probably closer to a human way of thinking though, so you will probably see many examples of it around.


NUMBER
There is no delimiter required for numeric literals.

We have two number controls. One handles an exact match, and the other a Greater Than Or Equal To match.

Exact Match - cboByAccountTyp e
We start with the string " AND ([AccountType]=" then add (string append) the value from our cboByAccountTyp e control then (lastly) add the string ")". Using cboByAccountTyp e=1, this results in :
Expand|Select|Wrap|Line Numbers
  1.  AND ([AccountType]=1)
This will find all records where the [AccountType] is 1 (Customer).

Greater Than Or Equal To Match - txtBySalesThisY ear
We start with the string " AND ([SalesThisYear]>=" then add (string append) the value from our txtBySalesThisY ear control then (lastly) add the string ")". Using txtBySalesThisY ear=1,000, this results in :
Expand|Select|Wrap|Line Numbers
  1.  AND ([SalesThisYear]>=1000)
This will find all records with SalesThisYear >= 1,000.

TEXT
The delimiter for text literals is (') - See Quotes (') and Double-Quotes (") - Where and When to use them for more on this.

The Text filtering is done as a 'Like' construct as the Account Name would be likely to be unique (An exact match would be fairly pointless in this context). We start with the string " AND ([AccountName] Like '*" then add (string append) the value from our txtByAccountNam e control then (lastly) add the string "*')". Using txtByAccountNam e="pub", this results in :
Expand|Select|Wrap|Line Numbers
  1.  AND ([AccountName] Like '*pub*')
This will find any record where the [AccountName] contains "pub".

DATE
The delimiter for date literals is (#) - See Literal DateTimes and Their Delimiters (#) for more on this.

Firstly, to describe the manipulation of the cboFromCreation Date control to return a version we can usefully pass to SQL. In the code it says :
Expand|Select|Wrap|Line Numbers
  1. Format(CDate(.cboFromCreationDate), "m/d/yyyy"))
The operator can enter the data in any format they wish. As long as it's a valid date we don't much care. That said, we do need the data to be properly interpreted by our code. The CDate() call ensures the data is first converted into a valid date value. This will use the local settings to interpret what is esentially string data. Next, the Format() call converts this date into a formatted string appropriate for SQL. That is specifically the m/d/y format.

They may enter "1 February 2007" or they may enter "1/2/2007" in England or "2/1/2007" in the USA. Whatever is entered, CDate() will convert, using the local settings, into a valid Date/Time value. SQL requires that any date literal be unambiguous but insists that the USA format of short date be used (m/d/y) rather than the local one, whichever that may be. See linked article above for more details on this.

As we have the option of entering a To Date also, the filtering here has to determine if the operator has entered a To value or not. If they have, it uses the Between #X# And #Y# construct. Otherwise it simply uses =#X#.

So, we start with the string " AND ([CreationDate]" then we determine if a To Date is available and go from there.

If the To date is available we add (string append) the string " Between #%F# To #%T#)". Otherwise we simply add "=#%F#)" instead. We will get to replacing %F and %T from this string with the actual values from the controls next.

Using the Replace() function, we strip out these replaceable tokens (%F and %T) and replace them with the values from the controls. If a token to replace isn't found in the string, then no change is made to the string (The result of the function is the same as the main string passed).

So, if we just entered "1 Feb 2007" in cboFromCreation Date, then the resultant filter element would be :
Expand|Select|Wrap|Line Numbers
  1.  AND ([CreationDate]=#2/1/2007#)
However, if we then added "28 Feb 2007" to cboToCreationDa te, then the resultant filter element would be :
Expand|Select|Wrap|Line Numbers
  1.  AND ([CreationDate] Between #2/1/2007# And #2/28/2007#)
Either way, we get exactly what we should expect.

If there are any lingering doubts or confusion about how all this works, I strongly suggest you remove the comment marks before the Debug lines near the bottom of the GetFilter() function procedure and review what gets printed into the Immediate pane of the VBA IDE window.

--------------------------------------------------------------------------------

CHAPTER 7 - CODE DISCUSSION (THE REST)

AFTERUPDATE EVENT PROCEDURES
Both the Cascade() and ApplyFilter() subroutines make use of the GetFilter() function.

Cascade() simply ensures both of the date filter controls (that are cascaded to) are reset before continuing. It then calls GetFilter() to determine the value of the overall filter string, which it then applies to the .RowSource properties of both the Date controls. It uses the Replace() function to insert the optional WHERE clause into the initial SQL found in conDateSQL.

ApplyFilter() is likewise pretty simple. It checks the proposed filter from GetFilter() and compares it with the existing filter. Only if they're different does it apply the new one. Refer to APPLYING THE FILTER below for details on how this is done.

GetFilter() is mainly just as described in detail in Chapter 6 above (CODE DISCUSSION (FILTER CONTROLS)).

I've noticed that many people have problems distinguishing what is processed by VBA from what is passed to and processed by the SQL engine (interpreter). It may help you to understand if you uncomment the 'Debug.Print lines and view the results after selecting a value or some values from the 'Find' controls. This comes between the VBA processing and the SQL so it may help to see it at that stage.

APPLYING THE FILTER
Firstly, we check that the filter is actually different from the current one. If it is, we need to set the filter and we also determine if filters should be used (.FilterOn) depending on whether or not strFilter is blank.

--------------------------------------------------------------------------------
Attached Files
File Type: zip CascadedFormFiltering.Zip (22.9 KB, 1347 views)
Aug 17 '10 #1
3 16943
Mr Key
132 New Member
Yep! nice job
Oct 5 '10 #2
medube1
6 New Member
Thanks guys that help me
Aug 8 '13 #3
NeoPa
32,566 Recognized Expert Moderator MVP
Always pleased to hear it's helpful :-)
Aug 15 '13 #4

Sign in to post your reply or Sign up for a free account.

Similar topics

4
1997
by: jfancy-Transport Canada | last post by:
Hi, I'm looking for an asp page to detect if there are any characters in my contact form that shouldn't be there. For example, if there is a "<" character, then this may mean there is html in my contact form, which is not good. I want to build a string that has all the values of my textboxes in my contact form. Its not working? Have a look: <%
2
3639
by: Dalan | last post by:
Okay, I have worked on this and then some, but cannot seem to crack it. So if someone can straighten my code out, or suggest a new approach, then I'm all ears. Here goes: I have two tables - one (tblReports) with all of the fields appearing on a report selection form (frmReports). The other one (tblGroup) is only use for the eight group types that I'm trying to use as a filter. The tblGroup is hooked to the tblReports Group field...
3
11094
by: Jason | last post by:
I am trying to filter records in a primary form based on records in related tables. The data in the related tables is being displayed in the primary form through subforms. To be more specific, I have a primary form named TestResults, which is connected to data in a table named TestResults. There are basically two other tables that are related to the TestResults table (and the primary form) named Names-Normalized and SiteAddresses. The...
0
6466
by: CSDunn | last post by:
Hello, I have a problem with field filtering between an Access 2000 Project form (the application is called CELDT), and the report that shows the results of the filter. Both the form and the report are based on the same View addressed in the Record Source of both as 'dbo.CLMain_vw'. The View resides in a SQL Server 2000 database. There are two different problems I am having with filtering: The first involves filtering in the form by date...
5
3071
by: Richard | last post by:
Hi, I have a form that take some time to load due to many comboboxes and at least 8 subforms. When I filter or sort the main form I get an error message and then Access shuts down. They ask if I want to send the error report to Microsoft. Has anybody seen this type of error message and what can I do to prevent it from happening. Am I doing something illegal in my code? It used to work but I have added conditional formatting to a subform...
2
5438
by: Sean | last post by:
Greetings all, I am attempting to make a form that will filter through several tables that (I believe) have refretial integrity. I am pulling data from several tables into the form and i would like to eventually be able to filter down through the tables untill i can reach one unique record. I am creating a datbase to keep track of registered accounts for a stae program. Each account is registered into the program through a two...
5
1612
by: glamster7 | last post by:
Hi Folks, I have a form frm.viewbookings which shows in the header the labels Stylist1 through to Stylist 7 with the stylist names underneath them. In total for the set of data I have there are 14stylists. Ok behind all this we have 2 tables tmp_bookingsGrid & tmp_clientGrid. Also in order to populate the form we run several queries in order to populate these tables with the required data & show the following in the detail section of...
11
2310
by: seangibson | last post by:
Sorry, I've been posting alot of questions regarding cascaded combo boxes here but each time I solve one problem I seem to be faced with a new. This time here's the problem: I've got the combo boxes more or less working but they don't refresh. I'm only allowed one chance to choose the value in the first combo box. If I try to change it to another value, the second (dependent) combo box only displays the options from my first choice in the...
0
1995
by: Lyn | last post by:
I have a problem using the form .Filter and .FilterOn properties which causes Access to crash (as detailed in a separate post). The form operates in continuous mode, displaying matching records from a file based on a search criterion. This basic functionality works just fine. I decided to allow the user to narrow down the output by providing some extra controls to allow the user to filter the recordset via the .Filter form property. ...
7
12501
by: DeZZar | last post by:
Hi all, Unfortunately I am quite a novice with Access!! I've created a number of data bases for my work however becuase my skills are limited to really built in functionality and wizards my programs are not really user friendly. I have searched and searched and tried numerous times to get the following right to no avail - and I think its really becuase of my lack of understanding.
0
7870
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
8236
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
1
7992
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
1
5732
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
5400
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
0
3850
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
3891
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
1
2378
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
1
1465
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.