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).
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.
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.
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:
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).
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
Es fehlt noch ein genauerer Blick auf die Klasse clsFastFilterControl. Diese wird für jedes gebundene Steuerelement im Unterformular genau einmal instanziert, denn wir wollen ja für jedes dieser Steuerelemente das Ereignis Bei Fokuserhalt implementieren, um dort das aktuelle Steuer-element in der Variablen m_CurrentControl des Objekts basierend auf der Klasse clsFastFilter zu speichern.
Die Klasse clsFastFilterControl soll zunächst einmal einen Verweis auf das erstellende Objekt speichern, hier also das Objekt auf Basis der Klasse clsFastFilter. Dazu verwenden wir die folgende Variable:
Private m_Parent As clsFastFilter
Die benötigte Property Set-Methode sieht so aus:
Public Property Set MyParent(obj As clsFastFilter) Set m_Parent = obj End Property
Außerdem wollen wir bei den Steuerelementen des Datenblatts unter den Steuerelementtypen Textfeld, Kombinationsfeld und Kontrollkästchen unterscheiden (andere Steuerelemente machen im Datenblatt auch keinen Sinn). Daher deklarieren wir die folgenden drei Variablen innerhalb der Klasse, da wir ja noch nicht wissen, welchen Steuerelementtyp die Klasse aufnehmen soll:
Private WithEvents txt As TextBox Private WithEvents cbo As ComboBox Private WithEvents chk As CheckBox
Nun fehlt noch die Property Set-Methode, die das zu untersuchende Steuerelement entgegennimmt und einige weitere Schritte durchführt. Diese sieht wie folgt aus:
Public Property Set Control(ctl As Control) Select Case ctl.ControlType Case acTextBox Set txt = ctl txt.OnGotFocus = "[Event Procedure]" Case acComboBox Set cbo = ctl cbo.OnGotFocus = "[Event Procedure]" Case acCheckBox Set chk = ctl chk.OnGotFocus = "[Event Procedure]" End Select End Property
Die Prozedur prüft zunächst anhand der Eigenschaft ControlType, um welchen Typ es sich handelt. Im Falle von acTextBox füllt sie beispielsweise die Variable txt mit einem Verweis auf das mit dem Parameter ctl gelieferte Steuerelement. Außerdem stellt sie die Eigenschaft OnGotFocus auf den Wert [Event Procedure] ein, was im Eigenschaftsfenster des Formulars dem Einstellen der Eigenschaft Bei Fokuserhalt auf [Ereignisprozedur] entspricht. Für die beiden anderen Typen, die mit den Konstanten dbComboBox und dbCheckBox geprüft werden, verläuft dies ähnlich. Schließlich müssen wir noch das Ereignis Bei Fokuserhalt für die drei deklarierten Objektvariablen für die drei Steuerelementtypen Textfeld, Kombinationsfeld und Kontrollkästchen implementieren, was wir wie folgt erledigen:
Private Sub cbo_GotFocus() Set m_Parent.CurrentControl = cbo End Sub Private Sub chk_GotFocus() Set m_Parent.CurrentControl = chk End Sub Private Sub txt_GotFocus() Set m_Parent.CurrentControl = txt End Sub
In diesen drei Prozeduren weisen wir jeweils der Eigenschaft CurrentControl des in m_Parent gespeicherten Objekts (also unsere Instanz der Klasse clsFastFilter) eine Referenz auf das auslösende Steuerelement zu, die dann wiederum in der Variablen m_CurrentControl der Klasse clsFastFilter landet. Damit ist die Programmierung der grundlegenden Funktion bereits erledigt: Wir können das Datenblatt nun nach den Werten einzelner Felder filtern.
Filtern nach „Enthält …“
Für den Einsatz mit Textfeldern wäre es noch interessant, wenn wir auch Vergleiche mit Teilausdrücken erlauben würden. Wie wäre es etwa, wenn Sie nur ein paar Buchstaben des Feldinhalts eines Textfeldes markieren und danach in allen Datensätzen suchen wollten Das wäre schon praktisch, also machen wir uns an die Arbeit. Zu diesem Zweck fügen wir dem Formular eine weitere Schaltfläche namens cmdSchnellerFilterTeilausdruck hinzu. Die Prozedur Form_Load erweitern wir entsprechend um die Zuweisung dieser Schaltfläche zur gleich noch anzulegenden Eigenschaft in der Klasse clsFast-Filter:
Private Sub Form_Load() Set objFastFilter = New clsFastFilter With objFastFilter ... Set .FastFilterContainsButton = Me!cmdSchnellerFilterTeilausdruck End With End Sub
Die Klasse clsFastFilter erhält eine neue Variable zum Speichern der Schaltfläche:
Private WithEvents m_FastFilterContainsButton As CommandButton
Um diese Schaltfläche der Variablen zuzuweisen, benötigen wir eine entsprechende Property Set-Prozedur:
Public Property Set FastFilterContainsButton( cmd As CommandButton) Set m_FastFilterContainsButton = cmd With cmd .OnClick = "[Event Procedure]" End With End Property
Im gleichen Zuge fügen wir eine Variable hinzu, welche die Teilzeichenkette speichert:
Private m_Searchstring As String
Um diese einzustellen, legen wir eine öffentliche Eigenschaft per Property Let-Prozedur fest:
Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...
den kompletten Artikel im PDF-Format mit Beispieldatenbank
diesen und alle anderen Artikel mit dem Jahresabo