Im Beitrag „Besserer „Suchen und Ersetzen“-Dialog: Grundgerüst“ haben wir das Grundgerüst für einen Ersatz der eingebauten Suchen- und Ersetzen-Funktion von Access erstellt. Im vorliegenden Artikel wollen wir nun die Such- und Ersetzungsfunktionen mit Leben füllen. Dabei werden Sie nicht nur die begonnene Lösung vervollständigen, sondern auch noch Einiges über das VBA-gesteuerte Suchen und Ersetzen in Datenblättern lernen.
Die Lösung aus dem oben genannten Beitrag bildet bisher die vollständige Benutzeroberfläche des eingebauten Suchen und Ersetzen-Dialogs ab.
Allerdings haben wir die eigentlichen Suchen- und Ersetzen-Funktionen noch nicht abgebildet. Das holen wir in diesem Beitrag nach.
Dabei unterscheiden wir verschiedene Fälle, die wir in den folgenden Abschnitten im Detail beschreiben.
Einfaches Weitersuchen in einem Feld
Der erste und einfachste Fall ist die Suche im aktuell markierten Feld (siehe Bild 1). Dazu enthält die Eigenschaft Suchen in den Wert Aktuelles Feld. Vorher müssen wir uns allerdings noch um einige weitere Informationen kümmern.
Bild 1: Erster Fall: Suchen in einem einzelnen Feld
Referenzieren des aktuellen Datenblatts
Wichtig ist für den Suchen und Ersetzen-Dialog, dass wir einen Verweis auf das Datenblatt erhalten, das wir untersuchen wollen. Dazu führen wir in der Ereignisprozedur, die durch das Öffnen des Formulars frmSuchenUndErsetzen ausgelöst wird, einige Aktionen aus (siehe Listing 1).
Private Sub Form_Open(Cancel As Integer) Dim strObjektname As String Set objAktuellesDatenblatt = AktuelleDatenblattansichtObjekt If objAktuellesDatenblatt Is Nothing Then MsgBox "Suchen und Ersetzen steht nur für die Datenblattansicht zur Verfügung.", vbOKOnly + vbExclamation, _ "Keine Datenblattansicht aktiviert" Cancel = True Exit Sub Else Set rstDatenblatt = objAktuellesDatenblatt.Recordset Me.TimerInterval = 100 End If If Not IsNull(Me.OpenArgs) Then If Me.OpenArgs = "Ersetzen" Then bolErsetzen = True End If End If End Sub
Listing 1: Aktionen beim Öffnen des Formulars
Die Prozedur ruft zuerst die Funktion AktuelleDatenblattansichtObjekt auf. Diese sehen wir in Listing 2. Sie versucht, bei deaktivierter Fehlerbehandlung über Screen.ActiveDatasheet auf das aktive Datasheet-Objekt zuzugreifen. Das gelingt in vielen Fällen, aber zum Beispiel nicht, wenn sich das Datenblatt in einem Unterformular befindet. Dann liefert der Zugriff auf Screen.ActiveDatasheet einen Fehler zurück.
Public Function AktuelleDatenblattansichtObjekt() As Object Dim objAktuellesObjekt As Object On Error Resume Next Set objAktuellesObjekt = Screen.ActiveDatasheet On Error GoTo 0 If objAktuellesObjekt Is Nothing Then On Error Resume Next Set objAktuellesObjekt = Screen.ActiveForm.ActiveControl If objAktuellesObjekt.CurrentView = acCurViewDatasheet Then Set AktuelleDatenblattansichtObjekt = objAktuellesObjekt.Form Exit Function End If On Error GoTo 0 Else Set AktuelleDatenblattansichtObjekt = objAktuellesObjekt End If End Function
Listing 2: Aktuelle Datenblattansicht holen
Deshalb deaktivieren wir hier die Fehlerbehandlung und prüfen anschließend, ob objAktuellesObjekt einen Wert enthält. Falls nicht, versuchen wir es auf einem anderen Weg. Wir versuchen, ebenfalls bei deaktivierter Fehlerbehandlung, mit Screen.ActiveForm.ActiveControl einen Verweis auf das Unterformular zu erhalten und in objAktuellesObjekt einzutragen. Wie gesagt: Datenblätter in Hauptformularen, Tabellen oder Abfragen liefert uns bereits Screen.ActiveDatasheet das aktuelle Datenblatt.
Die Funktion prüft dann, ob das aktive Steuerelement ein Datenblatt ist. Falls ja, liefert sie einen Verweis auf die Form-Eigenschaft von objAktuellesObjekt zurück.
Die Prozedur Form_Open schreibt das Ergebnis in die Variable objAktuellesDatenblatt, das wie folgt im Kopf des Klassenmoduls des Formulars deklariert wird:
Private objAktuellesDatenblatt As Object
Dort deklarieren wir direkt auch noch:
Private rstDatenblatt As DAO.Recordset
Wenn objAktuellesDatenblatt nun leer ist, gibt Form_Open eine Meldung aus, dass die Suchen und Ersetzen-Funktion nur für die Datenblattansicht zur Verfügung steht.
Danach wird die Prozedur mit Cancel = True verlassen, sodass das Formular gar nicht erst angezeigt wird.
Anderenfalls schreibt sie einen Verweis auf das Recordset in objAktuellesDatenblatt in die Variable rstDatenblatt und aktiviert einen Timer, der alle 100 Millisekunden feuern soll (es ginge vermutlich auch ein größeres Intervall). Diesen Timer schauen wir uns gleich an.
Schließlich prüft die Prozedur noch das Öffnungsargument. Enthält dieses den Wert Ersetzen, wird bolErsetzen auf True eingestellt. Dies sorgt später dafür, dass neben dem Suchen– auch das Ersetzen-Tab im Formular angezeigt wird.
Die Prozedur, die durch das Ereignis Bei Zeitgeber ausgelöst wird, prüft bei jedem Aufruf, ob das Datenblatt aus rstDatenblatt noch vorhanden ist. Dazu greifen wir einfach auf die RecordCount-Eigenschaft zu. Löst dies einen Fehler aus, sind das Recordset und somit wohl auch die Datenblattansicht, für die der Suchen und Ersetzen-Dialog geöffnet wurde, wohl nicht mehr vorhanden:
Private Sub Form_Timer() Dim lngRecordcount As Long On Error Resume Next lngRecordcount = rstDatenblatt.RecordCount If Not Err.Number = 0 Then On Error GoTo 0 Me.TimerInterval = 0 DoCmd.Close acForm, Me.Name End If End Sub
Einfachen Suchvorgang ausführen
Damit kommen wir zum eigentlichen Suchvorgang. Wir schauen uns zuerst einen einfachen Suchvorgang mit den Parametern aus Bild 2 an.
Bild 2: Einfache Suche
Ein Klick auf die Schaltfläche Weitersuchen löst die Prozedur aus Listing 3 aus. Diese holt sich den Suchbegriff aus dem Kombinationsfeld cboSuchenNach in die Variable txtSuchbegriff. Wenn dieser eine Länge von 0 hat, bricht sie die Prozedur ab.
Private Sub cmdWeitersuchen_Click() Dim strFeld As String Dim strSuchbegriff As String Dim strKriterium As String Dim bolFound As Boolean Dim strMeldung As String strSuchbegriff = Nz(Me.cboSuchenNach.Column(1), "") If Len(strSuchbegriff) = 0 Then Exit Sub Select Case Me.cboSuchenIn Case "Aktuelles Feld" If KriteriumAktuellesFeld(strSuchbegriff, strFeld) = True Then bolFound = False Select Case Me.cboSuchen Case "Alle" Call SuchenDatensatz(bolFound, strKriterium, strFeld, strSuchbegriff) Case "Abwärts" rstDatenblatt.FindNext strKriterium If rstDatenblatt.NoMatch Then bolFound = False Else bolFound = True End If Case "Aufwärts" rstDatenblatt.FindPrevious strKriterium If rstDatenblatt.NoMatch Then bolFound = False Else bolFound = True End If End Select If Not bolFound Then lngAbsolutePositionStart = -1 MsgBox "Kein weiterer Eintrag gefunden." Else Select Case Me.cboVergleichen Case "Teil des Feldinhalts", "Anfang des Feldinhalts" Call SuchbegriffMarkieren(objAktuellesDatenblatt.ActiveControl, Me.cboSuchenNach.Column(1)) End Select End If Else MsgBox strMeldung, vbExclamation + vbOKOnly, "Suche nicht erfolgreich" End If Case "Aktuelles Dokument" MsgBox "Nicht implementiert." End Select End Sub
Listing 3: Code hinter der Schaltfläche Weitersuchen
Anderenfalls erfolgt eine Unterscheidung nach dem Umfang der Suche, der mit dem Kombinationsfeld cboSuchenIn festgelegt wird. Im Fall von Aktuelles Feld beginnt der Teil, den wir hier beschreiben wollen.
Es folgt ein Aufruf der Funktion KriteriumAktuellesFeld, der als Ergebnis das Suchkriterium für die Suche liefern soll. Das ist recht aufwendig, da wir prüfen müssen, welchen Datentyp das Feld enthält, ob dieser zum eingegebenen Suchbegriff passt und so weiter – mehr dazu weiter unten. Wir gehen an dieser Stelle davon aus, dass wir zum Beispiel den Suchbegriff adi eingegeben haben und als Kriterium mit dem Parameter strKriterium den folgenden Ausdruck zurückerhalten:
Vorname LIKE ''*adi*''
Dann wird die Variable bolFound zunächst auf False eingestellt. Nun prüfen wir den Inhalt von cboSuchen und steuern abhängig davon einen der Case-Zweige für Alle, Abwärts oder Aufwärts an.
Suchen nach allen Vorkommen im aktuellen Feld
Wenn der Benutzer Alle gewählt hat, haben wir die umfangreichste Ausgabe vor uns. Während wie bei Abwärts oder Aufwärts einfach vom aktuellen Datensatz aus nach oben oder unten suchen können, müssen wir in diesem Fall vom aktuellen Datensatz aus nach unten suchen, dann oben neu beginnen und vor dem aktuellen Datensatz stoppen. Da dies ein wenig aufwendiger ist, haben wir diesen Teil in eine eigene Prozedur namens SuchenDatensatz ausgelagert (siehe Listing 4).
Private Sub SuchenDatensatz(bolGefunden As Boolean, strKriterium As String) If lngAbsolutePositionStart = -1 Then lngAbsolutePositionStart = rstDatenblatt.AbsolutePosition bolVonVornGestartet = False Else If Not lngLetzteFundstelle = rstDatenblatt.AbsolutePosition Then lngAbsolutePositionStart = rstDatenblatt.AbsolutePosition bolVonVornGestartet = False End If End If rstDatenblatt.FindNext strKriterium If rstDatenblatt.NoMatch = False Then If bolVonVornGestartet = True Then If rstDatenblatt.AbsolutePosition > lngAbsolutePositionStart Then rstDatenblatt.AbsolutePosition = lngLetzteFundstelle bolGefunden = False Exit Sub End If End If lngLetzteFundstelle = rstDatenblatt.AbsolutePosition bolGefunden = True Exit Sub Else If bolVonVornGestartet = False Then rstDatenblatt.MoveFirst bolVonVornGestartet = True rstDatenblatt.FindNext strKriterium If rstDatenblatt.NoMatch = False Then If rstDatenblatt.AbsolutePosition <= lngAbsolutePositionStart Then lngLetzteFundstelle = rstDatenblatt.AbsolutePosition bolGefunden = True Exit Sub Else rstDatenblatt.AbsolutePosition = lngLetzteFundstelle bolGefunden = False Exit Sub End If End If Else bolGefunden = False Exit Sub End If End If End Sub
Listing 4: Code zum Suchen nach einem Datensatz
Dieser übergeben wir verschiedene Parameter, die zum größten Teil auch als Rückgabeparameter dienen:
- bolGefunden gibt an, ob ein Datensatz gefunden wurde.
- strKriterium nimmt das Kriterium entgegen.
Die beiden folgenden Variablen müssen wir noch im Modulkopf deklarieren, da diese von mehreren Prozeduren genutzt werden:
Private lngAbsolutePositionStart As Long Private varLetzteFundposition As Variant
Beim Aufrufen des Formulars frmSuchenUndErsetzen wird der Wert von lngAbsolutePositionStart auf -1 eingestellt. Deshalb ist die erste Bedingung beim ersten Suchlauf nach dem Öffnen immer wahr und wir stellen nun lngAbsolutePositionStart auf die aktuelle Position des Datensatzzeigers aus rstDatenblatt.AbsolutePosition ein.
Außerdem legen wir hier den Wert der Variablen bolVonVornGestartet auf False fest. Diese Variable gibt an, ob wir den Suchlauf bereits von vorn begonnen haben. Das ist wichtig, wenn wir mittendrin mit der Suche beginnen, damit auch die vor dem Startpunkt liegenden Datensätze noch durchsucht werden können.
Wenn der Benutzer nach dem ersten Suchlauf noch weitere Suchvorgänge startet, hat lngAbsolutePositionStart bereits einen anderen Wert als -1. Also wird der zweite Teil der If…Then-Bedingung angesteuert.
Hier prüfen wir, ob sich der Datensatzzeiger aktuell auf der letzten Fundstelle befindet. Das sollte immer der Fall sein, wenn der Benutzer mehrere Suchvorgänge hintereinander aufruft. Wenn sich der Datensatzzeiger woanders befindet, gehen wir davon aus, dass der Benutzer diesen manuell an eine andere Stelle verschoben hat. In diesem Fall stellen wir lngAbsolutePositionStart wieder auf die aktuelle Position ein und bolVonVornGestartet auf False, da selbst ein Suchvorgang, der bis zum Ende lief und von vorn beginnt, nun zurückgesetzt werden soll.
Damit können wir endlich suchen und nutzen dafür die FindNext-Methode mit dem Kriterium aus strKriterium. Warum FindNext und nicht FindFirst? Weil wir genau von der Stelle mit der Suche beginnen wollen, auf der sich der Datensatzzeiger aktuell befindet.
Die folgende If…Then-Bedingung prüft durch den Vergleich der Eigenschaft NoMatch mit dem Wert False, ob es einen Suchtreffer gab. In diesem Fall prüfen wir, ob bolVonVornGestartet den Wert True hat, also ob wir die Suche bereits wieder ganz oben fortgesetzt haben. Ist das der Fall, kann es sein, dass wir mit der Suche ein zweites Mal auf dem gleichen Datensatz landen. Um das herauszufinden, ermitteln wir, ob die aktuelle Position des Datensatzzeigers aus rstDatenblatt.AbsolutePosition größer ist als die Startposition aus lngAbsolutePositionStart. Ist diese Bedingung wahr, sind wir mehr als einmal durch die Datensätze navigiert. Dann verschieben wir den Datensatzzeiger wieder auf die Position der vorherigen Fundstelle, geben für bolGefunden den Wert False zurück und verlassen die Prozedur.
Sind die letzten beiden Bedingungen nicht erfüllt, haben wir einen Suchtreffer. Wir speichern die Position des Datensatzzeigers für diesen Datensatz in der Variablen lngLetzteFundstelle, geben den Wert True für bolGefunden zurück und verlassen die Prozedur.
Nun kommt der Fall, wo wir von der Startposition aus bereits alle Vorkommen des gesuchten Wertes gefunden haben. In diesem Fall prüfen wir, ob wir die Suche bereits von vorn begonnen haben. Falls nicht, ist es nun an der Zeit. Wir verschieben den Datensatzzeiger mit MoveFirst an die erste Position und stellen bolVonVornGestartet auf True ein. Danach rufen wir erneut FindNext mit unserem Kriterium auf.
Ist NoMatch danach False, haben wir einen erneuten Treffer. Dann prüfen wir, ob wir gegebenenfalls bereits mehr als eine Runde durch die Datensätze gedreht haben.
Dazu prüfen wir, ob der Wert von rstDatenblatt.AbsolutePosition kleiner ist als lngAbsolutePositionStart. Ist das der Fall, sind wir noch im ersten Durchlauf und haben eine Fundstelle.
Wenn nicht, stellen wir AbsolutePosition auf die Position des letzten Suchergebnisses ein, verschieben den Datensatzzeiger also zurück zum letzten regulären Treffer. Außerdem geben wir für bolGefunden den Wert False zurück.
Abwärts-Suche und Aufwärts-Suche
Die einfache Abwärts- und Aufwärts-Suche ist wesentlich einfacher zu programmieren, weil wir nicht prüfen müssen, ob wir am Ende angekommen sind und gegebenenfalls noch Suchergebnisse in dem bisher nicht berücksichtigten Bereich vorhanden sind. Wir gehen also weiter in der Prozedur cmdWeitersuchen_Click.
Hier folgen nun die beiden Case-Zweige Abwärts und Aufwärts. Im Abwärts-Zweig rufen wir die FindNext-Methode auf und übergeben strKriterium. Dies liefert entweder einen Treffer (NoMatch = False) oder nicht.
Abhängig davon stellen wir bolFound auf True oder False ein. Im Aufwärts-Zweig rufen wir FindPrevious auf, um nach oben zu suchen.
In beiden Fällen wird der Datensatzzeiger jeweils auf der Fundstelle platziert.
Danach untersuchen wir in der Prozedur für alle drei Suchfälle den Wert der Variablen bolFound. Hat es den Wert False, stellen wir lngAbsolutePositionStart auf -1 ein, damit wir uns bei der nächsten Suche über alle Daten wieder die korrekte Startposition merken können. Außerdem erscheint eine Meldung, dass kein weiterer Eintrag gefunden werden konnte.
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