Schneller Filter

Wenn Sie ein Abonnement des Magazins 'Access im Unternehmen' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

Schneller Filter

Formulare in der Datenblattansicht bieten alle Filter- und Sortiermöglichkeiten, die das Benutzerherz begehrt. Allerdings sind diese nicht unbedingt immer schnell erreichbar – hier und da könnte es noch ein wenig fixer gehen. Ein Beispiel ist ein Filter, der nur die Datensätze anzeigt, die den Wert des aktuell markierten Feldes im jeweiligen Feld enthalten. Wenn Sie also etwa eine Reihe von Artikeln anzeigen, die einer bestimmten Kategorie angehören und schnell nur noch die Artikel dieser Kategorie sehen wollen, benötigen Sie dazu mehrere Mausklicks. Dieser Beitrag zeigt, wie Sie verschiedene Suchen mit einem einfachen Klick auf eine Schaltfläche erledigen.

Die Datenblattansicht von Access-Formularen bietet eine Reihe von Möglichkeiten, schnell nach Daten zu suchen oder diese zu sortieren.

Dazu klicken Sie einfach auf das nach unten zeigende Dreieck rechts im Spaltenkopf der jeweiligen Spalte. Hier sehen Sie auf Anhieb zwei Einträge zum Sortieren in verschiedenen Richtungen oder zum Selektieren verschiedener, im aktuellen Feld enthaltener Werte (s. Bild 1).

Filtern nach allen vorhandenen Werten

Bild 1: Filtern nach allen vorhandenen Werten

Der Untereintrag Textfilter liefert etwa für Textfelder weitere Möglichkeiten: Hier können Sie beispielsweise nach Datensätzen suchen, deren markiertes Feld einen benutzerdefinierten Wert enthält. Bei Textfeldern können hier etwa die Vergleichsoperatoren Gleich, Nicht gleich, Beginnt mit, Beginnt nicht mit, Enthält und weitere verwendet werden (s. Bild 2). Andere Felddatentypen halten dem Datentyp entsprechende Vergleichskriterien bereit.

Filtern nach benutzerdefinierten Vergleichswerten

Bild 2: Filtern nach benutzerdefinierten Vergleichswerten

Datensätze mit gleichem Feldwert finden

Was aber hier fehlt, ist die einfache Möglichkeit, schnell alle Einträge anzuzeigen, die den gleichen Wert im zurzeit markierten Feld aufweisen wie der aktuelle Datensatz. Und genau diese Funktion wollen wir nun nachrüsten. Für das Feld Artikelname macht dies natürlich recht wenig Sinn, aber beim Lieferanten oder bei der Kategorie finden sich schnell Einsatzmöglichkeiten.

Warum nicht beim Artikelnamen? Nun: Dabei handelt es sich um ein Feld mit einem eindeutigen Index. Da nur jeweils ein Datensatz mit dem aktuellen Wert vorhanden ist, macht es wenig Sinn, danach zu filtern ... außer natürlich, wenn Sie etwa aus Gründen der Übersicht nur diesen einen Datensatz anzeigen möchten. Also nehmen wir diese einfache Variante einfach mit hinzu.

Später wollen wir jedoch gerade für Textfelder noch eine schnelle Filterfunktion hinzufügen, mit der Sie sogar alle Datensätze anzeigen können, die den aktuell markierten Wert enthalten.

Der Filter soll dann wie im Beispiel aus Bild 3 funktionieren. Der Benutzer markiert den Wert, nach dem die Daten gefiltert werden sollen, und klickt auf die Schaltfläche Schneller Filter. Daraufhin werden alle Datensätze ausgeblendet, deren Inhalt im betroffenen Feld nicht mit dem Vergleichswert übereinstimmt. Eine weitere Schaltfläche soll den Filter wieder deaktivieren.

So soll der Filter nach einem Feldwert arbeiten.

Bild 3: So soll der Filter nach einem Feldwert arbeiten.

Schaltfläche zum schnellen Filtern

Beginnen wir doch einfach mit einer Schaltfläche, die wir cmdSchnellerFilter nennen und mit der Beschriftung Schneller Filter versehen. Diese soll die Datenherkunft des Unterformulars in dem Formular, in dem sich die Schaltfläche befindet, nach dem Wert des zuvor markierten Feldes filtern. Wie sich herausstellt, ist dies gar nicht so einfach, denn wir finden erst gar nicht heraus, welches Feld gerade überhaupt markiert war.

Unsere erste Idee war es nämlich, das Steuerelement mit dem besagten Filtervergleichswert einfach über den Ausdruck Screen.PreviousControl.Name zu ermitteln und diesen per Debug.Print im Direktbereich auszugeben. Dazu haben wir die Beim Klicken-Ereignisprozedur der Schaltfläche cmdSchnellerFilter wie folgt ausgestattet:

Private Sub cmdSchnellerFilter_Click()
     Debug.Print Screen.PreviousControl.Name
End Sub

Das Ergebnis lieferte aber leider nicht das gesuchte Steuerelement, sondern den Namen des Unterformulars:

sfmArtikel_SchnellerFilter

Zuletzt aktives Feld ermitteln

Wir stehen nun also vor dem Problem, zwar zum Zeitpunkt den Inhalt des zuletzt aktivierten Feldes im Unterformular zu benötigen, dieses aber nicht mehr zu kennen.

Es gibt nun diverse Möglichkeiten, die gewünschte Information zu erhalten. Eine davon lautet, irgendwo eine Variable vorzuhalten, die wir mit einem Verweis auf das jeweils aktive Steuerelement des Unterformulars füllen und dann beim Mausklick auf die Schaltfläche cmdSchnellerFilter über diese Variable auf das Feld und seinen Inhalt zugreifen. Das ist allerdings mit einigem Aufwand verbunden, wenn wir es auf dem einfachen Weg erledigen. Dieser sieht vor, eine Variable zum Speichern des zuletzt verwendeten Feldes im Klassenmodul des Hauptformulars zu deklarieren. Außerdem legen wir für jedes Steuerelement im Unterformular eine Ereigniseigenschaft namens Bei Fokuserhalt an und hinterlegen dafür eine Ereignisprozedur, welche einen Verweis auf das jeweilige Steuerelement in die Variable im Klassenmodul des Hauptformulars einträgt.

Mit der Ereignisprozedur Beim Klicken der Schaltfläche cmdSchnellerFilter können Sie dann aus der Variablen den Wert ermitteln und nach dem Feld, an welches das Steuerelement aus der Variablen gebunden ist, filtern.

Wir müssen nur für jedes betroffene Steuerelement im Unterformular eine entsprechende Ereignisprozedur für das Ereignis Bei Fokuserhalt hinterlegen. Und ebenso für alle Steuerelemente der Unterformulare in anderen Formularen, die Sie mit der Funktion ausstatten möchten.

Lösung mit Klasse

Nun ist Access im Unternehmen aber weniger bekannt dafür, den Leser mit Fleißarbeit auszustatten. Wir suchen eher nach einer Lösung, die der Leser in wenigen Minuten implementieren kann. Also greifen wir, wie schon ein paar Mal geschehen, auf Klassenmodule zurück, in denen wir die gewünschte Funktionalität unterbringen. Das Klassenmodul des Hauptformulars soll nur mit wenigen Zeilen Code ausgestattet werden, die zum größten Teil in der Ereignisprozedur Form_Load landen. Insgesamt sieht der benötigte Code wie folgt aus. Als Erstes benötigen wir eine Objektvariable, welche den Verweis auf die gleich noch erläuterte Klasse clsFastFilter aufnimmt:

Dim objFastFilter As clsFastFilter

Als Nächstes folgt dann die Ereignisprozedur Form_Load, die wir folgt aussieht:

Private Sub Form_Load()
     Set objFastFilter = New clsFastFilter
     With objFastFilter
         Set .Subform = Me!sfmArtikel_SchnellerFilter.Form
         Set .FastFilterButton = Me!cmdSchnellerFilter
     End With
End Sub

Sie erstellt zunächst ein neues Objekt auf Basis der Klasse clsFastFilter und speichert den Verweis darauf in der Variablen objFastFilter. Dann weist sie den beiden Eigenschaften Subform und FastFilterButton Verweise auf das Unterformular mit den zu durchsuchenden Datensätzen (Achtung: .Form liefert die richtige Referenz) und auf die Schaltfläche zu, welche den Filter erstellen soll. Zur Abrundung fügen Sie noch eine Ereignisprozedur für die Schaltfläche mit der Beschriftung Filter leeren hinzu, welche schlicht den Filter des Unterformulars leert und somit wieder alle Datensätze anzeigt:

Private Sub cmdFilterLeeren_Click()
     Me!sfmArtikel_SchnellerFilter.Form.Filter = ""
End Sub

Wichtige Vorbereitung

Wenn Sie Klassen erstellen, die Objekte wie etwa Formulare oder die enthaltenen Steuerelemente referenzieren und deren Ereignisse implementieren wollen, muss für das jeweilige Formular (und somit auch für Unterformulare) auch ein Klassenmodul vorliegen! In unserem Fall haben wir etwa für das Unterformular mit dem Datenblatt noch kein Klassenmodul angelegt. Dies erfolgt automatisch, sobald Sie für eine der Ereigniseigenschaften des Formulars ein Ereignis anlegen und dieses über die Schaltfläche mit den drei Punkten im VBA-Editor öffnen. Sie können dies aber auch durch einfaches Einstellen der Eigenschaft Enthält Modul erledigen. Diese Eigenschaft finden Sie im Reiter Andere des jeweiligen Formulars (s. Bild 4).

Hinzufügen eines Klassenmoduls per Eigenschaft

Bild 4: Hinzufügen eines Klassenmoduls per Eigenschaft

Die Klasse clsFastFilter

Diese Klasse ist die Steuerzentrale der Lösung. Sie nimmt die Verweise auf die beteiligten Elemente entgegen, also das Unterformular sowie die Schaltfläche zum Auslösen des Filters. Das Unterformular wird mit der folgenden Variablen referenziert, die im Kopf des Klassenmoduls deklariert wird:

Private m_Subform As Form

Die Schaltfläche zum Auslösen des Filters landet per Verweis in dieser Variablen:

Private WithEvents m_FastFilterButton As CommandButton

Die Variable ist mit dem Schlüsselwort WithEvents deklariert, was dafür sorgt, dass wir in diesem Klassenmodul Ereignisprozeduren für das Steuerelement implementieren können. Desweiteren benötigen wir noch zwei weitere Variablen. Die erste ist eine Collection und nimmt die Instanzen der Wrapper-Klassen auf, von denen wir für jedes filterbare Steuerelement eine erstellen und in die Collection schreiben:

Private colControls As Collection

Außerdem brauchen wir noch die besagte Variable, welche das zuletzt durch den Benutzer angeklickte Steuerelement im Unterformular aufnimmt:

Private m_CurrentControl As Control

Damit die Wrapper-Objekte, die jeweils eines der gebundenen Steuerelemente im Unterformular aufnehmen, einen Verweis auf das zuletzt durch den Benutzer angeklickte Element in die Variable m_CurrentControl schreiben können, stellen wir eine Property Set-Methode in der Klasse clsFastFilter bereit, die wie folgt aussieht:

Public Property Set CurrentControl(ctl As Control)
     Set m_CurrentControl = ctl
End Property

Damit wir die Funktion, welche die Schaltfläche cmdFastFilter auslöst, auch in der Klasse clsFastFilter unterbringen können und diese nicht in jedem neuen Formular erneut schreiben müssen, füllen wir die lokale Variable m_FastFilterButton über die Property Set-Prozedur FastFilterButton mit einem Verweis auf die jeweilige Schaltfläche. Die Prozedur sieht so aus:

Public Property Set FastFilterButton(cmd As CommandButton)
     Set m_FastFilterButton = cmd
     cmd.OnClick = "[Event Procedure]"
End Property

Sie erwartet einen Verweis auf die Schaltfläche als Parameter und trägt diese in die Variable m_FastFilterButton ein. Außerdem legt sie noch fest, dass in diesem Klassenmodul eine Implementierung des Ereignisses OnClick vorliegen könnte und beim Auslösen entsprechend berücksichtigt werden soll.

Die Hauptarbeit in der Klasse übernimmt die Property Set-Methode Subform, mit welcher die Form_Load-Ereignisprozedur der Klasse clsFastFilter das zu verwendende Unterformular zuweist. Sie erwartet das Formular als Parameter und sieht wie folgt aus:

Public Property Set Subform(frm As Form)
     Dim ctl As Control
     Dim objFastFilterControl As clsFastFilterControl
     Dim strControlSource As String
     Set m_Subform = frm
     Set colControls = New Collection
     For Each ctl In m_Subform.Controls
         strControlSource = ""
         On Error Resume Next
         strControlSource = ctl.ControlSource
         On Error GoTo 0
         If Len(strControlSource) > 0 Then
             Set objFastFilterControl =  New clsFastFilterControl
             With objFastFilterControl
                 Set .Control = ctl
                 Set .MyParent = Me
                 colControls.Add objFastFilterControl
             End With
         End If
     Next ctl
End Property

Die Prozedur stellt zunächst die Variable m_Subform auf das übergebene Formular ein. Dann instanziert sie eine neue Collection und speichert diese in der Variablen colControls. Schließlich durchläuft sie alle Steuerelemente des mit frm angegebenen Unterformulars. In der dafür verwendeten For Each-Schleife prüft die Prozedur, ob es sich beim aktuell durchlaufenen Steuerelement überhaupt um ein gebundenes Steuerelement handelt. Andere Steuerelemente wie etwa Bezeichnungsfelder brauchen wir gar nicht zu berücksichtigen. Dazu leert die Prozedur eine Variable namens strControlSource und versucht dann, diese bei deaktivierter Fehlerbehandlung mit dem Wert der Eigenschaft ControlSource des aktuellen Steuerelements aus ctl zu füllen. Enthält strControlSource danach keine leere Zeichenkette, handelt es sich um ein gebundenes Steuerelement und es kann in Form des Wrapper-Objekts auf Basis der Klasse clsFastFilterControl referenziert und zur Collection colControls hinzugefügt werden. Dann erstellt die Prozedur ein neues Objekt auf Basis von clsFastFilterControl, füllt dessen Eigenschaft Control mit einem Verweis auf das aktuelle Steuerelement und die Eigenschaft MyParent mit einem Verweis auf sich selbst, also die Instanz der Klasse clsFastFilter. Wozu dies nötig ist, erfahren Sie gleich bei der Beschreibung der Klasse clsFastFilterControl. Nun müssen wir nur noch dafür sorgen, dass das Wrapper-Objekt, das ja lokal innerhalb der Property Set-Prozedur deklariert wurde und anderenfalls mit dem Ende der Prozedur erlöschen würde, nicht im Nirwana verschwindet. Dazu fügt die Prozedur es zur Collection colControls hinzu.

Gehen wir an dieser Stelle vereinfachend davon aus, dass die Wrapper-Objekte beim Anklicken durch den Benutzer dafür sorgen, dass ein Verweis auf das angeklickte gebundene Steuerelement in der Variablen m_CurrentControl landet.

Dann können wir uns die Ereignisprozedur ansehen, die durch einen Mausklick auf die mit der Variablen m_FastFilterButton referenzierte Schaltfläche ausgelöst wird. Die Prozedur sieht wie in Listing 1 aus. Sie liest zunächst den Namen des Feldes, das dem mit der Variablen m_CurrentControl referenzierten und zuletzt durch den Benutzer angeklickten Steuerelement angehört, in die Variable strControlSource ein.

Private Sub m_FastFilterButton_Click()
     Dim rst As DAO.Recordset
     Dim fld As DAO.Field
     Dim strControlSource As String
     strControlSource = m_CurrentControl.ControlSource
     Set rst = m_Subform.Recordset
     Select Case rst.Fields(strControlSource).Type
         Case dbText
             m_Subform.Filter = strControlSource & " = '" _
                 & Replace(m_CurrentControl.Value, "'", "''") & "'"
             m_Subform.FilterOn = True
        Case dbDate
             m_Subform.Filter = strControlSource & " = " & CDbl(m_CurrentControl.Value)
             m_Subform.FilterOn = True
         Case Else
             m_Subform.Filter = strControlSource & " = " & m_CurrentControl.Value
             m_Subform.FilterOn = True
     End Select
End Sub

Listing 1: Implementierung des Beim Klicken-Ereignisses der Filter-Schaltfläche

Dann füllt sie eine Recordset-Variable namens rst mit dem Recordset des zu filternden Unterformulars. Sie ermittelt dann für das Element der Fields-Auflistung mit dem Namen aus strControlsource den Datentyp und gleicht diesen in einer Select Case-Bedingung mit verschiedenen Werten ab. Wir haben hier nur dbText für Textfelder, dbDate für Datumsfelder und alle übrigen Datentypen untersucht, obwohl hier noch weitere Unterscheidungen möglich wären. Im Falle des Wertes dbText stellt die folgende Anweisung einen Vergleichsausdruck zusammen, der aus dem Feldnamen aus strControlSource, dem Gleichheitszeichen und dem in Hochkommata eingefassten Wert des Steuerelements aus m_CurrentControl besteht, also etwa Artikelname = 'Chai'. Dieser Vergleichsausdruck landet in der Eigenschaft Filter des Unterformulars. Das Einstellen einer weiteren Eigenschaft namens FilterOn auf den Wert True sorgt schließlich dafür, dass der Filter auch auf die aktuellen Daten angewendet wird.

Wichtig ist an dieser Stelle noch der Hinweis auf das eventuelle Auftreten von Hochkommata oder Anführungszeichen innerhalb der Zeichenkette. Diese führen beim Zusammensetzen des Filterausdrucks zu Fehlern, was nicht geschieht, wenn Sie diese verdoppeln – in diesem Fall durch entsprechenden Einsatz der Replace-Funktion.

Im Falle eines Datums würde, wenn wir dieses einfach wie eine Zahl behandeln, ein Ausdruck wie Auslaufdatum = 1.1.2016 verwendet, was zu einem Fehler führt. Daher wandeln wir den Wert des Datumsfeldes zuvor mit der CDbl-Funktion in einen Double-Wert um, der dann keinen Fehler mehr auslöst.

Für alle übrigen Datentypen soll einfach das Feld mit dem jeweiligen Wert verglichen werden.

Die Klasse clsFastFilterControl

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.