Filter speichern und wieder hervorholen

Bis jetzt kann unser Filterformular schon eine ganze Menge: Man klickt sich seine Suchbedingungen zusammen und sieht sofort nur noch die passenden Datensätze. Doch sobald man das Formular schließt, ist der mühsam eingestellte Filter wieder weg. Wer denselben Filter morgen erneut braucht, muss alles noch einmal eintippen. Das ändern wir jetzt. In diesem Teil bringen wir dem Filterformular bei, einen fertigen Filter unter einem Namen zu speichern und ihn später mit einem Klick wieder hervorzuholen.

Die Idee

Stellen Sie sich vor, Sie malen ein schönes Bild und legen es in eine Schublade. Wollen Sie es später wieder ansehen, holen Sie es einfach heraus. Genau das machen wir mit unseren Filtern: Wir schreiben die eingestellten Bedingungen in eine Tabelle – das ist unsere Schublade – und können sie jederzeit wieder herausholen. Damit das Ganze aufgeräumt bleibt, bekommt jeder gespeicherte Filter einen Namen, den man sich selbst aussucht, zum Beispiel „Große Kunden“ oder „Geburtstage diesen Monat“.

Wichtig ist uns dabei eine Sache: Ein gespeicherter Filter soll nicht nur wieder angewendet werden können, sondern auch wieder vollständig im Formular erscheinen – mit allen Feldern, Bedingungen und Werten. So kann man einen alten Filter laden und ihn bei Bedarf noch ein bisschen anpassen, statt ganz von vorn anzufangen.

Ein Knopf, der alles kann

Man könnte nun zwei Knöpfe einbauen: einen zum Speichern und einen zum Laden. Doch das würde schnell unübersichtlich. Wo wählt man beim Laden aus mehreren Filtern den richtigen aus? Wie löscht man einen, den man nicht mehr braucht? Deshalb gehen wir einen anderen Weg: Ein einziger Knopf mit der Aufschrift Filter verwalten im Fuß des Formulars öffnet ein kleines Fenster, in dem man alles erledigen kann – speichern, laden, umbenennen und löschen. So bleibt der Formularfuß aufgeräumt.

Wie dieser neue Knopf aussieht, zeigt Bild 1. Er sitzt zwischen den bereits bekannten Schaltflächen zum Anwenden und Zurücksetzen.

Der neue Knopf

Bild 1: Der neue Knopf „Filter verwalten“ öffnet das Verwaltungsfenster

Ein Klick darauf öffnet das Verwaltungsfenster frmFilterVerwaltung (siehe Bild 2). Links sieht man die Liste der bereits gespeicherten Filter, rechts die Knöpfe für die einzelnen Aktionen. Praktisch: Es werden immer nur die Filter angezeigt, die zu genau diesem Formular gehören – so vermischt sich nichts, wenn man das Filterformular an mehreren Stellen einsetzt.

Das Formular zum Verwalten der Filter

Bild 2: Das Formular zum Verwalten der Filter

Die Prozedur hinter der Schaltfläche frmVerwalten lautet wie folgt:

Private Sub cmdVerwalten_Click()
    DoCmd.OpenForm "frmFilterVerwaltung", , , , , _
        acDialog, DatentragendesFormularName()
End Sub

Wohin die Filter wandern

Zum Aufbewahren brauchen wir zwei Tabellen. Die erste, tblFilter, merkt sich zu jedem Filter einen Steckbrief: zu welchem Formular er gehört, wie er heißt und wann er angelegt und zuletzt geändert wurde.

Für diese Tabelle haben wir außerdem einen eindeutigen Index über die Felder Formularname und Filtername definiert, damit keine doppelten Filternamen je Formular vergeben werden können (siehe Bild 3).

Tabelle zum Speichern der Filter

Bild 3: Tabelle zum Speichern der Filter

Die zweite, tblFilterZeilen, speichert die einzelnen Filterzeilen – also welches Feld, welche Bedingung und welcher Wert in jeder Zeile stehen. Weil ein Filter aus mehreren Zeilen bestehen kann, gehören zu einem Steckbrief in der ersten Tabelle mehrere Einträge in der zweiten.

Den Entwurf dieser Tabelle sehen wir in Bild 4.

Tabelle zum Speichern der Filterzeilen eines Filters

Bild 4: Tabelle zum Speichern der Filterzeilen eines Filters

Damit beim Löschen eines Filters auch seine Zeilen mitverschwinden, verknüpfen wir die beiden Tabellen mit einer Beziehung, die eine sogenannte Löschweitergabe hat. Löscht man den Steckbrief, räumt Access die zugehörigen Zeilen automatisch mit weg (siehe Bild 5).

Tabellen der Lösung und ihre Beziehung

Bild 5: Tabellen der Lösung und ihre Beziehung

Ein kleiner, aber wichtiger Trick steckt bei den Nachschlagefeldern aus dem vorigen Teil. Dort wählt man einen Wert bequem aus einer Liste aus – angezeigt wird zum Beispiel „Stammkunde“, gespeichert wird aber die dahinterliegende Nummer. Genau diese Nummer schreiben wir auch in unsere Schublade.

Beim Hervorholen setzt das Formular die Nummer wieder ein, und die Liste zeigt von selbst wieder den richtigen Text an. So bleibt alles stimmig, selbst wenn sich der angezeigte Text einmal ändert.

Einen Filter in die Schublade legen

Das Fenster zum Verwalten der Filter sehen wir in der Entwurfsansicht in Bild 6.

Entwurf des Formulars zum Verwalten der Filter

Bild 6: Entwurf des Formulars zum Verwalten der Filter

Klickt man hier auf Aktuellen Filter speichern, startet der Speicher-Vorgang für den aktuell definierten Filter.

Die dadurch ausgelöste Prozedur cmdSpeichern_Click (siehe Listing 1) besorgt sich zunächst über FilterFormular eine Referenz auf das noch geöffnete Filterformular – schließlich liegen die einzustellenden Bedingungen ja dort und nicht im Dialog.

Private Sub cmdSpeichern_Click()
    Dim frm As Form, strName As String, bolUeberschreiben As Boolean, varVorgabe As Variant
    Set frm = FilterFormular()
    If frm Is Nothing Then Exit Sub
    varVorgabe = ""
    If Not IsNull(Me.lstFilter.Value) Then
        varVorgabe = Nz(Me.lstFilter.Column(1), "") 'Filtername
    End If
    strName = Trim(Nz(InputBox("Name fuer den Filter:", "Filter speichern", varVorgabe), ""))
    If Len(strName) = 0 Then Exit Sub
    If FilterExistiert(strName) Then
        If MsgBox("Ein Filter '" & strName & "' existiert bereits." & vbCrLf & "Ueberschreiben?", _
                vbQuestion + vbYesNo, "Ueberschreiben") = vbNo Then
            Exit Sub
        End If
        bolUeberschreiben = True
    End If
    If frm.FilterSpeichern(strName, bolUeberschreiben) Then
        ListeAktualisieren
        EintragMarkieren strName
    End If
End Sub

Listing 1: Aufruf des Speichervorgangs für einen Filter

Lässt sich das Formular nicht finden, bricht die Prozedur still ab, denn ohne Filterformular gibt es nichts zu speichern.

Danach wird der Name erfragt. Ist in der Liste bereits ein Filter markiert, verwendet die Prozedur dessen Namen als Vorgabe – so lässt sich ein bestehender Eintrag bequem aktualisieren, ohne den Namen erneut eintippen zu müssen. Die InputBox zeigt diesen Vorschlag an, und das umschließende Trim entfernt überflüssige Leerzeichen am Rand. Gibt der Benutzer keinen Namen ein oder bricht er ab, endet die Prozedur an dieser Stelle.

Bevor gespeichert wird, klärt FilterExistiert, ob unter diesem Namen schon ein Filter abgelegt ist. Falls ja, fragt eine Rückfrage, ob er überschrieben werden soll. Verneint der Benutzer, geschieht nichts weiter; bestätigt er, merkt sich die Prozedur das im Kennzeichen bolUeberschreiben. Handelt es sich dagegen um einen neuen Namen, bleibt dieses Kennzeichen auf False – es wird schlicht ein neuer Filter angelegt.

Die eigentliche Arbeit übergibt die Prozedur nun an das Filterformular, indem sie dessen Funktion FilterSpeichern mit Namen und Überschreiben-Kennzeichen aufruft.

Meldet diese Erfolg, baut ListeAktualisieren die Liste im Dialog neu auf, und EintragMarkieren hebt den soeben gespeicherten Filter darin hervor.

Auf diese Weise sieht der Benutzer sofort, dass sein Filter angekommen ist, und das Fenster bleibt für weitere Aktionen geöffnet.

Die Funktion FilterSpeichern im Detail

Das Speichern eines Filters übernimmt die öffentliche Funktion FilterSpeichern. Sie erhält zwei Parameter: den vom Benutzer vergebenen Namen sowie ein Kennzeichen, ob ein bereits vorhandener Filter gleichen Namens überschrieben werden darf.

Als Ergebnis liefert sie True zurück, wenn das Speichern geklappt hat – so kann der aufrufende Verwaltungsdialog anschließend die Liste aktualisieren. Den ersten Teil des Codes zeigt Listing 2.

Public Function FilterSpeichern(strName As String, bolUeberschreiben As Boolean) As Boolean
    Dim db As DAO.Database
    Dim rstKopf As DAO.Recordset
    Dim rstZeile As DAO.Recordset
    Dim lngFilterID As Long
    Dim strFormular As String
    Dim g As Integer, z As Integer
    Dim strFeld As String, strBedingung As String
    Dim strWert As String, strWert2 As String, strDatumstyp As String
    On Error GoTo Fehler
    strFormular = DatentragendesFormularName()
    If Len(strName) = 0 Then
        MsgBox "Bitte einen Namen fuer den Filter angeben.", vbExclamation, "Name fehlt"
        Exit Function
    End If
    Set db = CurrentDb
    'Pruefen ob schon ein Filter dieses Namens existiert
    lngFilterID = FilterIDErmitteln(strFormular, strName)
    If lngFilterID > 0 And Not bolUeberschreiben Then
        MsgBox "Ein Filter mit diesem Namen existiert bereits.", vbExclamation, "Bereits vorhanden"
        Exit Function
    End If
    If lngFilterID > 0 Then
        'Vorhandene Zeilen entfernen, Kopf aktualisieren
        db.Execute "DELETE FROM tblFilterZeilen WHERE FilterID = " & lngFilterID, dbFailOnError
        db.Execute "UPDATE tblFilter SET GeaendertAm = Now() " & "WHERE FilterID = " & lngFilterID, dbFailOnError
    Else
        'Neuen Kopf anlegen
        Set rstKopf = db.OpenRecordset("tblFilter", dbOpenDynaset)
        rstKopf.AddNew
        rstKopf!Formularname = strFormular
        rstKopf!Filtername = strName
        rstKopf!Angelegt = Now()
        rstKopf!GeaendertAm = Now()
        rstKopf.Update
        rstKopf.Bookmark = rstKopf.LastModified
        lngFilterID = rstKopf!FilterID
        rstKopf.Close
    End If
    ...

Listing 2: Prozedur zum Speichern des Filters, Teil 1

Vorbereitung und Namensprüfung

Nach den Variablendeklarationen aktiviert On Error GoTo Fehler eine Fehlerbehandlung, die am Ende der Funktion steht. Sollte beim Zugriff auf die Tabellen etwas schiefgehen, landet die Ausführung dort und zeigt eine verständliche Meldung, statt mit einem rohen Laufzeitfehler abzustürzen.

Zunächst ermittelt die Funktion über DatentragendesFormularName den Namen des Formulars, dessen Datensätze gefiltert werden. Dieser Name dient später als Zuordnung, damit im Verwaltungsdialog nur die Filter des jeweils passenden Formulars erscheinen. Ist gar kein Name für den Filter angegeben, bricht die Funktion mit einem Hinweis ab – ohne Namen lässt sich ein Filter später schließlich nicht wiederfinden.

Neu anlegen oder überschreiben

Der nächste Block klärt, ob unter diesem Namen bereits ein Filter existiert. Die Hilfsfunktion FilterIDErmitteln liefert entweder die Kennung eines vorhandenen Filters oder den Wert 0. Findet sie einen Treffer und wurde das Überschreiben nicht ausdrücklich erlaubt, meldet die Funktion, dass der Name schon vergeben ist, und beendet sich. So kann ein bestehender Filter niemals versehentlich überschrieben werden.

Darf hingegen überschrieben werden, räumt die Funktion zunächst auf: Sie löscht alle bisherigen Zeilen dieses Filters aus tblFilterZeilen und vermerkt im Steckbrief über GeaendertAm den Zeitpunkt der Änderung. Anschließend werden die Zeilen neu geschrieben – so bleibt kein Rest einer früheren Fassung zurück. Gibt es den Filter dagegen noch nicht, legt die Funktion einen frischen Steckbrief an. Bemerkenswert ist hier die Zeile mit rstKopf.Bookmark = rstKopf.LastModified: Sie springt zum eben angelegten Datensatz, um dessen automatisch vergebene Kennung FilterID auszulesen. Diese Kennung brauchen wir gleich, um die einzelnen Filterzeilen dem richtigen Steckbrief zuzuordnen.

Die einzelnen Filterzeilen schreiben

Nun folgt das Herzstück (zweiter Teil der Prozedur FilterSpeichern, siehe Listing 3). Zwei ineinander verschachtelte Schleifen durchlaufen alle aktiven Gruppen und deren Zeilen – genau in derselben Systematik, mit der auch das Anwenden eines Filters arbeitet. Für jede Zeile liest die Funktion das gewählte Feld und die Bedingung aus. Nur wenn beide gefüllt sind, gilt die Zeile als echte Filterzeile und wird gespeichert; leere Zeilen überspringt die Schleife stillschweigend.

    ...
    'Zeilen schreiben
    Set rstZeile = db.OpenRecordset("tblFilterZeilen", dbOpenDynaset)
    For g = 1 To m_intAktiveGruppen
        For z = 1 To m_intAktiveZeilen(g)
            strFeld = Nz(Me("cboFeld_" & g & "_" & z).Value, "")
            strBedingung = Nz(Me("cboBedingung_" & g & "_" & z).Value, "")
            If Len(strFeld) > 0 And Len(strBedingung) > 0 Then
                'Wert wie beim Anwenden ermitteln
                If m_bolAktKombi(g, z) Then
                    strWert = Nz(Me("cboWert_" & g & "_" & z).Value, "")
                Else
                    strWert = Nz(Me("txtWert_" & g & "_" & z).Value, "")
                    If Len(strWert) = 0 Then
                        strWert = Nz(Me("txtDatum_" & g & "_" & z).Value, "")
                    End If
                End If
                strWert2 = Nz(Me("txtWert2_" & g & "_" & z).Value, "")
                If Len(strWert2) = 0 Then
                    strWert2 = Nz(Me("txtDatumA_" & g & "_" & z).Value, "")
                End If
                strDatumstyp = Nz(Me("cboDatumstyp_" & g & "_" & z).Value, "")
                rstZeile.AddNew
                rstZeile!FilterID = lngFilterID
                rstZeile!GruppeNr = g
                rstZeile!Reihenfolge = z
                rstZeile!Feldname = strFeld
                rstZeile!Bedingung = strBedingung
                rstZeile!Datumstyp = strDatumstyp
                rstZeile!Wert = strWert
                rstZeile!Wert2 = strWert2
                rstZeile.Update
            End If
        Next z
    Next g
    rstZeile.Close
    FilterSpeichern = True
    Exit Function
Fehler:
    MsgBox "Beim Speichern ist ein Fehler aufgetreten:" & vbCrLf & _
        Err.Description, vbCritical, "Fehler"
End Function

Listing 3: Prozedur zum Speichern des Filters, Teil 2

Beim Auslesen des Vergleichswerts zeigt sich, wie eng das Speichern an die übrige Logik angelehnt ist. Handelt es sich um ein Nachschlagefeld, holt die Funktion den Wert aus dem Auswahl-Kombinationsfeld cboWert – und damit die dahinterliegende Nummer, nicht den angezeigten Text.

Andernfalls stammt der Wert aus dem Textfeld txtWert; ist dieses leer, wird ersatzweise das Datumsfeld txtDatum herangezogen. Nach demselben Muster ermittelt die Funktion einen etwaigen zweiten Wert – nötig bei der Bedingung Ist zwischen – aus txtWert2 beziehungsweise txtDatumA. Zusätzlich sichert sie den Datumstyp, damit vordefinierte Zeiträume wie „Diesen Monat“ später korrekt wiederhergestellt werden.

Steht der Datensatz zusammen, wird er mit AddNew angelegt, mit den Werten gefüllt und mit Update gespeichert. Die Felder GruppeNr und Reihenfolge halten dabei fest, an welcher Stelle die Zeile stand – diese Angaben sind später beim Laden entscheidend, damit die Zeilen wieder in der richtigen Anordnung erscheinen. Sind alle Zeilen geschrieben, schließt die Funktion das Recordset, meldet mit True den Erfolg und ist fertig.

Einen Filter wieder hervorholen

Das Hervorholen eines gespeicherten Filters ist der spannendste Teil, denn hier muss das Formular seinen früheren Zustand exakt nachbauen.

Der Trick dabei ist einfach und clever zugleich: Statt sich etwas Neues auszudenken, tut das Formular beim Laden genau das, was auch ein Mensch täte. Es wählt ein Feld aus, dann eine Bedingung, dann trägt es den Wert ein – Zeile für Zeile. Dadurch ist garantiert, dass sich ein geladener Filter genauso verhält wie ein von Hand eingegebener.

Access im Unternehmen

Unser exklusives Angebot für Dich!

Access im Unternehmen
13,25 € im Monat*

(Gilt für den Abschluss eines Jahres-Abonnements im ersten Jahr, danach 189,-/Jahr)

Hier geht’s weiter →

Die ersten 4 Wochen kostenlos testen – voller Zugriff auf alle Artikel, vollständigen Code und Beispieldatenbanken. Kein Risiko: Wenn es nicht passt, kündigst Du einfach innerhalb der ersten vier Wochen.

PayPal VISA Mastercard SEPA
Kostenlos & unverbindlich

Hast Du eine konkrete Frage zu Deiner eigenen Access-Anwendung?

Vielleicht stellt Deine Anwendung Dich vor eine Herausforderung, zu der Du bisher keine Lösung findest. Schlechte Performance, kein ausreichender Zugriffsschutz, Du bist unsicher über Dein Datenmodell oder Dein Code liefert unerklärliche Fehler?

In unserem kostenlosen Access-Audit schaut sich André Minhorst persönlich gemeinsam mit Dir Deine Lösung per Zoom an – und zeigt Dir, wo Datenmodell, VBA-Code, Ergonomie und Sicherheit Optimierungspotenzial bieten.

Jetzt kostenloses Access-Audit anfordern →