IM UNTERNEHMEN EINFACH NAVIGIEREN LOOKUP-DATEN: AUS ZWEI MACH EINS

Größe: px
Ab Seite anzeigen:

Download "IM UNTERNEHMEN EINFACH NAVIGIEREN LOOKUP-DATEN: AUS ZWEI MACH EINS"

Transkript

1 Ausgabe 04/2014 EINFACH NAVIGIEREN Sie sind während der Bearbeitung einer Bestellung schnell zur nächsten gesprungen, weil ein neuer Kunde anruft? Und dann wollen Sie schnell wieder zurück zur vorherigen Bestellung springen? Dafür haben wir mit unserer Internet-Explorer-ähnlichen Navigation (ab S. 13) ge nau die richtige Lösung! In diesem Heft: SQLITE UND Die kompakte SQL-Server-Alternative vieler Android-Apps: Hier erfahren Sie, wie Sie auch von Access aus auf dieses Datenbanksystem zugreifen. LOOKUP-DATEN: AUS ZWEI MACH EINS Wenn die Benutzer etwa zu viele Kategorien eingeben, müssen diese wieder zusammengeführt werden mit dieser Lösung! WORD-DOKUMENTE PER VBA EINLESEN Ordentlich formatierte Word-Dokumente lesen Sie ganz einfach in eine Tabelle ein und nutzen die Inhalte etwa als Access-Richtext. SEITE 46 SEITE 55 SEITE 24 Mat-Nr. H

2 INHALTSVERZEICHNIS/IMPRESSUM TABELLEN UND DATENMODELLIERUNG Neue Werte in Lookup-Feldern 2 Neue Werte in eindeutigen Feldern 7 FORMULARE UND STEUERELEMENTE Vorherige Datensätze anzeigen 13 INTERAKTIV Word-Dokumente per VBA einlesen 24 Von Word zu Access-Richtext/HTML 34 SQL SERVER UND CO. SQLite und Access 46 LÖSUNGEN Lookup-Daten: Aus zwei mach eins 55 QR-Codes mit Access erzeugen, Teil II 64 SERVICE Impressum U2 Editorial 1 DOWNLOAD Die Downloads zu dieser Ausgabe finden Sie wie gewohnt unter: Benutzername: word Kennwort: richtext Die Direktlinks zu den Downloads befinden sich am unteren Rand des jeweiligen Beitrags. Impressum ISBN ISSN: Mat.-Nr Access im Unternehmen Haufe-Lexware GmbH & Co. KG, Munzinger Str. 9, Freiburg Kommanditgesellschaft, Sitz Freiburg Registergericht Freiburg, HRA 4408 Komplementäre: Haufe-Lexware Verwaltungs GmbH, Sitz Freiburg, Registergericht Freiburg, HRB 5557; Martin Laqua Geschäftsführung: Isabel Blank, Markus Dränert, Jörg Frey, Birte Hackenjos, Randolf Jessl, Markus Reithwiesner, Joachim Rotzinger, Dr. Carsten Thies Beiratsvorsitzende: Andrea Haufe Steuernummer: 06392/11008 Umsatzsteuer-Identifikationsnummer: DE Redaktion: Dipl.-Inform. (FH) Michael Forster, (Chefredakteur, V.i.S.d.P.), Dipl.-Ing. André Minhorst (Chefredaktion, extern), Frieda Flechler (Redaktionsassistentin), Rita Klingenstein (sprachl. Lektorat), Sascha Trowitzsch (Fachlektorat) Autoren: André Minhorst, Sascha Trowitzsch Anschrift der Redaktion: Postfach , Freiburg, Tel. 0761/898-0, Fax: 0761/ , computer@haufe.de, Internet: Druck: J.P. Himmer GmbH & Co KG, Postfach , Augsburg Auslieferung und Vertretung für die Schweiz: H.R. Balmer AG, Neugasse 12, CH-6301 Zug, Tel. 041/ , Fax: 041/ Das Update-Heft und alle darin enthaltenen Beiträge und Abbildungen sind urheberrechtlich geschützt. Jede Verwertung, die nicht ausdrücklich vom Urheberrechtsgesetz zugelassen ist, bedarf der vorherigen Zustimmung des Verlags. Das gilt insbesondere für Vervielfältigungen, Bearbeitungen, Übersetzungen, Mikroverfilmung und für die Einspeicherung in elektronische Systeme. Wir weisen darauf hin, dass die verwendeten Bezeichnungen und Markennamen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen. Die im Werk gemachten Angaben erfolgen nach bestem Wissen, jedoch ohne Gewähr. Für mögliche Schäden, die im Zusammenhang mit den Angaben im Werk stehen könnten, wird keine Gewährleistung übernommen. Seite U2

3 EDITORIAL Vor und zurück Internet-Browser sind wohl die Anwendung Nummer Eins bei der täglichen Arbeit mit dem Computer. Ob Sie nun direkt Internet-Anwendungen nutzen, um Ihren Job zu erledigen oder nur bei Ihrer Arbeit nach Lösungen googlen der Browser ist eigentlich immer aktiv. Genießen Sie da nicht auch die Möglichkeit, über den Verlauf ganz schnell per Mausklick zwischen den bereits besuchten Seiten zu wechseln? Und damit sind wir gleich bei dieser Ausgabe: Hier finden Sie nämlich eine entsprechende Lösung für Ihre Access-Datenbank! In normalen Formularen gibt es die Möglichkeit, mit den Navigationsschaltflächen in den Datensätzen zu stöbern. Doch die zuletzt besuchten Datensätze kann man nirgends in der entsprechenden Reihenfolge sehen, geschweige denn wieder aufrufen. Und gerade, wenn der Benutzer beispielsweise Kundendaten oder Bestellungen bearbeitet, gibt es Bedarf für eine solche Funktion: Wenn er etwa gerade die Bestellung des Kunden A bearbeitet und sich dann Kunde B telefonisch wegen einer anderen Bestellung meldet, so wechselt der Benutzer zur Bestellung von Kunde B. Die Bestellung von Kunde A muss er danach wieder suchen. Im Beitrag Vorherige Datensätze anzeigen finden Sie ab S. 13 ein Beispiel dafür, wie Sie ein eigenes Formular mit zwei Schaltflächen zum Blättern zwischen den zuletzt verwendeten Datensätzen ausstatten. Außerdem können Sie damit, ebenso wie beim Internet Explorer, durch Gedrückthalten einer der Schaltflächen die Liste der zuletzt verwendeten Datensätze anzeigen. Für den Umgang mit Lookup-Tabellen finden Sie in dieser Ausgabe gleich zwei Beiträge. Der erste heißt Neue Werte in Lookup-Feldern und zeigt, wie Sie Lookup-Felder direkt über das Kombinationsfeld zur Auswahl dieser Werte erweitern können (ab Seite 2). Der zweite Beitrag kommt in Form einer kleinen Lösung. Wenn Sie dem Benutzer die Eingabe von Werten in Lookup-Felder etwa für die Verwaltung von Kategorien überlassen, wimmelt es darin schnell von Dubletten. Der Beitrag Lookup- Daten: Aus zwei mach eins zeigt ab S. 55, wie Sie solche Daten bereinigen. Dort berücksichtigen wir auch die mit den Lookup-Tabellen verknüpften Daten diese werden beim Zusammenführen gleichbedeutender Lookupwerte gleich mit aktualisiert. Der Beitrag Neue Werte in eindeutigen Feldern zeigt ab S. 7, wie Sie dem Benutzer einer Datenbankanwendung die kryptischen Fehlermeldungen von Access ersparen, wenn dieser einen bereits vorhandenen Wert in ein Feld mit einem eindeutigen Index eingibt. Außerdem kümmern wir uns mal wieder um die übrigen Office-Anwendungen, in diesem Fall um Word. Der Beitrag Word- Dokumente per VBA einlesen zeigt ab S. 24 gleich zwei Dinge: Erstens, wie Sie ein Word-Dokument ordentlich strukturieren und formatieren, indem Sie dieses mit einer Reihe von Absatz- und Zeichenformatvorlagen ausstatten. Ist diese Voraussetzung erfüllt und hat der Benutzer den kompletten Inhalt mit den dafür vorgesehenen Vorlagen ausgestattet, ist es ein Leichtes, den Inhalt des Word-Dokuments von Access aus einzulesen und gleich die passenden Formate für die Absätze zu hinterlegen. Der Beitrag zeigt schließlich, wie Sie den Inhalt eines Word-Dokuments Absatz für Absatz in die Datensätze einer Tabelle importieren und dabei sogar die Zeichenformatierungen innerhalb der Absätze berücksichtigen. Dieses Vorgehen ist die Vorlage für den Beitrag Von Word zu Access-Richtext/ HTML ab Seite 34. Hier verarbeiten wir die in die Tabelle eingelesenen Word- Dokumente nämlich gleich weiter, indem wir ein HTML-Dokument erstellen, das den Vorgaben des Richtext-Formats von Access entspricht. In einem weiteren Beitrag stellen wir SQLite vor, eine einfache SQL-Server- Variante, und zeigen, wie Sie von Access aus darauf zugreifen siehe SQLite und Access ab S. 46. Und schließlich folgt der zweite Teil der Beitragsreihe zum Thema QR-Codes: QR-Codes mit Access erzeugen, Teil II, zu finden ab S. 64. Viel Spaß mit der neuen Ausgabe! Ihr Michael Forster Seite 1

4 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN LOOKUP-FELDERN Neue Werte in Lookup-Feldern Manchmal möchten Sie schnell mit ein paar Zeilen Code eine Funktion programmieren, mit der ein neuer Datensatz zu einer Tabelle mit einem eindeutigen Index hinzugefügt wird. Dies geschieht bevorzugt bei Lookup-Tabellen. Dort kommt es aber schnell dazu, dass der Benutzer einen neuen Datensatz hinzufügt, der bereits vorhanden ist. In diesem Beitrag zeigen wir, wie Sie solche Fälle umschiffen oder den Benutzer gegebenenfalls einen neuen Wert eingeben lassen. Lookup-Tabellen Warum benutzt man eigentlich Lookup- Tabellen? Der wichtigste Grund ist wohl, dass man die möglichen Werte nur je einmal in eine separate Tabelle eingibt und von anderen Tabellen darauf verweisen kann, indem man diese über ein Fremdschlüsselfeld mit dem Primärschlüsselwert dieser Tabelle verknüpft. Nun legt man nicht immer nur über die Benutzeroberfläche neue Datensätze an, bei denen man bequem etwa per Kombinationsfeld einen der vorhandenen verknüpften Datensätze auswählt zum Beispiel eine Anrede oder eine Kategorie. Gelegentlich sollen auch neue Datensätze beim Anlegen über die Benutzeroberfläche hinzugefügt werden oder Sie wollen vielleicht Daten aus einer anderen Datenbank importieren, wo die Daten noch in einer Tabelle vorliegen. Eindeutiger Schlüssel Wenn Sie eine solche Lookup-Tabelle verwenden wie etwa die Tabelle tblkategorien aus Bild 1, dann haben Sie verschiedene Möglichkeiten, um sicherzustellen, dass jede Kategorie nur einmal eingegeben wird. Die einfachste Bild 1: Tabelle mit eindeutig indiziertem Feld ist, einfach wie in der Abbildung einen eindeutigen Index für dieses Feld anzulegen. Dazu wählen Sie für die Eigenschaft Indiziert einfach den Wert Ja (Ohne Duplikate) aus. Achtung: Dies ist für Tabellen mit bestehenden Daten etwas komplizierter. Es kann immerhin vorkommen, dass Benutzer es geschafft haben, den gleichen Datensatz mehrfach in einer Lookup-Tabelle einzugeben. Wenn dort also beispielsweise zwei Mal die gleiche Kategorie enthalten ist und Sie dann versuchen, für das Feld Kategorie einen eindeutigen Index zu definieren, erhalten Sie eine entsprechende Fehlermeldung (s. Bild 2). In diesem Fall müssen Sie die Daten zuvor noch vereinheitlichen. Wie das gelingt, lesen Sie im Beitrag Lookup- Daten: Aus zwei mach eins (www. access-im-unternehmen.de/946). Wenn die Daten jedoch keine Redundanzen aufweisen, können Sie den eindeutigen Index problemlos anlegen. Anschlie- Bild 2: Fehlermeldung beim Versuch, einen eindeutigen Index für ein Feld mit nicht eindeutigen Daten anzulegen Seite 2

5 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN LOOKUP-FELDERN ßend führt der Versuch, einen neuen Datensatz mit einem bereits vorhandenem Wert in dem eindeutig indizierten Feld anzulegen, wiederum zu einem Fehler. Interessanterweise erhalten wir hier genau die gleiche Fehlermeldung wie bei dem Versuch, einen eindeutigen Index für ein Feld mit nicht eindeutigen Werten anzulegen. Nun wollen wir dem Benutzer sicher nicht diese Fehlermeldung präsentieren, sondern eine, mit der er etwas mehr anfangen kann. Lookup-Werte per Formular eingeben In Bild 3 haben wir die einfachste Konstellation für die Eingabe von Werten in eine Lookup-Tabelle geschaffen. Der Benutzer kann aus dem Kombinationsfeld eine Kategorie auswählen, aber auch neue Werte eingeben zumindest scheinbar. Denn wenn er wirklich einen neuen Wert dort eingibt, der noch nicht in der Datensatzherkunft dieses Steuerelements vorhanden ist, erscheint wiederum eine Fehlermeldung (s. Bild 4). Bild 3: Einfaches Formular mit einem Kombinationsfeld zur Eingabe oder Auswahl von Lookup-Werten nicht in Liste des Kombinationsfeldes ausgelöst wird. Diese sieht wie in Listing 1 aus. Diese Ereignisprozedur liefert gleich zwei entsprechende Parameter, wobei der erste, NewData, den Wert des neuen Eintrags liefert und der zweite, Response, einen Wert erwartet, der die weitere Vorgehensweise festlegt. In diesem Fall legt die Prozedur gleich einen neuen Eintrag in der Tabelle tblkategorien mit dem mit NewData gelieferten Kategorienamen an. Dann stellt sie Response auf acdataerradded ein, was für den weiteren Verlauf festlegt, dass Access keine Fehlermeldung liefert, sondern direkt den neuen Wert im Kombinationsfeld auswählt. Die Datensatzherkunft wird auf diesem Wege gleich mit aktualisiert. Hinzufügen mit Rückfrage Gegebenenfalls möchten Sie den Benutzer dennoch darauf hinweisen, dass er gerade einen neuen Wert zur Liste hinzufügt. Zu Beispielzwecken kopieren wir das Kombinationsfeld cbokategorien in ein neues Steuerelement namens cbokategorien_ii. Für dieses hinterlegen wir nun wiederum eine Ereignisprozedur für das Ereignis Bei Nicht in Liste. Diese sieht nun wie in Listing 2 aus. Die Prozedur zeigt ein Meldungsfenster an, mit dem es den Benutzer fragt, ob der Eintrag wirklich zur Liste hinzu- Um dies zu verhindern und den neuen Wert gleich in die Tabelle tblkategorien zu schreiben, legen Sie eine Ereignisprozedur an, die durch das Ereignis Bei Bild 4: Fehler beim Versuch, einen Wert in ein unvorbereitetes Lookup-Feld einzugeben Private Sub KategorieID_NotInList(NewData As String, Response As Integer) Dim db As DAO.Database Set db = CurrentDb db.execute "INSERT INTO tblkategorien(kategorie) VALUES('" & NewData & "')", dbfailonerror Response = acdataerradded Set db = Nothing Listing 1: Hinzufügen eines neuen Datensatzes über die Eingabe in ein Kombinationsfeld Seite 3

6 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN LOOKUP-FELDERN Private Sub cbokategorien_ii_notinlist(newdata As String, Response As Integer) Dim db As DAO.Database Dim bolhinzufuegen As Boolean bolhinzufuegen = MsgBox("Diesen Eintrag zur Liste hinzufügen?", vbyesno + vbexclamation, "Neuer Eintrag") = vbyes If bolhinzufuegen = True Then Else Set db = CurrentDb db.execute "INSERT INTO tblkategorien(kategorie) VALUES('" & NewData & "')", dbfailonerror Response = acdataerradded Set db = Nothing Response = acdataerrcontinue Listing 2: Hinzufügen eines neuen Datensatzes mit Rückfrage gefügt werden soll. Das Ergebnis dieses Meldungsfensters (vbyes oder vbno) vergleicht die Prozedur dann mit dem Wert vbyes. Dieses Resultat wiederum landet in der Variablen bolhinzufuegen. An dieser Stelle ist es wichtig, das Ergebnis des Meldungsfensters nicht direkt in der Variablen bolhinzufuegen zu speichern: vbyes hat nämlich den Wert 6, vbno hat den Wert 7. Wenn Sie einer Boolean-Variablen den Wert 6 oder 7 (oder eine andere positive Zahl) zuweisen, erhält diese den Wert True. Das bedeutet: Egal, ob der Benutzer auf Ja oder Nein klickt, die Variable bolhinzufuegen würde immer mit dem Wert True gefüllt werden. Bild 5: Rückfrage vor dem Hinzufügen eines neuen Wertes Bild 6: Soll der Wert nicht zur Liste hinzugefügt werden, bleibt dieser im Feld stehen und das Kombinationsfeld wird aufgeklappt. Also schreiben wir ordnungsgemäß das Ergebnis aus dem Vergleich der MsgBox-Funktion mit dem Wert vbyes in die Variable bolhinzufuegen. Beim Eintragen eines noch nicht vorhandenen Wertes in das Kombinationsfeld erscheint nun die Meldung aus Bild 5. Klickt der Benutzer auf Ja, werden die Anweisungen ausgeführt, die wir bereits aus dem vorherigen Beispiel kennen. Anderenfalls gibt die Prozedur über den Parameter Response den Wert acdataerrcontinue zurück. Das heißt: Access soll keine Fehlermeldung ausgeben. Stattdessen wird einfach nur das aufgeklappte Kombinationsfeld angezeigt (s. Bild 6). Zusätzlich könnten Sie noch das Kombinationsfeld leeren etwa mit folgender Anweisung, die Sie ebenfalls in den Else-Zweig der If...Then-Bedingung schreiben: Me!cboKategorien_II = Null Neuer Lookup-Wert per InputBox Nun gibt es allerdings Benutzer, die nicht ständig mit Access arbeiten und auch von anderen Anwendungen möglicherweise nicht die Möglichkeit kennen, neue Werte direkt in ein Auswahlfeld einzugeben. Formulare auf Internetangeboten bieten dies beispielsweise gar nicht an dort gibt es lediglich reine Auswahlsteuerelemente. Seite 4

7 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN LOOKUP-FELDERN Private Sub cmdhinzufuegen_click() Dim lngkategorieid As Long Dim strinhalt As String Dim strdefault As String strdefault = "[Neue Kategorie eingeben]" strinhalt = InputBox("Geben Sie die Bezeichnung der neuen Kategorie ein:", "Neue Kategorie", strdefault) If Not (Len(strInhalt) = 0 Or strinhalt = strdefault) Then lngkategorieid = LookupIDErmitteln("tblKategorien", "KategorieID", "Kategorie", strinhalt) If Not lngkategorieid = 0 Then Me!cboKategorien_III = lngkategorieid Listing 3: Neuen Datensatz per Schaltfläche hinzufügen Um die Dateneingabe etwas intuitiver zu gestalten, fügen wir neben dem Kombinationsfeld also noch ein Steuerelement mit der Beschriftung Hinzufügen hinzu (je nach Platzangebot lässt sich dies auch auf ein Plus-Zeichen (+) verkürzen oder, wenn Sie eine aktuelle Version von Access verwenden, durch ein entsprechendes Icon). Die Schaltfläche erhält den Namen cmdhinzufuegen und soll beim Anklicken eine Inputbox anzeigen, die den Benutzer zur Eingabe eines neuen Wertes für die Lookup-Tabelle auffordert. Für die Schaltfläche cmdhinzufuegen hinterlegen Sie die Ereignisprozedur aus Listing 3, die durch das Ereignis Beim Klicken ausgelöst wird. Die Prozedur ermittelt zunächst mit einer Inputbox den neuen Eintrag für das Kombinationsfeld (s. Bild 7) und speichert diesen in der Variablen strinhalt. Wenn die Variable eine leere Zeichenkette enthält, spricht dies dafür, dass der Benutzer entweder tatsächlich nichts eingegeben oder auf die Abbrechen-Schaltfläche geklickt Bild 7: Eingabe des neuen Wertes per Inputbox hat. In diesem Fall soll nichts geschehen genauso wie in dem Fall, dass der Benutzer einfach auf OK geklickt hat und somit der in der InputBox-Anweisung angegebene Default-Wert (hier [Neue Kategorie eingeben]) in die Variable strinhalt übernommen wurde. Die Prozedur ruft nun eine eigene Funktion auf, die sich um das Hinzufügen des neuen Datensatzes kümmert und um noch mehr! Denn immerhin geraten wir mit der Inputbox außerhalb der Kontrolle des Kombinationsfeldes. Dieses hat ja, wie in den vorherigen Beispielen zu sehen, sehr schön aufgepasst, dass wir keine bereits vorhandenen Werte in die Lookup-Tabelle eintragen. Diese Funktion heißt LookupIDErmitteln und wird gleich im Anschluss beschrieben. Sie erwartet einige für die Verarbeitung nötige Parameter und liefert den Primärschlüsselwert für den neuen oder gegebenenfalls einen bereits vorhandenen Datensatz mit dem gleichen Wert im indizierten Feld zurück. Diesen verarbeitet die Prozedur cmdhinzufügen_click derart, dass das Kombinationsfeld nach der Aktualisierung seiner Datensatzherkunft direkt auf den neuen Wert eingestellt wird. Lookup-Wert per Code einfügen Die soeben erwähnte Funktion Lookup- ID Ermitteln sieht wie in Listing 4 aus. Sie erwartet die folgenden Parameter: strtabelle: Name der Tabelle, zu welcher der Wert hinzugefügt werden soll Seite 5

8 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN LOOKUP-FELDERN Public Function LookupIDErmitteln(strTabelle As String, strprimaerschluesselfeld As String, strinhaltsfeld As String, _ strinhalt As String) As Long Dim lngtempid As Long Dim db As DAO.Database lngtempid = Nz(DLookup(strPrimaerschluesselfeld, strtabelle, strinhaltsfeld & " = '" & strinhalt & "'"), 0) If lngtempid = 0 Then Set db = CurrentDb db.execute "INSERT INTO " & strtabelle & "(" & strinhaltsfeld & ") VALUES('" & strinhalt & "')", dbfailonerror lngtempid = LookupIDErmitteln = lngtempid End Function Listing 4: Primärschlüsselwert eines neuen oder gegebenenfalls vorhandenen Wertes zurückliefern strprimaerschluesselfeld: Name des Primärschlüsselfeldes dieser Tabelle strinhaltsfeld: Feld, das den neuen Wert aufnehmen soll strinhalt: Neuer Wert für dieses Feld Die Prozedur versucht zunächst, mit einer DLookup-Funktion den Primärschlüsselwert für einen Datensatz der angegebenen Tabelle zu finden, der den hinzuzufügenden Wert in dem betroffenen Feld aufweist. Wenn man die Parameter durch die übergebenen Werte ersetzt, würde die DLookup-Anweisung wie folgt aussehen: DLookup("KategorieID", µ "tblkategorien", "Kategorie = µ '<Neue Kategorie>'") Für den Fall, dass der Eintrag noch nicht vorhanden ist, fassen wir die DLookup- Funktion noch in die Nz-Funktion ein diese liefert in dem Fall, dass DLookup den Wert Null zurückliefert, den Wert 0 zurück. Enthält lngtempid nun den Wert 0, was darauf hindeutet, dass der neu eingegebene Wert tatsächlich noch nicht in der Zieltabelle enthalten ist, legt die Funktion den Datensatz neu an. Dazu ruft sie per Execute-Methode des Database- Objekts für die aktuelle Datenbank eine entsprechende INSERT INTO-Anweisung auf, die etwa so aussehen könnte: INSERT INTO tblkategorien(kategorie) VALUES('<Neue Kategorie')" Den Primärschlüsselwert für den neuen Datensatz ermittelt die Funktion dann mithilfe der Abfrage IDENTITY. Diese liefert jeweils den Wert des zuletzt in dieser Session angelegten Autowertes. Dieser Wert wird dann auch als Funktionswert zurückgeliefert. War der Wert von lngtempid jedoch bereits nach dem Aufruf der DLookup- Funktion ungleich 0, dann werden die Anweisungen innerhalb der If...Then- Bedingung nicht ausgeführt und die Prozedur gibt gleich den Primärschlüsselwert des per DLookup ermittelten Datensatzes zurück. Seite 6

9 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN Neue Werte in eindeutigen Feldern Manche Felder sollen zusätzlich zu den Primärschlüsselfeldern der eindeutigen Indizierung eines Datensatzes dienen. Der Zweck ist dann nicht hauptsächlich auf Seiten des Datenmodells zu finden, sondern eher bei der Anzeige: Wenn Sie etwa mehrere gleiche Artikel haben, die sie aber in unterschiedlichen Verpackungseinheiten in der Artikeltabelle pflegen, können Sie den Artikel nicht nur nach dem Artikelnamen identifizieren. Hier hilft ein weiteres Bezeichnungsfeld, das den Artikelnamen um eindeutige Informationen erweitert. Dieser Beitrag zeigt, wie Sie die Zuweisung eindeutiger Werte für ein solches Feld sicherstellen. Über welche Konstellation Sie nun dazu gelangen, ein Feld mit eindeutigen Werten belegen zu müssen, spielt eigentlich keine Rolle Tatsache ist, dass Sie dem Benutzer eine ergonomische Möglichkeit bieten sollten, diese Werte einzugeben. In unserem Beispiel wollen wir in der Tabelle tblartikel eine eindeutige Artikelbezeichnung durchsetzen, weshalb wir das Feld Artikelbezeichnung der Tabelle, die Sie im Entwurf in Bild 1 sehen, mit einem eindeutigen Index versehen haben. Um die Eingabe durch einen Benutzer zu ermöglichen, haben wir ein denkbar einfaches Formular namens frmartikel erstellt, das an die Tabelle tblartikel gebunden ist und lediglich die beiden darin enthaltenen Felder anzeigt (s. Bild 2). Bild 1: Tabelle mit eindeutig indiziertem Feld Wenn der Benutzer nun einen neuen Datensatz anlegt und eine Artikelbezeichnung einträgt, die bereits einmal in der Tabelle vorhanden ist, liefert dies die folgende Fehlermeldung zutage: Die von Ihnen gewünschten Änderungen an der Tabelle konnten nicht vorgenommen werden, da der Index, der Primärschlüssel oder die Beziehung mehrfach vorkommende Werte enthalten würde. Ändern Sie die Daten in den Feldern, die gleiche Daten enthalten, entfernen Sie den Index, oder Bild 2: Formular zur Eingabe von Artikeln definieren Sie den Index neu, damit doppelte Einträge möglich sind, und versuchen Sie es erneut. Diese Meldung wollen wir dem Benutzer natürlich so nicht präsentieren. Abhilfe schaffen wir nicht mit einer herkömmlichen Fehlerbehandlung, denn dieser Fehler wird ja gar nicht in einer VBA- Prozedur ausgelöst, sondern direkt bei der Eingabe in ein gebundenes Steuerelement eines Formulars. Also schauen wir uns zunächst an, wie Sie dieses Problem nachträglich beheben also wenn der Benutzer bereits alle Daten eingegeben hat und den Datensatz speichern will. Hier können Seite 7

10 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN Private Sub Form_Error(DataErr As Integer, Response As Integer) Select Case DataErr Case 3022 MsgBox "Die Artikelbezeichnung '" & Me!Artikelbezeichnung & "' ist bereits vergeben.", vbexclamation + vbokonly, _ "Doppelte Artikelbezeichnung" Me!Artikelbezeichnung.SetFocus Response = acdataerrcontinue End Select Listing 1: Benutzerdefinierte Fehlermeldung bei Eingabe eines bereits vorhandenen Wertes in ein eindeutiges Feld Sie auf den Fehler über die Ereigniseigenschaft Bei Fehler des Formulars reagieren. Für dieses legen Sie eine Ereignisprozedur wie in Listing 1 an. Diese liefert zwei Parameter: DataErr übergibt die Nummer des Fehlers, der zum Auslösen dieses Ereignisses geführt hat. Response erwartet eine Access-Konstante, die Access mitteilt, wie mit dem Fehler verfahren werden soll. In diesem Fall prüfen wir in einer Select Case-Anweisung, ob DataErr den Fehler mit der Nummer 3022 liefert (dies entspricht dem obigen Fehlertext, wie Sie auch im Direktbereich mit der Anweisung Debug.Print AccessError(3022) prüfen können). In diesem Fall geben Sie eine entsprechende Meldung aus und stellen den Fokus auf das Textfeld zur Eingabe des Artikelnamens ein. Das Ergebnis Bild 3: Benutzerdefinierte Fehlermeldung sieht dann etwa wie in Bild 3 aus, das Beispiel finden Sie im Formular frmartikel_pruefungbeimspeichern. Prüfung bei der Eingabe Nun kann es sein, dass die Datenherkunft des Formulars mehrere eindeutig indizierte Felder enthält. In diesem Fall fällt die Prüfung, welches Feld nun für den Fehler verantwortlich ist, entsprechend schwerer. Doch zum Glück können wir auch direkt nach der Eingabe eines Textes in ein Feld prüfen, ob der Wert bereits in der Tabelle vorhanden ist, und den Benutzer gegebenenfalls darauf hinweisen. Dazu verwenden wir die Ereignisprozedur, die durch das Ereignis Vor Aktualisierung des Textfeldes txtartikelbe- Private Sub txtartikelbezeichnung_beforeupdate(cancel As Integer) Dim lngartikelid As Long lngartikelid = Nz(DLookup("ArtikelID", "tblartikel", "Artikelbezeichnung = '" & Me!txtArtikelbezeichnung & "'"), 0) If Not lngartikelid = 0 Then MsgBox "Die Artikelbezeichnung ist bereits in der Datenbank enthalten.", vbexclamation + vbokonly, _ "Doppelte Artikelbezeichnung" Cancel = True Listing 2: Prüfung des Artikelnamens vor dem Speichern des Feldes Seite 8

11 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN zeichnung ausgelöst wird (s. Listing 2). Die Prozedur prüft per DLookup-Anweisung, ob die Tabelle tblartikel bereits einen Datensatz enthält, der im Feld Artikelbezeichnung den soeben durch den Benutzer eingegebenen Wert aufweist. Ist dies der Fall, liefert die DLookup- Funktion den Primärschlüsselwert dieses Datensatzes zurück und speichert diesen in der Variablen lngartikelid, anderenfalls den Wert Null. Dieser wird durch die umschließende Nz-Funktion in den Wert 0 umgewandelt. Die folgende If...Then-Bedingung prüft lngartikelid. Enthält diese nicht den Wert 0, ist die Artikelbezeichnung bereits vorhanden. In diesem Fall erscheint wieder eine entsprechende Meldung. Wichtig ist an dieser Stelle, dass wir den Parameter Cancel auf den Wert True einstellen, damit das Speichern der Eingabe in das Textfeld abgebrochen wird und der Fokus auf dem Textfeld verbleibt. Zusätzlich können Sie noch das Textfeld leeren, und zwar mit dieser Anweisung: Me!txtArtikelbezeichnung.Undo Dummerweise erschlagen wir damit nicht alle denkbaren Fälle: Es kann ja auch geschehen, dass der Benutzer den Wert im Feld Artikelbezeichnung eines bestehenden Datensatzes erst einmal ändert, das Feld verlässt und dann wieder den ursprünglichen Wert für das Feld einträgt. In diesem Falle wird deutlich, dass der Datensatz bei der Eingabe in ein Formular zwar temporär gespeichert wird, aber erst etwa beim Datensatzwechsel in die Tabelle übernommen wird: Unsere für dieses Formular definierte Ereignisprozedur wird in diesem Fall nämlich ausgelöst, obwohl wir nur die Artikelbezeichnung erneut in den gleichen Datensatz eingetragen haben. Sprich: Ein Datensatz enthält den Wert Beispielartikel im Feld Artikelbezeichnung. Wir ändern diesen auf Testartikel und verlassen das Feld kein Problem, sofern Testartikel noch nicht in einem anderen Datensatz gespeichert ist. Dann kehren wir zurück zum Feld und geben wieder Beispielartikel ein. Die Prozedur txtartikelbezeichnung_beforeupdate wird in beiden Fällen ausgelöst. Im zweiten Fall findet sie den Wert des Feldes Artikelname jedoch in der Tabelle vor und zeigt die entsprechende Meldung an. Also erweitern wir den Aufruf der DLookup-Funktion so, dass diese den aktuellen Datensatz ausdrücklich nicht mit in die Untersuchung einbezieht. Dazu fügen wir ein weiteres Kriterium hinzu, das den Datensatz mit dem Primärschlüsselwert des aktuell im Formular angezeigten Datensatzes ausschließt: lngartikelid = Nz(DLookup("ArtikelID", "tblartikel", "Artikelbezeichnung = '" & Me!txtArtikelbezeichnung & "' AND NOT ArtikelID = " & Me!ArtikelID), 0) Nun können Sie auch bei bestehenden Datensätzen den Artikelnamen ändern und wieder zurücksetzen, ohne dass ein für den Benutzer nicht erklärbarer Fehler angezeigt wird. Das Beispiel zu diesem Abschnitt finden Sie im Formular frmartikel_pruefung- NachEingabe. Prüfung vor der Eingabe Manchmal möchten Sie den Wert für ein eindeutig indiziertes Feld bereits vor der Eingabe prüfen. Wenn Sie dann auch noch die gängigen Mechanismen bei der Eingabe in gebundene Felder umgehen möchten, werden Sie möglichweise auf die InputBox-Funktion zurückgreifen. Bild 4: Eingabe der neuen Bezeichnung per InputBox In diesem Beispiel haben wir das Textfeld zur Eingabe der Artikelbezeichnung einmal gesperrt, damit der Benutzer die Eingabe nur über die Schaltfläche rechts vom Textfeld aktivieren kann s. Bild 4. Seite 9

12 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN Private Sub cmdneu_click() Dim bolvorhanden As String Dim strartikelbezeichnung As String Dim strstandardwert As String strstandardwert = "[Artikelbezeichnung]" strartikelbezeichnung = InputBox("Geben Sie die neue Artikelbezeichnung ein.", "Neuer Artikel", strstandardwert) bolvorhanden = BezeichnungErmitteln(strArtikelbezeichnung, "tblartikel", "ArtikelID", "Artikelbezeichnung", True) If bolvorhanden = True Then Me!txtArtikelbezeichnung = strartikelbezeichnung Listing 3: Abfrage einer Bezeichnung per InputBox Ein Klick auf die Schaltfläche löst die Prozedur aus Listing 3 aus. Diese speichert zunächst einen Standardwert, der beim Anzeigen der Inputbox vorgegeben werden soll, in der Variablen strstandardwert. Die folgende Anweisung zeigt bereits die Inputbox an. Das Ergebnis soll an die Variable bolvorhanden zurückgegeben werden. Die InputBox-Funktion verhält sich nun so, dass immer der angegebene Text zurückgeliefert wird außer, wenn der Benutzer die Abbrechen- Schaltfläche betätigt. In diesem Fall liefert die Funktion eine leere Zeichenkette zurück. Nun wollen wir die Aufgabe, auf eine leere Zeichenkette und auf eine eventuell bereits vorhandene Zeichenkette zu prüfen, an eine mit Parametern ausgestattete und somit wiederverwendbare Funktion auslagern. Was genau soll diese Funktion erledigen? Sie soll mit einem Parameter namens strbezeichnung einen Wert entgegennehmen, den der Benutzer zuvor eingegeben hat, und prüfen, ob dieser bereits in dem eindeutigen Feld in der Tabelle vorhanden ist. In diesem Fall soll die Funktion eine weitere Inputbox anzeigen mit dem Hinweis, dass die eingegebene Bezeichnung bereits vorhanden ist. Die Bezeichnung soll dabei als Standardwert vorgegeben werden, damit der Benutzer sieht, um welchen Ausdruck es geht. Wenn der Benutzer nun eine noch nicht verwendete Bezeichnung eingibt, soll die Funktion den Wert True zurückliefern. Die neue Bezeichnung soll mit dem gleichen Parameter zurückgegeben werden, über den die vorhandene Bezeichnung bereits an die Funktion übergeben wurde (strbezeichnung soll also als ByRef und nicht ByVal deklariert werden). Wenn der Benutzer hier auf Abbrechen klickt, soll die Funktion den Wert False zurückliefern. Wenn der Benutzer eine leere Zeichenkette eingibt und auf OK klickt, soll dies als Abbruch gewertet werden also mit dem Wert False. Die Funktion heißt BezeichnungErmitteln und sieht wie in Listing 4 aus. Diese Prozedur erwartet die folgenden Parameter: strbezeichnung: Erwartet den aktuellen Wert, den der Benutzer gegebenenfalls zuvor ebenfalls per InputBox-Funktion eingegeben hat oder der auf andere Weise vorgegeben wurde. strtabelle: Name der Tabelle, in welche die Bezeichung gespeichert werden soll strprimaerschluessel: Name des Primärschlüsselfeldes dieser Tabelle strbezeichnungsfeld: Name des Feldes, das nur eindeutige Werte aufnehmen soll strmeldung: Optionaler Parameter. Gibt an, welche Meldung in einer InputBox angezeigt werden soll, wenn die Bezeichnung erneut eingegeben werden muss. strtitel: Wie strmeldung, gibt aber den Titel des InputBox-Fensters an. Ebenfalls ein optionaler Parameter. Die Prozedur prüft zunächst, ob der Benutzer gegebenenfalls eine leere Bezeichnung eingegeben hat, was als Betätigen der Abbrechen-Schaltfläche gewertet werden soll. Die Funktion Seite 10

13 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN Public Function BezeichnungErmitteln(strBezeichnung As String, strtabelle As String, strprimaerschluesselfeld As String, _ strbezeichnungsfeld As String, Optional strmeldung As String, Optional strtitel As String) As Boolean Dim db As DAO.Database Dim lngid As Long Dim bolabbrechen As Boolean Set db = CurrentDb If Len(strBezeichnung) = 0 Then BezeichnungErmitteln = False Exit Function lngid = Nz(DLookup(strPrimaerschluesselfeld, strtabelle, strbezeichnungsfeld & "='" & strbezeichnung & "'")) If lngid = 0 Then BezeichnungErmitteln = True Else If Len(strMeldung) = 0 Then strmeldung = "Die Bezeichnung ist bereits vorhanden. Geben Sie eine andere Bezeichnung ein." If Len(strTitel) = 0 Then strtitel = "Doppelte Bezeichnung" Do While Not lngid = 0 And bolabbrechen = False strbezeichnung = InputBox(strMeldung, _ strtitel, strbezeichnung) If Not Len(strBezeichnung) = 0 Then lngid = Nz(DLookup(strPrimaerschluesselfeld, strtabelle, strbezeichnungsfeld & "='" & strbezeichnung & "'")) Else bolabbrechen = True Loop If lngid = 0 And bolabbrechen = False Then BezeichnungErmitteln = True End Function Listing 4: Ermitteln einer eindeutigen Bezeichnung für einen Datensatz liefert in diesem Fall den Wert False zurück und wird an dieser Stelle beendet. Anderenfalls versucht die Prozedur, den Primärschüsselwert für einen Datensatz mit dem angegebenen Wert in dem angegebenen Feld zu ermitteln. Ist kein solcher Datensatz vorhanden, liefert die hier verwendete DLookup-Abfrage den Wert Null zurück, der mit der Nz- Funktion durch den Wert 0 ersetzt wird. Der gefundene Primärschlüsselwert oder der Wert 0 landen in der Variablen lngid. Dieser wird dann mit dem Wert 0 verglichen. Liefert die entsprechende If...Then- Bedingung den Wert True, stellt die Prozedur den Rückgabewert der Funktion auf True ein die vom Benutzer eingegebene Bezeichnung ist noch nicht vorhanden und kann verwendet werden. Anderenfalls prüft die Funktion nun, ob beim Aufruf ein benutzerdefinierter Titel und ein entsprechender Text für die InputBox-Funktion übergeben wurden. Falls nicht, trägt die Funktion entsprechende vorgefertigte Ausdrücke in die beiden Variablen strmeldung und strtitel ein. Dann beginnt die Prozedur mit dem Durchlaufen einer Do While-Schleife, Seite 11

14 TABELLEN UND DATENMODELLIERUNG NEUE WERTE IN EINDEUTIGEN FELDERN die solange läuft, bis der in lngid gespeicherte Wert nicht mehr 0 ist und die Variable bolabbrechen nicht True ist. Ersteres bedeutet, dass der Benutzer einen Wert eingegeben hat, der noch nicht im betroffenen Feld enthalten ist, zweites bedeutet, dass der Benutzer entweder eine leere Zeichenkette eingegeben und auf OK geklickt hat oder dass er direkt die Abbrechen-Schaltfläche betätigt hat. Innerhalb der Schleife fragt die Prozedur nun erneut durch den Aufruf einer InputBox-Funktion die Bezeichnung für den neuen Datensatz ab. Als Parameter übergibt sie den in strmeldung gespeicherten Meldungstext, die in strtitel gespeicherte Überschrift und den aktuellen Wert aus der Variablen strbezeichnung. Das Ergebnis landet wiederum in der Variablen strbezeichnung. Die Funktion prüft dann mit der Len- Funktion, ob die Länge des Ausdrucks in strbezeichnung ungleich 0 ist (also größer als 0, da Len ja keine negativen Werte liefern kann). Ist dies der Fall, ermittelt die Funktion mithilfe des Aufrufs der DLookup-Funktion, ob die nun eingegebene Bezeichnung bereits in dem mit strbezeichnungsfeld angegebenen Feld der mit strtabelle angegebenen Tabelle enthalten ist. Das Ergebnis, also entweder der Primärschlüsselwert des betroffenen Datensatzes oder die Zahl 0, landen in der Variablen lngid. Sollte die Länge der Zeichenkette 0 sein, stellt die Funktion die Variable bolabbrechen direkt auf den Wert True ein. Dies prüft die Funktion dann beim nächsten Start der Do While-Schleife. Diese wird erst abgebrochen, wenn die Variable lngid den Wert 0 enthält, also die angegebene Bezeichnung noch nicht in dem eindeutigen Feld der Tabelle enthalten ist oder wenn die Variable bolabbrechen den Wert True aufweist. Hat der Benutzer also entweder einen noch nicht vorhandenen Wert für das eindeutige Feld eingegeben oder bei leerem Eingabefeld auf OK geklickt beziehungsweise direkt auf Abbrechen, dann verlässt die Funktion die Schleife. Im positiven Fall legt die Funktion als Rückgabewert noch den Wert True fest. Diese Funktion können Sie an Stellen, an denen Sie ohne Einsatz eines gebundenen Feldes einen neuen Wert ermitteln wollen, der noch nicht in einem bestimmten Feld einer vorgegebenen Tabelle enthalten ist, einsetzen. Ein Beispiel zur Anwendung dieser Funktion finden Sie im Formular frmartikel_pruefungbeidereingabe der Beispieldatenbank, die Funktion im Standardmodul mdlneuewerteineindeutigenfeldern. Zusammenfassung und Ausblick Für den Benutzer Ihrer Anwendung ist es unbefriedigend, wenn er Daten eingibt und für den Laien schwer verständliche Fehlermeldungen erhält, wenn er beispielsweise doppelte Werte in Tabellenfelder mit eindeutigem Index eingibt. In diesem Beitrag finden Sie einige Beispiele, wie Sie solche Probleme umgehen können und dem Anwender aussagekräftige Hinweise liefern statt der Standardfehlermeldungen von Access. Seite 12

15 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Vorherige Datensätze anzeigen In Detailformularen zeigen Sie Datensätze an, bearbeiten oder betrachten diese und schließen dann das Formular oder wechseln zum nächsten Datensatz. In vielen Fällen möchten Sie aber dann vielleicht noch einmal zu einem der zuvor bearbeiteten Datensätze und gegebenenfalls wieder zurück. Solch eine Funktion bietet Access nicht, dort können Sie über die Navigationsschaltflächen nur durch die Datensätze in der Reihenfolge der Datenherkunft springen. Wir statten in diesem Beitrag ein Formular mit einer Historie aus, wie Sie sie auch vom Internet-Browser kennen. Im Internet-Browser starten Sie beispielsweise mit einer Google-Suche, finden dann in der Ergebnisliste eine Seite und rufen diese dann auf. Dort fällt Ihnen ein weiterer Links ins Auge, den Sie sich anschauen. Sie entscheiden sich, dass Sie doch wieder zum vorherigen Link zurückwechseln möchten. Bringt dieser nicht das gewünschte Ergebnis, soll es wieder zurück zum Suchergebnis gehen, von wo Sie entweder den nächsten Treffer betrachten oder vielleicht sogar die Seite mit den folgenden Treffern aufrufen. Wie auch immer dies läuft: Die Vor- und Zurück-Tasten des Browsers nehmen Ihnen hier eine Menge Arbeit ab. Dabei bieten diese die folgenden Möglichkeiten: Sie klicken direkt auf die Vor- oder Zurück-Taste, um zur folgenden oder vorherigen Seite zu springen. Bild 1: Historie im Internet-Explorer Wenn Sie dort auf den Eintrag Verlauf klicken, können Sie sogar die zuletzt besuchten Seiten inklusive Datum aufrufen (s. Bild 2). Das sind interessante Aussichten vor allem, wenn man sich vorstellt, dies etwa auf ein Kunden-Formular zu übertragen und mal eben schnell die Kunden zu betrachten, die man zuletzt oder vor zwei Tagen bearbeitet hat. Also wollen wir uns nun an die Umsetzung dieser nützlichen Funktion für ein Access-Formular machen. Sie klicken auf die Pfeile und halten die Maustaste gedrückt. Der Internet-Explorer zeigt dann beispielsweise die Titel der zuletzt besuchten Seiten an (s. Bild 1). Bild 2: Anzeige des Verlaufs Vorgaben Zuvor müssen wir noch ein paar Definitionen treffen, die sich auf das Verhalten der Schaltflächen beziehen. Wenn Sie sich nur vorwärts durch die Kunden bewegen, ist es kein Problem alle bisher besich- Seite 13

16 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN tigten Kundendatensätze werden dann in der richtigen Reihenfolge in der Liste der zuvor betrachteten Kunden abgelegt. Was aber geschieht, wenn ich mich vom aktuellen Datensatz zu dem zuvor betrachteten Datensatz bewege und dann einen anderen Datensatz aufrufe als den, von dem ich gerade komme? Sprich: Ich schaue mir erst Datensatz A an, dann Datensatz B, gehe zu Datensatz A zurück und rufe dann Datensatz C auf. Im Browser wird dies so gelöst, dass Datensatz B in diesem Fall aus der Reihenfolge verschwindet. Im Verlauf werden alle Seiten angezeigt. Dies wollen wir auch so umsetzen. Ausgangsformular Wir beginnen mit einem Formular, das wie in Bild 3 aussieht. Es verwendet die Tabelle tblkunden der Beispieldatenbank als Datenherkunft und zeigt alle Felder im Detailbereich an. Im Formularkopf haben wir ein Kombinationsfeld eingebaut, mit dem der Benutzer direkt zu jedem beliebigen Datensatz springen kann. Das Kombinationsfeld heißt cboauswahl und soll immer beim Anzeigen eines Datensatzes direkt mit dem Datensatz gefüllt werden, der auch im Formular erscheint. Dazu müssen wir zunächst die Datensatzherkunft des Bild 3: Entwurf des Beispielformulars Kombinationsfeldes auf die folgende Abfrage einstellen: SELECT tblkunden.kundeid, tblkunden.firma FROM tblkunden ORDER BY tblkunden.firma; Damit das Kombinationsfeld nur den Inhalt des Feldes Firma anzeigt, aber nicht den des Feldes KundeID, stellen Sie die Eigenschaften Spaltenanzahl auf 2 und Spaltenbreiten auf 0cm ein. Um das Kombinationsfeld beim Auswählen eines Datensatzes über die übrigen Elemente des Formulars wie etwa die Navigationsschaltflächen mit dem Da tensatz des Formulars zu synchronisieren, legen Sie die folgende Ereignispro zedur an: Private Sub Form_Current() Me!cboAuswahl = Me!KundeID Diese wird durch das Ereignis Beim Anzeigen des Formulars ausgelöst. Nun müssen wir noch dafür sorgen, dass das Formular auch nach der Auswahl eines Eintrags im Kombinationsfeld gleich den passenden Datensatz anzeigt. Dies erledigt die folgende Ereignisprozedur, die durch das Ereignis Nach Aktualisierung des Kombinationsfeldes ausgelöst wird: Private Sub cboauswahl_ AfterUpdate() Me.Recordset.Find- First "KundeID = " µ & Me!cboAuswahl Vor-und-Zurück-Schaltfläche Wenn dies alles funktioniert, können wir uns der Erweiterung zuwenden. Dazu fügen Sie neben dem Kombinationsfeld zwei Schaltflächen hinzu, die Sie mit dem Kleiner- und dem Größer- Zeichen als Beschriftung ausstatten. Die Schaltflächen sollen cmdvor und cmdzurueck heißen und jeweils eine eigene Ereignisprozedur beim Anklicken auslösen. Mit den Schaltflächen sieht der Formularkopf nun wie in Bild 4 aus. Verlauf speichern Bevor wir uns an die Programmierung der Funktion für die Schaltflächen begeben, müssen wir erst einmal eine Mög- Seite 14

17 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN lichkeit schaffen, die zuletzt besuchten Datensätze zu speichern. Dies wird ohne Zweifel eine Tabelle sein, da die Daten ja auch nach dem Schließen und erneutem Öffnen zur Verfügung stehen sollen. Aber welche Felder benötigen wir in dieser Tabelle? Die erste Frage, die sich stellt, ist die nach dem Primärschlüsselfeld. Benötigen wir ein eigenes Primärschlüsselfeld, wenn wir doch wahrscheinlich in einem Feld der Tabelle den Wert des Primärschlüsselfeldes der Tabelle tblkunden speichern also der Tabelle, für die wir die Zugriffe speichern möchten? Man könnte es tun, wenn man dieses Primärschlüsselfeld zur Sortierung verwenden möchte. Was aber ist, wenn man erst den Kunden A aufruft, dann Kunde B und Kunde C und schließlich wieder zu Kunde A zurückspringt? Kunde A kann dann, wenn der Verlauf tatsächlich in chronologischer Reihenfolge dargestellt werden soll, nicht mehr über den Primärschlüsselwert an die richtige Position gebracht werden, da diese ja nicht geändert werden kann. Bild 4: Kombinationsfeld zur Auswahl von Kunden-Datensätzen. Rechts die Schaltflächen zum Vor- und Zurückblättern Bild 5: Diese Tabelle speichert den Verlauf der zuletzt aufgerufenen Kunden. Also vergeben wir keinen eigenen Autowert-Primärschlüssel für die Tabelle, sondern übernehmen einfach den Primärschlüssel aus der Kundentabelle als eindeutiges Merkmal für die angezeigten Datensätze. Diesen benötigen wir dann später auch, um einen der Kundendatensätze aus der Verlaufsliste heraus aufrufen zu können. Wie aber sortieren wir die Datensätze dann in chronologischer Reihenfolge, und wie bringen wir diese in Ordnung, wenn der Benutzer einen bereits im Verlauf befindlichen Kunden nochmals aufruft? Zu diesem Zweck fügen wir der Tabelle ein Feld namens Zugriffszeit hinzu. Diese nimmt Datum und Zeit des letzten Zugriffs auf diesen Datensatz auf. Auf diese Weise brauchen wir, wenn der Benutzer einen bereits in der Verlaufstabelle befindlichen Datensatz erneut aufruft, auch nur eine kleine Änderung an diesem Datensatz vorzunehmen nämlich den Wert im Feld Zugriffszeit zu aktualisieren. Wenn wir die Anzeige der Verlaufsliste dann absteigend nach dem Inhalt des Feldes Zugriffszeit sortieren, liefert diese immer die zuletzt verwendeten Datensätze zuerst. Die Tabelle zum Speichern des Verlaufs soll tblverlauf heißen und nimmt aktuell die Felder auf, die auch in der Entwurfsansicht in Bild 5 zu erkennen sind. Noch nicht gesprochen haben wir über das letzte Feld dieser Tabelle namens PopuplisteID. Wir wollen ja wie im Internet Explorer sowohl eine Verlaufsliste mit der kompletten Historie der zuletzt aufgerufenen Kunden darstellen, aber auch Ziele für die beiden Schalt- Seite 15

18 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN flächen zum Anspringen des vorherigen und des nächsten Datensatzes speichern und diese auch in einem Popup-Listenfeld anzeigen. Letztere sollen allerdings nach dem Schließen des Formulars gelöscht und nach dem erneuten Öffnen und Auswählen von Datensätzen wieder gefüllt werden. Für diese Liste wollen wir jeweils maximal sieben Einträge speichern. Wenn der Benutzer Kunde 1 auswählt, erhält Kunde 1 in diesem Feld den Wert 0. Wählt der Benutzer dann Kunde 2 aus, erhält Kunde 1 den Wert -1 und Kunde 2 den Wert 0. Folgt dann Kunde 3, erhält Kunde 1 den Wert -2, Kunde 2 erhält den Wert -1 und Kunde 3 den Wert Bild 7: Dies ist die Datensatzherkunft für die Popupliste. 0. Springt der Benutzer dann zu Kunde 2 zurück, erhält Kunde 1 den Wert -1, Kunde 2 den Private Sub Form_Current() Wert 0 und Kunde 3 den Wert 1 im Feld Me!cboAuswahl = Me!KundeID PopuplisteID. VerlaufSpeichern Me!KundeID, Me!Firma Der aktuelle Kunde erhält also immer den Wert 0, die Liste wird immer in Richtung der positiven Zahlen erweitert. Was aber geschieht, wenn der Benutzer nun vom aktuellen Kunden (Nummer 0) aus zum vorherigen Kunden springen kann (-1) und zum folgenden (1), aber einen neuen Kunden auswählt? In diesem Fall wird der Pfad in Richtung positiver Zahlen gelöscht, der neue Kunde mit der Zahl 0 und die übrigen mit -1, -2 und -3 versehen. Bild 6: Die Popupliste realisieren wir in Form eines Listenfeldes, das zunächst unsichtbar ist und nur bei Bedarf eingeblendet wird. Me!lstPopupliste.Visible = False PopuplisteAktualisieren Programmierung des Verlaufs Theoretisch hört sich das alles recht einfach an in der Praxis ist es allerdings etwas komplizierter als erwartet. Vor allem solche Details wie das Einblenden der Popupliste nach längerem Drücken der beiden Schaltflächen cmdvorheriger und cmdnaechster sind recht aufwendig zu programmieren. Die Popupliste Unter den Schaltflächen cmdvorheriger und cmdnaechster müssen wir noch die Liste unterbringen, die beim längeren Drücken einer der beiden Schaltflächen eingeblendet wird. Dies erledigen wir mit einem Listenfeld namens If Nz(DLookup("PKID", "tblverlauf", "PopuplisteID > 0"), 0) = 0 Then Else Me!cmdNaechster.Enabled = False Me!cmdNaechster.Enabled = True If Nz(DLookup("PKID", "tblverlauf", "PopuplisteID < 0"), 0) = 0 Then Else Me!cmdVorheriger.Enabled = False Me!cmdVorheriger.Enabled = True Listing 1: Diese Prozedur wird beim Anzeigen eines jeden Datensatzes ausgeführt. Seite 16

19 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Private Sub VerlaufSpeichern(lngPKID As Long, strtitel As String) Dim db As DAO.Database Set db = CurrentDb On Error Resume Next db.execute "INSERT INTO tblverlauf(pkid, Titel, Zugriffszeit) VALUES(" & lngpkid & ", '" & Replace(strTitel, "'", "''") _ & "', " & ISODatum(Now) & ")", dbfailonerror If Err.Number = 3022 Then db.execute "UPDATE tblverlauf SET Zugriffszeit = " & ISODatum(Now) & " WHERE PKID = " & lngpkid Set db = Nothing Listing 2: Speichern des soeben aufgerufenen Datensatzes in der Tabelle tblverlauf lstpopupliste, das wir wie in Bild 6 im Formular positionieren. Für dieses Listenfeld legen wir eine Datensatzherkunft namens qrypopupliste an. Diese sieht im Entwurf wie in Bild 7 aus und liefert alle Datensätze der Tabelle tblverlauf, deren Feld PopuplisteID nicht leer ist und zwar in absteigender Reihenfolge nach diesem Feld sortiert. Popupliste beim Laden zurücksetzen Wenn das Formular geöffnet wird, soll eine neue Historie der zuletzt verwendeten Datensätze geschaffen werden. Dies betrifft nicht den eigentlichen Verlauf in zeitlicher Abfolge dieser soll immer erhalten bleiben. Für die Auswahl eines der seit dem Öffnen des Formulars angezeigten Datensätze ist aber auch nur der Inhalt des Feldes PopupListeID der Tabelle tblverlauf verantwortlich, sodass dieses Feld einfach nur beim Laden des Formulars geleert werden soll. Dies erledigt die Ereignisprozedur Form_Load, die durch das Ereignis Beim Laden ausgelöst wird: Private Sub Form_Load() Dim db As DAO.Database Set db = CurrentDb db.execute "UPDATE tblverlauf SET µ PopuplisteID = NULL", dbfailonerror Set db = Nothing Speichern des aktuellen Datensatzes Die erste Aufgabe lautet, überhaupt den aktuellen Datensatz in die Tabelle tblverlauf einzutragen beziehungsweise die Position für die Anzeige in der Popup liste der zuletzt verwendeten Datensätze zu speichern. Wann soll dieser überhaupt gespeichert werden? Direkt nach dem Anzeigen. Dies erledigen wir am einfachsten mit einer Ereignisprozedur, die durch das Ereignis Beim Anzeigen des Formulars ausgelöst wird. Diese Prozedur sieht wie in Listing 1 aus und stellt zunächst das Kombinationsfeld cboauswahl auf den Datensatz ein, der auch im Formular angezeigt wird. Dann ruft sie eine Prozedur namens VerlaufSpeichern auf, welche den Primärschlüsselwert und die Bezeichnung des aktuellen Datensatzes, hier den Wert des Feldes Firma, als Parameter erwartet. Diese Prozedur finden Sie in Listing 2: Sie legt einen neuen Datensatz in der Tabelle tblverlauf an und trägt dabei den Primärschlüsselwert des Datensatzes (aus dem Parameter lngpkid), den Titel für die Anzeige im Popup-Listenfeld (aus strtitel) sowie Zeit und Datum ein. Sollte bereits ein Datensatz für diesen Kunden vorhanden sein, löst der Versuch, diesen erneut anzulegen, den Fehler 3022 aus. Diesen behandeln wir entsprechend, indem wir einfach dem bereits vorhandenen Datensatz den aktuelle Wert für Datum und Zeit zuweisen. Damit wäre der aktuell ausgewählte Datensatz also zumindest schon in der Tabelle tblverlauf gespeichert. Die aufrufende Prozedur Form_Current blendet nun das Listenfeld lstpopupliste aus, da diese ja gegebenenfalls durch vorheriges Drücken einer der beiden Schaltflächen cmdvorheriger oder cmdnaechster eingeblendet worden sein könnte. Dann ruft sie eine weitere Prozedur auf, die sich um das aktualisieren des Feldes PopuplisteID in der Tabelle tblverlauf kümmert mehr dazu weiter unten. Schließlich sollen auch noch, wie beim Internet Explorer, die beiden Schaltflächen cmdvorheriger und cmdnaechster aktiviert oder deaktiviert werden je Seite 17

20 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Private Sub PopuplisteAktualisieren() Dim db As DAO.Database Dim lngpositiondesaktuelleneintragsinpopupliste As Long Dim intdifferenz As Integer Dim intanzahleintraege As Integer Set db = CurrentDb intanzahleintraege = DCount("PKID", "tblverlauf", "PopuplisteID IS NOT NULL") If intanzahleintraege = 0 Then db.execute "UPDATE tblverlauf SET PopuplisteID = 0 WHERE PKID = " & Me!KundeID, dbfailonerror Else lngpositiondesaktuelleneintragsinpopupliste = Nz(DLookup("PopuplisteID", "tblverlauf", "PKID = " & Me!KundeID), 99) If lngpositiondesaktuelleneintragsinpopupliste = 99 Then db.execute "UPDATE tblverlauf SET PopuplisteID = NULL WHERE PopuplisteID > 0", dbfailonerror db.execute "UPDATE tblverlauf Set PopuplisteID = PopuplisteID - 1 WHERE PopuplisteID IS NOT NULL" db.execute "UPDATE tblverlauf SET PopuplisteID = " & DMax("PopuplisteID", "tblverlauf", _ "PopuplisteID IS NOT NULL") + 1 & " WHERE PKID = " & Me!KundeID, dbfailonerror Else intdifferenz = lngpositiondesaktuelleneintragsinpopupliste db.execute "UPDATE tblverlauf SET PopuplisteID = PopuplisteID - " & intdifferenz _ & " WHERE PopuplisteID IS NOT NULL", dbfailonerror Set db = Nothing Listing 3: Aktualisieren der Werte für das Feld PopuplisteID der Tabelle tblverlauf nachdem, ob in der Popupliste bereits Einträge stehen, die seit dem Öffnen des Formulars vor oder nach dem aktuellen Datensatz angezeigt wurden. Dazu prüft die Prozedur per DLookup-Funktion, ob die Tabelle tblverlauf mindestens einen Datensatz enthält, dessen Wert im Feld PopuplisteID größer als 0 ist. In diesem Fall blendet sie die Schaltfläche cmdnaechster ein, anderenfalls wird die Schaltfläche ausgeblendet. Gleiches geschieht wenige Zeilen danach mit der Schaltfläche cmdvorheriger. Popup-Liste aktualisieren Die Prozedur PopuplisteAktualisieren wird jeweils beim Anzeigen eines Datensatzes im Formular aufgerufen (s. Listing 3). Sie ermittelt zunächst die Anzahl der Einträge, die im Listenfeld lstpopupliste angezeigt werden sollen. Dies sind alle Datensätze der Tabelle tblverlauf, für die im Feld PopuplisteID zuvor ein Zahlenwert hinterlegt wurde. Wenn das Formular soeben geöffnet wurde, ist dieser Wert 0 dafür hat die weiter oben beschriebene Prozedur Form_Load gesorgt, die das Feld PopuplisteID für alle Datensätze geleert hat. Wenn die Anzahl der Einträge also 0 ist, wie es direkt nach dem Öffnen des Formulars der Fall ist, führt die Prozedur gleich den If-Teil der If...Then-Bedingung der Prozedur aus. Die einzige dort enthaltene Anweisung stellt den Wert des Feldes PopuplisteID für den aktuell angezeigten Datensatz auf den Wert 0 ein. 0 steht ja, wie weiter oben beschrieben, für den aktuell geöffneten Datensatz. In diesem Fall ist die Prozedur damit beendet. Die aufrufende Prozedur Form_Current prüft nun noch, ob eine der beiden Schaltflächen cmdvorheriger oder cmdnaechster aktiviert wird werden muss, was aber nicht der Fall ist. Weiteren Datensatz ansehen Schauen wir uns an, was in der Prozedur PopuplisteAktualisieren geschieht, wenn der Benutzer nun einen weiteren Datensatz betrachtet. Dies löst den Else-Teil der If...Then-Bedingung aus, denn nun gibt es ja bereits einen Datensatz, dessen Feld PopuplisteID gefüllt ist (hier mit dem Wert 0). An dieser Stelle wissen wir noch nicht, dass bislang nur ein einziger Datensatz Seite 18

21 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN zur Popupliste hinzugefügt wurde und dass der nun ausgewählte Datensatz noch nicht dazugehört. Daher prüft die Prozedur dies mit einem weiteren Aufruf der DLookup-Funktion, der diesmal untersucht, ob das Feld PopuplisteID für den aktuell im Formular angezeigten Datensatz bereits einen Wert enthält. Ist dies nicht der Fall, liefert DLook up den Wert Null, was durch die umschließende Nz-Funktion in den Wert 99 umgewandelt und in der Variablen lngpositi- ondesaktuelleneintrags- InPopupliste gespeichert wird. Die nun folgende If... Then-Bedingung prüft, ob lngpositiondesaktuellen- EintragsInPopupliste den Wert 99 aufweist. In diesem Fall leert die Prozedur das Feld PopuplisteID für alle Datensätze, in denen dieses Feld einen Wert größer als 0 aufweist. Dies ist etwa für den Fall, dass der Benutzer Kunde 1, Kunde 2 und Kunde 3 aufruft, dann zu Kunde 2 zurückspringt und Kunde 4 aufruft in diesem Fall soll Kunde 3 aus der Popupliste entfernt werden. Anschließend ändert die Prozedur den Wert des Feldes PopuplisteID für alle Datensätze, in denen dieses Feld nicht leer ist, indem es den Wert 1 subtrahiert. Für unseren zuvor angezeigten Datensatz ändert sich der Wert in diesem Feld also von 0 auf -1. Dann stellt es den entsprechenden Wert für den aktuell Bild 8: Vier Kunden mit dem Index für die Popup-Liste Bild 9: Der Kunde mit PKID = 3 ist nun der aktuell angezeigt Kunde. Bild 10: Der bisher als folgender Datensatz markierte Kunde wird aus der Liste entfernt, die übrigen rücken nach hinten. angezeigten Datensatz in der Tabelle tblverlauf auf den Wert 0 ein. Der aktuelle Stand also: Ein Eintrag mit dem Wert 0, einer mit -1. Schauen wir uns auf diese Weise noch die zwei weiteren Einträge an, weisen die vier Einträge die Werte 0, -1, -2 und -3 im Feld Popup- ListeID der Tabelle tblverlauf auf. Die Abfrage qryverlauf zeigt diese Situation in Bild 8. Ausgehend davon soll nun einmal ein Datensatz ausgewählt werden, der bereits im Verlauf enthalten ist. Dabei spielt es keine Rolle, ob Sie diesen über das Kombinationsfeld cboauswahl auswählen oder über die Navigationsschaltflächen des Formulars (später kommen natürlich noch Prozeduren hinzu, mit denen Sie dies über die Schaltflächen cmdvorheriger und cmdnaechster beziehungsweise über das Listenfeld lstpopupliste erledigen können). Wählen wir also nun beispielsweise den Datensatz aus, bei dem der Wert -1 im Feld PopuplisteID steht. Wir springen an dieser Stelle also quasi einen Datensatz zurück. Dies löst wieder die Prozedur Form_Current aus, und diese startet die Prozedur PopuplisteAktualisieren. Wir ermitteln wieder den Wert des Feldes PopuplisteID des aktuellen Datensatzes und speichern diesen in der Variablen lngpositiondesaktuelleneintragsin- Popupliste. Da dieser Wert nun nicht mehr 99, sondern -1 beträgt, steuert die Prozedur in der inneren If...Then- Bedingung nun nicht den If-, sondern den Else-Teil an. Hier speichert die Prozedur den Wert von lngpositiondesaktuelleneintrags- InPopupliste in der Variablen intdifferenz. intdifferenz enthält also nun den Abstand des neu ausgewählten Datensatzes von dem zuletzt ausgewählten Datensatz. Dieser Wert wird benötigt, damit wir die Werte des Feldes PopuplisteID so verschieben können, dass Seite 19

22 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN der aktuell ausgewählte Datensatz den Wert 0 erhält und alle anderen entsprechend angepasst werden. Dies erledigen wir in einer einzigen UPDATE-Anweisung, die für alle Datensätze, die überhaupt einen Wert im Feld PopuplisteID eine Wert aufweisen, den in intdifferenz gespeicherten Wert subtrahiert. Das Ergebnis sieht nun wie in Bild 9 aus: Statt drei vorheriger Einträge gibt es nun zwei vorherige Einträge und einen folgenden Eintrag in der Tabelle. Nun gibt es noch eine Konstellation, die wir beachten müssen. Was geschieht nun, wenn der Benutzer einen Datensatz auswählt, der noch nicht über das Feld PopupListeID als Eintrag der Popup-Liste markiert ist? Der Plan ist, dass dieser Eintrag der neue aktuelle Eintrag wird. Die vom bisherigen aktuellen Eintrag aus als folgende Einträge markierten Datensätze sollen wegfallen, die übrigen werden entsprechend angepasst. Das Ergebnis soll wie in Bild 10 aussehen. Der Datensatz mit dem Wert 4 im Feld PKID fällt weg, der neue Datensatz mit der 14 erhält den Wert 0 im Feld PopuplisteID und bei den übrigen wird der Wert 1 vom Feld PopuplisteID subtrahiert. Dieser Fall wird ebenfalls im ersten Teil der inneren If...Then-Anweisung behandelt. lngpositiondesaktuellen- EintragsInPopupliste erhält hier den Wert 99, da der Kunde ja noch nicht ausgewählt wurde. Die erste der drei UPDATE-Anweisungen leert das Feld Bild 11: Das Listenfeld zum Auswählen der vorherigen und der folgenden Datensätze in Aktion PopuplisteID für alle Datensätze, bei denen der Wert dieses Feldes größer als 0 ist. Die zweite stellt dieses Feld für den aktuellen Datensatz auf 0 ein und zieht für alle übrigen Datensätze, deren Feld PopuplisteID nicht leer ist, den Wert 1 ab. Vorherigen und nächsten Datensatz auswählen Damit haben wir bereits alle Fälle abgedeckt und können uns nun um die Steuerelemente zum Ansteuern der als vorherige und nächste Datensätze markierten Kunden kümmern. Dabei sind die Aktionen zum Auswählen des vorherigen und des nächsten Datensatzes sowie das Anzeigen der Liste der vorherigen und nächsten Datensätze eng miteinander verknüpft. Der Hintergrund ist, dass Access kein Ereignis vorsieht, das reagiert, wenn man eine Schaltlfäche eine gewisse Zeit gedrückt hält. Genau dies ist aber das Ziel wir wollen ja das Verhalten des Internet Explorers möglichst genau nachstellen. Dazu soll ein einfacher Mausklick etwa auf die Schaltfläche cmd- Vorheriger den vorherigen Datensatz anzeigen. Ein längerer Klick hingegen soll das Listenfeld mit den zuletzt verwendeten Datensätzen einblenden (s. Bild 11). Warum ist dies so kompliziert? Man könnte doch die Ereigniseigenschaft Beim Klicken nutzen, um direkt zum vorherigen oder folgenden zu springen und die Ereignisse Bei Maustaste ab und Bei Maustaste auf irgendwie nutzen, um das Listenfeld lstpopupliste einzublenden. Grundsätzlich ist das eine gute Idee, scheitert aber an den Eigenschaften der Ereignisse. Wenn wir eine der Schaltflächen anklicken und schnell wieder loslassen, soll also nur das Ereignis ausgelöst werden, das wir für die Ereignisprozedur Beim Klicken hinterlegt haben. Erst, wenn wir die Schaltfläche länger gedrückt halten, soll das Listenfeld eingeblendet werden (dies wollen wir in der Prozedur programmieren, die durch Bei Maustaste ab ausgelöst wird). Wenn wir die Maustaste dann loslassen (löst das Ereignis Bei Maustaste auf aus), soll natürlich nichts weiter geschehen, denn das Ziel ist ja schon erreicht das Listenfeld wurde eingeblendet. Hier macht uns das Ereignis Beim Klicken einen Strich durch die Rechnung, Seite 20

23 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Private Sub cmdvorheriger_mousedown(button As Integer, Shift As Integer, X As Single, Y As Single) Dim i As Integer bolvorheriger = True Me!lstPopupliste.Visible = False For i = 1 To 20 Sleep 10 DoEvents If bolvorheriger = False Then Exit Sub Next i With Me!lstPopupliste.Value = Me!KundeID.Height = Me!lstPopupliste.ListCount * 300.Visible = True End With Listing 4: Das Herunterdrücken der Schaltfläche zum Anzeigen des vorherigen Datensatzes löst diese Prozedur aus. denn dieses wird natürlich immer beim Loslassen der Schaltfläche ausgelöst also auch, wenn wir die Schaltfläche länger gedrückt gehalten haben, um das Listenfeld einzublenden. Also lassen wir die Ereignisprozeduren weg, die durch das Ereignis Beim Klicken ausgelöst werden, und verwenden lediglich die beiden Ereignisse Bei Maustaste ab und Bei Maustaste auf. Bei Zeitgeber? Die nächste Frage ist, wie wir prüfen, wie lange der Benutzer bereits die entsprechende Schaltfläche gedrückt hält. Die erste Idee war, dazu den Zeitgeber des Formulars zu nutzen. Das klappt aber auch nicht, denn das Ereignis Bei Zeitgeber wird nicht ausgelöst, solange der Benutzer eine Schaltfläche gedrückt hält. So sind wir dann bei der Prozedur gelandet, die Sie in Listing 4 finden. Diese stellt zuerst den Wert der Variablen bolvorheriger auf den Wert True ein. Diese Variable soll später auch noch von der Prozedur ausgelesen werden können, die durch das Ereignis Bei Maustaste auf ausgelöst wird. Daher deklarieren wir diese wie folgt im allgemeinen Teil des Klassenmoduls Form_frmKunden des Formulars: Dim bolvorheriger As Boolean Wo wir schon beim Deklarieren sind, legen wir auch gleich noch die entsprechende Variable für die Schaltfläche cmdnaechster an: Dim bolnaechster As Boolean Die Prozedur blendet nun zunächst das Listenfeld aus (sofern dieses überhaupt eingeblendet war). Dies ist für den Fall vorgesehen, dass der Benutzer zuerst durch längeres Drücken der Schaltfläche das Listenfeld einblendet, sich dann aber entscheidet, keinen Eintrag über das Listenfeld auszublenden, sondern direkt über die Schaltfläche cmdvorheriger zum gewünschten Datensatz zu springen. Dann durchläuft die Prozedur eine For...Next-Schleife, und zwar maximal 20 Mal. Im ersten Schritt ruft die Prozedur eine API-Funktion namens Sleep auf, die wie folgt im Kopf des Klassenmoduls deklariert wird: Private Declare Sub Sleep Lib "kernel32" (ByVal dwmilliseconds As Long) Dies führt schlicht und einfach dazu, dass die Prozedur für zehn Millisekunden angehalten wird. Dann prüft die Prozedur, ob die Variable bolvorheriger den Wert False enthält. Um es kurz zu machen: Wenn Sie die Maustaste nicht loslassen, ist dies nicht der Fall und die Prozedur läuft weiter. Dies tut sie maximal solange, bis die Schleife 20 Mal durchlaufen wurde. Dann haben wir 200 Millisekunden gewartet, wenn der Benutzer die Schaltfläche so lange herunterdrückt, soll das Listenfeld lstpopupliste eingeblendet werden. Dies erledigen die folgenden Anweisungen. Die erste stellt das Listenfeld auf den Datensatz ein, Seite 21

24 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Private Sub cmdvorheriger_mouseup(button As Integer, Shift As Integer, X As Single, Y As Single) Dim lngkundeid As Long bolvorheriger = False If Me!lstPopupliste.Visible = False Then lngkundeid = DLookup("PKID", "tblverlauf", "PopuplisteID = -1") Me.Recordset.FindFirst "KundeID = " & lngkundeid Listing 5: Diese Prozedur folgt beim Loslassen der Schaltfläche cmdvorheriger. der aktuell im Formular angezeigt wird dazu nutzt sie die Value-Eigenschaft des Listenfeldes. Die zweite legt mit der Eigenschaft Height die Höhe des Listenfeldes fest, damit dieses genau die in der Datensatzherkunft enthaltene Anzahl Datensätze anzeigt. Dabei haben wir den Faktor nach Augenmaß experimentell ermittelt. Schließlich blendet die Einstellung True für die Eigenschaft Visible das Listenfeld ein. Das war es schon das Listenfeld wird nun unter der Schaltfläche cmdvorheriger eingeblendet. Wie nun können wir sicherstellen, dass beim Loslassen der Schaltfläche vor dem Ablauf von 200 Millisekunden das Listenfeld nicht eingeblendet und stattdessen einfach der vorherige Datensatz angezeigt wird? Dies erledigen wir mit der Prozedur, die durch das Ereignis Bei Maustaste auf ausgelöst wird (s. Listing 5). Private Sub cmdnaechster_mousedown(button As Integer, Shift As Integer, X As Single, Y As Single) Dim i As Integer bolnaechster = True Me!lstPopupliste.Visible = False For i = 1 To 20 Sleep 10 DoEvents If bolnaechster = False Then Exit Sub Next i With Me!lstPopupliste.Value = Me!KundeID.Height = Me!lstPopupliste.ListCount * 300.Visible = True End With Private Sub cmdnaechster_mouseup(button As Integer, Shift As Integer, X As Single, Y As Single) Dim lngkundeid As Long bolnaechster = False If Me!lstPopupliste.Visible = False Then lngkundeid = DLookup("PKID", "tblverlauf", "PopuplisteID = 1") Me.Recordset.FindFirst "KundeID = " & lngkundeid Listing 6: Die Prozeduren zum Einblenden der Popupliste oder Auswählen eines Datensatzes für die Schaltfläche cmdnaechster. Seite 22

25 FORMULARE UND STEUERELEMENTE VORHERIGE DATENSÄTZE ANZEIGEN Diese Prozedur stellt zunächst die Variable bolvorheriger auf den Wert False ein. Dies sorgt dann auch beim Ablauf der Prozedur cmdvorheriger_mouse- Down dafür, dass die Abbruchbedingung innerhalb der Schleife wahr ist und diese Prozedur verlassen wird, ohne das Listenfeld lstpopupliste einzublenden. Damit die Prozedur cmdvorheriger_ MouseUp überhaupt ausgeführt wird, während die Schleife in der Prozedur cmdvorheriger_mousedown durchlaufen wird, bauen wir dort die Anweisung DoEvents ein. Anderenfalls würde die Prozedur cmdvorheriger_mousedown immer zuerst bis zum Ende ausgeführt werden, bevor die Prozedur cmdvorheriger_mouseup überhaupt gestartet wird. Die Prozedur cmdvorheriger_mouseup sorgt also zunächst dafür, dass die Prozedur cmdvorheriger_mousedown bis zum Ende ausgeführt wird. Dann prüft sie selbst, ob die Maustaste vielleicht solange gedrückt wurde, dass das Listenfeld mittlerweile sichtbar ist. In diesem Fall ist die Prozedur hiermit beendet, der Benutzer kann den gewünschten Datensatz nun aus dem Listenfeld auswählen. Ist das Listenfeld aber noch nicht sichtbar, ermittelt die Prozedur den Primärschlüsselwert des Kunden, der in der Tabelle tblverlauf für das Feld Popup listeid den Wert -1 enthält und somit dem vorherigen Datensatz aus dem Listenfeld entspricht. Dieser wird dann über die FindFirst- Methode des Recordset-Objekts des Formulars ausgewählt. Die beiden Prozeduren, welche die entsprechende Funktionalität für die Schaltfläche cmdnaechster bereitstellen, finden Sie übrigens in Listing 6. Anzeigen des gewählten Datensatzes Nun fehlt noch der letzte Schritt: Die Auswahl eines der Einträge aus dem Listenfeld. Wenn Sie einen der Einträge anklicken, löst dies die folgende Prozedur aus: Private Sub lstpopupliste_click() Me.Recordset.FindFirst "KundeID = " _ & Me!lstPopupliste Me!KundeID.SetFocus Me!lstPopupliste.Visible = False Dies stellt das Formular auf den im Listenfeld angeklickten Datensatz ein und setzt den Fokus auf das Textfeld KundeID des Formulars. Dies ist nötig, damit wir das Listenfeld lstpopupliste ausblenden können anderenfalls würde dies einen Fehler liefern, weil Sie kein Element ausblenden können, während es noch den Fokus besitzt. Der Rest wird über die bereits beschriebenen Ereignisprozeduren erledigt, vor allem durch die Prozedur, die durch das Ereignis Beim Anzeigen des Formulars ausgelöst wird. Restarbeiten Damit das Formular nach der Auswahl eines neuen Datensatzes aus dem Kombinationsfeld cboauswahl den entsprechenden Datensatz anzeigt, hinterlegen sie für seine Ereigniseigenschaft Nach Aktualisierung die folgende Ereignisprozedur: Private Sub cboauswahl_afterupdate() Me.Recordset.FindFirst _ "KundeID = " & Me!cboAuswahl Schließlich sorgt die Schaltfläche OK mit der folgenden Beim Klicken-Ereignisprozedur dafür, dass das Formular wieder geschlossen wird: Private Sub cmdok_click() DoCmd.Close acform, Me.Name Zusammenfassung und Ausblick Wenn Sie viel mit Datensätzen wie Kunden, Artikeln oder sonstigen Daten arbeiten, ist diese Erweiterung eines Formulars enorm hilfreich. Es geschieht doch recht oft, dass man von einem Datensatz zum nächsten wechselt, aber dann doch nochmal schnell zum zuvor verwendeten Datensatz zurück wechseln möchte. Dies wird mit der hier vorgestellten Lösung sehr schön abgebildet. Die dazu angelegte Tabelle tblverlauf wollen wir noch etwas ausgiebiger nutzen nämlich, indem wir die komplette Liste der zuletzt verwendeten Datensätze in der Reihenfolge der Nutzung zur Auswahl anbieten. Dabei wollen wir, ähnlich wie im Internet Explorer oder bei der Darstellung von Mails in Outlook, die Zeiträume wie Heute, Vor einem Tag, Vor zwei Tagen, Letzte Woche et cetera verwenden. Wie dies gelingt, erfahren Sie im Beitrag Auflistung nach Zeiträumen (www. access-im-unternehmen.de/948). Seite 23

26 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Word-Dokumente per VBA einlesen Früher oder später möchten Sie vielleicht einmal Inhalte aus Word-Dokumenten in Access-Tabellen einlesen. Dabei gilt es einiges zu beachten: Zum Beispiel, dass sich die Inhalte nicht etwa über ein seiten- oder spaltenorientiertes Objektmodell einlesen lassen, sondern dass der Inhalt prinzipiell eine einzige Abfolge von Absätzen ist. Wie Sie diese einlesen und dabei auch die unterschiedlichen Absatz- und Zeichenformatvorlagen berücksichtigen, zeigt dieser Beitrag. In diesem Beitrag gehen wir davon aus, dass Ihnen ein Word-Dokument mit einigen Absatzformatvorlagen und Zeichenformatvorlagen vorliegt. Dieses könnte beispielsweise wie in Bild 1 aussehen. Wir möchten in diesem Beitrag die Techniken vorstellen, die dazu notwendig sind, die einzelnen Texte samt der Formatierung in eine Access-Tabelle einzulesen. Dazu werden wir den Text zunächst Absatz für Absatz durchlaufen und die Inhalte speichern, später schauen wir uns noch an, wie Sie die in den einzelnen Absätzen enthaltenen Zeichenformatvorlagen identifizieren können. Diese werden wir in Form entsprechender Auszeichnungen wie unter HTML in den in der Tabelle gespeicherten Texten unterbringen. Formatvorlagen erstellen Wenn Sie zuvor noch nicht mit Formatvorlagen gearbeitet haben, benötigen Sie Bild 1: Beispiel-Dokument dieses Beitrags Seite 24

27 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Bild 2: Formatvorlagen-Dialog noch einige grundlegende Informationen darüber. Es gibt einige verschiedene Typen von Formatvorlagen, von denen wir die folgenden berücksichtigen werden: Absatzformatvorlagen: Damit werden die Formateigenschaften eines kompletten Absatzes festgelegt, also etwa Einzüge, Abstände zum vorherigen und zum nachfolgenden Absatz Zeichenformatvorlagen: Damit legen Sie das Aussehen der Zeichen fest, also etwa Schriftart, Schriftgröße, Fett, Kursiv et cetera. Verknüpfte Formatvorlagen: Dies sind kombinierte Formatvorlagen, die sowohl Formateigenschaften für den Absatz als auch für den enthaltenen Text festlegen. Mit einer solchen Vorlage können Sie also sowohl komplette Absätze versehen als auch einzelne, vor der Zuweisung markierte Textpassagen. Neue Vorlage anlegen Wenn die von Ihnen gewünschten Vorlagen noch nicht vorhanden sind, legen Sie diese zunächst an. Dazu öffnen Sie Bild 3: Der Dialog Neue Formatvorlage etwa unter Word 2010 zunächst den Dialog Formatvorlagen, indem Sie auf den kleinen Pfeil unten rechts in der Ribbongruppe Start Formatvorlagen klicken. Der Dialog sieht wie in Bild 2 aus. Klicken Sie hier auf die Schaltfläche Neue Formatvorlage, erscheint ein weiterer Dialog namens Neue Formatvorlage (s. Bild 3). Hier legen Sie zunächst den Namen für die neue Formatvorlage fest, zum Beispiel H1 für die erste Überschriftenebene. Als Formatvorlagentyp verwenden wir zunächst Absatz. Da dies die oberste Überschriftenebene sein soll, legen Sie für die Eigenschaft Formatvorlage basiert auf den Eintrag (Keine Formatvorlage) fest. Fehlt noch die Formatvorlage für den folgenden Absatz. Damit stellen Sie ein, welche Formatvorlage automatisch für Absätze verwendet werden soll, die nach Einfügen eines Zeilenumbruchs etwa durch Betätigen der Eingabetaste von einem Absatz mit der Formatvorlage H1 aus zugewiesen werden soll. Dieses Absatzformat haben wir bislang noch nicht festgelegt, also lassen wir die Eigenschaft auf H1 stehen. Nun können Sie mit den Elementen im Bereich Formatierung die grundlegenden Formatierungen festlegen: Schriftart Schriftgröße Fett, Kursiv, Unterstrichen Schriftfarbe Horizontale Ausrichtung Vertikale Ausrichtung Seite 25

28 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Bild 4: Schnellformatvorlagen Abstand zur vorherigen Zeile vergrößern und verkleinern Einzug vergrößern und verkleinern Danach folgen noch einige weitere Optionen: Zur Liste der Schnellformatvorlagen hinzufügen: Fügt die neu erstellte Formatvorlage zur Liste der Schnellformatvorlagen im Ribbon zu (s. Bild 4). Dokumente: Wenn Sie gerade eine Dokumentvorlage erstellen, können Sie festlegen, ob die definierten Formatvorlagen in Dokumente übernommen werden, die neu auf Basis dieser Dokumentvorlage erstellt werden. Weiter unten geht es dann ans Eingemachte: Mit einem Klick auf die Schaltfläche Format klappen Sie ein Menü auf, mit dem Sie weitere Dialoge zum Einstellen folgender Parameter öffnen können: Wir wollen an dieser Stelle nicht ins Detail gehen wichtig ist, dass Sie die wichtigen Schritte zum Erstellen der Formatvorlagen kennen. Wenn Sie neben den Absatzformatvorlagen für eine oder mehrere Überschriften auch eine für den Fließtext erstellt haben (im Beispieldokument heißt dieses p), können Sie diese bei den Überschrift- Formatvorlagen für die Eigenschaft Formatvorlage für den folgenden Absatz einstellen. Automatisch aktualisieren: Diese Option bewirkt, dass die Formatvorlage, auf die sich ein Text bezieht, bei Änderung der Formatierung des Textes im Dokument ebenfalls ändert. Wenn Sie also die neu erstellte Absatzformatvorlage auf einen Absatz anwenden und dann im Dokument die Schriftart dieses Absatzes anpassen, wird auch die Schriftart in der entsprechende Formatvorlage angepasst. Nur in diesem Dokument/Neue auf dieser Vorlage basierende Schriftart Absatz Tabstopp Rahmen Sprache Positionsrahmen Nummerierung Tastenkombination Auf diese Weise erstellen wir einige Formatvorlagen und legen ein einfaches Dokument an, das wir im Folgenden einlesen und in einer Tabelle speichern. Verweis auf die Word-Bibliothek Damit Sie gleich einfach auf das Objektmodell von Word zugreifen können, fügen Sie dem VBA-Projekt der aktuellen Datenbank noch einen Verweis auf die Word-Bibliothek hinzu. Dazu wählen Sie im VBA-Editor den Menüpunkt Extras Verweise aus. Dort aktivieren Sie den Eintrag Microsoft Word x.0 Object Library (s. Bild 5). Seite 26

29 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Word-Dokument öffnen Nun wollen wir das Word-Dokument öffnen, die Inhalte der einzelnen Absätze einlesen und zunächst im Direktbereich des VBA-Editors ausgeben und schließlich das Dokument und Word wieder schließen. Dazu verwenden wir die Prozeduren und Funktionen aus Listing 1. Zunächst deklarieren wir dort eine Objektvariable zum Aufnehmen eines Verweises auf Bild 5: Einfügen eines Verweises auf die Word-Bibliothek eine Instanz von Word namens objword. Um die gewünschten Aufgaben durchzuführen, programmieren wir eine Hauptprozedur namens WordDokumentEinlesen. Diese deklariert eine Variable namens strdokument, die den Namen des einzulesenden Dokuments aufnimmt. Diese Variable füllt die Prozedur mit dem Verzeichnis der aktuellen Datenbank plus dem Namen des Beispieldokuments Beispiel.docx. Beim Aufruf der Funktion DokumentOeffnen übergibt die Prozedur den Dokumentnamen und erhält einen Verweis auf das Dim objword As Word.Application Public Function DokumentOeffnen(strDokument As String) As Word.Document Dim objdokument As Word.Document Set objword = New Word.Application Set objdokument = objword.documents.open(strdokument) Set DokumentOeffnen = objdokument End Function Public Function DokumentSchliessen(objDokument As Word.Document) objdokument.close False objword.quit Set objword = Nothing End Function Public Sub WordDokumentEinlesen() Dim objdokument As Word.Document Dim strdokument As String Dim objabsatz As Word.Paragraph strdokument = CurrentProject.Path & "\Beispiel.docx" Set objdokument = DokumentOeffnen(strDokument) objword.visible = True For Each objabsatz In objdokument.paragraphs Debug.Print objabsatz.range.text Next objabsatz DokumentSchliessen objdokument Listing 1: Prozeduren zum Öffnen, Einlesen und Schließen eines Word-Dokuments Seite 27

30 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN geöffnete Dokument zurück. Dokument- Oeffnen füllt sowohl die Objektvariable objword mit einem Verweis auf eine neu erstellte Word-Instanz als auch die Rückgabevariable objdokument mit einem Verweis auf das soeben geöffnete Dokument. Die Prozedur WordDokumentEinlesen stellt dann die Eigenschaft Visible von objword auf True ein und macht das Word-Fenster so sichtbar. Nun kommt die Hauptarbeit: In einer For Each- Schleife über alle Elemente der Paragraphs-Auflistung des Word-Dokuments durchläuft die Prozedur alle Absätze des Dokuments. Dabei gibt sie jeweils den Inhalt der Text-Eigenschaft des zum aktuellen Absatz gehörenden Range- Objekts im Direktfenster aus. Das Ergebnis sieht schließlich wie in Bild 6 aus. Anschließend ruft die Prozedur noch die Funktion DokumentSchliessen auf, die zunächst das aktuelle Dokument mit der Close-Methode schließt. Der Wert False für den ersten Parameter dieser Methode sorgt dafür, dass keine Änderungen gespeichert werden. Anschließend beendet sie die Word- Instanz mit der Quit-Methode und leert die Objektvariable objword. Von hier aus könnten Sie nun auch einfach die Inhalte der Absätze in jeweils einen Datensatz einer Tabelle schreiben. Wir benötigen aber noch weitere Informationen zum Beispiel die Bezeichnung der Absatzformatvorlage, die dem jeweiligen Absatz zugeordnet wurde. Also erweitern wir die Ausgabe innerhalb der Schleife wie folgt: Debug.Print objabsatz.range.µ ParagraphStyle, objabsatz.range.text Die ParagraphStyle-Eigenschaft gibt nun den Namen der jeweils verwendeten Formatvorlage aus, sodass die Ausga- Bild 6: Ausgabe des Inhalts der einzelnen Absätze im Direktbereich Seite 28

31 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN be im Direktbereich nun wie in Bild 7 aussieht. Für den Fall, dass das Word-Dokument aufgrund eines Fehlers einmal geöffnet bleibt oder Sie das Dokument zum Bearbeiten geöffnet haben, erweitern wir außerdem die Funktion Dokument- Oeffnen noch um einen Parameter für die Open-Methode: Set objdokument = objword.documents.µ Open(strDokument,, True) Der hier verwendete Parameter heißt ReadOnly und sorgt dafür, dass die Meldung aus Bild 8 ausbleibt. Damit können wir nun die Texte der einzelnen Absätze und ihre Formatierungen in einer Tabelle speichern. Aber was geschieht, wenn innerhalb eines Absatzes nur ein oder mehrere Wörter mit einer Zeichenformatvorlage versehen sind? Zeichenformatvorlagen erkennen Dies ist etwas komplizierter als das Durchlaufen der Absätze zumindest, wenn man es auf einem etwas umständlicheren Weg probiert. Vor einiger Zeit habe ich einmal alle Zeichen eines Absatzes durchlaufen, bis ich auf ein Zeichen gestoßen bin, das mit einer Public Sub FettErsetzen(objDokument As Word.Document) Dim objfind As Word.Find Set objfind = objdokument.parent.selection.find With objfind.style = "Fett".Text = "".Replacement.Text = "<b>^&</b>".execute Replace:=wdReplaceAll, Format:=True End With Bild 7: Ausgabe der Absätze plus Name der Absatzformatvorlage entsprechenden Zeichenformatvorlage versehen war. Dies habe ich dann als Startpunkt der Formatierung registriert und habe den Text dann Zeichen für Zeichen weiter untersucht, bis ich zum nächsten Zeichen ohne diese Zeichenformatvorlage gelangte. Um diese formatierten Zeichen habe ich dann eine entsprechende Markierung gesetzt, beispielsweise <b> und </b>. Das hat bei längeren Texten natürlich eine Weile gedauert. Listing 2: Suchen und Ersetzen nach bestimmten Zeichenformatvorlagen Bild 8: Meldung, wenn eine bereits geöffnete Datei geöffnet werden soll Wenn man sich jedoch etwas mit Word und mit den Möglichkeiten zum Suchen und Ersetzen beschäftigt, stößt man schnell auf eine wesentlich effizientere und schnellere Technik. Diese sieht beispielsweise wie in Listing 2 aus. Die Prozedur FettErsetzen erwartet einen Verweis auf das zu untersuchende Word-Dokument als Parameter. Sie verwendet eine weitere Objektvariable des Typs Find mit dem Variablennamen objfind. Für dieses Find-Objekt legen Sie zunächst einige Eigenschaften fest: Seite 29

32 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Style: Legt fest, welche Formatierung die zu suchenden Texte haben sollen. Text: Legt den Suchbegriff fest. Eine leere Zeichenkette besagt, dass alle Texte gesucht werden sollen, welche die übrigen Kriterien erfüllen. Replacement.Text: Legt fest, durch welchen Text gefundene Textstellen ersetzt werden sollen. In diesem Fall verwenden wir einen regulären Ausdruck, bei dem ^& den gefundenen Text repräsentiert und <b> und </b> den hinzuzufügenden Text. Schließlich ruft die Prozedur die Execute-Methode auf, die wiederum zwei Parameter verwendet: Bild 9: Texte mit bestimmten Zeichenformatvorlagen werden um öffnende und schließende Tags erweitert. Replace: Der Wert wdreplaceall gibt an, dass alle Fundstellen ersetzt werden sollen. Format: Der Wert True legt fest, dass neben (oder statt) dem Suchtext nach Formatierungen gesucht werden soll eben nach der weiter oben mit Style angegebenen Zeichenformatierung. Als Ergebnis ändert der Aufruf dieser Prozedur den Inhalt des Dokuments. Kein Problem, da wir dieses ohnehin schreibgeschützt geöffnet haben und die Änderungen anschließend auch explizit nicht speichern. Das Ergebnis sieht wie in Bild 9 aus die Zeile wird auch so per VBA eingelesen. Makrorekorder nutzen Wie aber kommt man von einer eher umständlichen Lösung zu so einer einfachen und fast schon genialen Lösung, um die Tags zu einem speziell formatierten Text hinzuzufügen? Die notwendigen Informationen liefert Word selbst. Bild 10: Einblenden der Entwicklerwerkzeuge im Ribbon von Word Seite 30

33 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Bild 11: Ribbon-Befehl zum Start der Aufzeichnung eines Makros Im Gegensatz zu Access bieten Word und Excel die Möglichkeit, verschiedene Benutzeraktionen per Makrorekorder aufzuzeichnen. Sprich: Sie führen ein paar Schritte unter Word durch und Word erstellt den Code, um diese Schritte zu reproduzieren. Diesen Code können Sie natürlich, wie in diesem Fall geschehen, auch als Vorlage für eine eigene Prozedur verwenden, die dann von Access aus aufgerufen wird. Wenn Sie den Makrorekorder etwa unter Word 2010 nutzen möchten, müssen Bild 12: Start der Aufzeichnung eines Makros Sie zunächst ein spezielles Ribbon-Tab einblenden. Dies erledigen Sie in den Word-Optionen, wo Sie den Bereich Menüband anpassen einblenden. Hier finden Sie auf der rechten Seite unter Hauptregisterkarten den Eintrag Entwicklertools (s. Bild 10). Diesen Eintrag aktivieren Sie nun und schließen den Dialog dann wieder. Im Ribbon erscheint nun ein neues Tab-Element wie in Bild 11. Dort finden Sie ganz links die relevanten Befehle. Klicken Sie dort auf die Schaltfläche Makro aufzeichn., erscheint der Dialog aus Bild 12. Hier geben Sie den Namen des zu erstellenden Makros ein (beziehungsweise der VBA-Prozedur, um bei den unter Access gängigen Bezeichnungen zu bleiben) und legen fest, dass das Makro nur im aktuellen Dokument gespeichert werden soll. Klicken Sie auf OK, um die Aufzeichnung des Makros zu starten. Ausgehend davon, dass Sie das zu durchsuchende Dokument bereits geöffnet haben, aktivieren Sie nun den Ersetzen-Dialog (unter Word 2010 unter dem Ribbon-Eintrag Start Bearbeiten Ersetzen zu finden). Dieser sieht im Rohzustand wie in Bild 13 aus. Bild 13: Eingeben der Suchparameter für ein Word-Dokument Hier nehmen Sie nun die folgenden Schritte vor: Seite 31

34 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN Soweit noch nicht geschehen, erweitern Sie die Ansicht mit der Erweitern>>-Schaltfläche. Klicken Sie im Bereich Ersetzen auf die Schaltfläche Format und wählen Sie den Eintrag Formatvorlage aus. Im nun erscheinenden Dialog selektieren Sie den Namen der zu ersetzenden Zeichenformatierung, hier Fett. Nun folgt der Teil, den man sich schlicht und einfach ergooglen muss: Fügen Sie im Feld Ersetzen durch den Wert <b>^&</b> ein. Klicken Sie auf Alle ersetzen. Word zeigt das Resultat dieses Vorgangs nun in einem Meldungsfenster an (s. Bild 14). Damit ist die aufzuzeichnende Aktion beendet. Sie können den Makro rekorder mit dem Ribbon-Eintrag Ent wick lertools Code Aufzeichnung be en den stoppen. Nun schauen wir uns den aufgezeichneten Code an, den Sie im VBA-Editor von Word (Alt + F11) im Modul Module New Macros finden. Dieser sieht wie in Listing 3 aus. Mit ein wenig Experimentierfreude lässt sich daraus der für unsere Zwecke geeignete und weiter oben vorgestellte Code ermitteln. Weitere Zeichenformatvorlagen Wenn Sie nun auch noch eine Zeichenformatvorlage anwenden möchten, welche die markierten Zeichen kursiv setzt, ist auch das kein Problem: Sie fügen einfach eine neue Zeichenformatvorlage hinzu, nennen diese Kursiv Bild 14: Ergebnis des Suchen-Ersetzen-Vorgangs Sub macformatierungersetzen() ' ' macformatierungersetzen Makro ' Selection.Find.ClearFormatting Selection.Find.Style = ActiveDocument.Styles("Fett") Selection.Find.Replacement.ClearFormatting With Selection.Find.Text = "".Replacement.Text = "<b>^&</b>".forward = True.Wrap = wdfindcontinue.format = True.MatchCase = False.MatchWholeWord = False.MatchWildcards = False.MatchSoundsLike = False.MatchAllWordForms = False End With Selection.Find.Execute Replace:=wdReplaceAll Listing 3: Suchen und Ersetzen mit dem aufgezeichneten Makro Public Sub KursivErsetzen(objDokument As Word.Document) Dim objfind As Word.Find Set objfind = objdokument.parent.selection.find With objfind.style = "Kursiv".Text = "".Replacement.ClearFormatting.Replacement.Text = "<i>^&</i>".execute Replace:=wdReplaceAll, Format:=True End With Listing 4: Prozedur zum Auszeichnen kursiv gesetzter Schrift und aktivieren die Option für die kursive Schrift. Speichern Sie das Dokument, nachdem Sie ein paar Zeichen mit dieser Zeichenformatvorlage formatiert haben. Nun fügen wir dem VBA-Modul unter Access eine weitere kleine Prozedur namens KursivErsetzen hinzu, die wie in Listing 4 aussieht. Diese ist genau- Seite 32

35 INTERAKTIV WORD-DOKUMENTE PER VBA EINLESEN so aufgebaut wie die Prozedur zum Ersetzen der fett gesetzten Schrift. Der Unterschied ist, dass Sie nach der Formatvorlage Kursiv sucht und entsprechende Zeichen in die Tags <i> und </ i> einschließt. Ein kleiner Test zeigt, dass auch diese Formatierung erfolgreich ausgezeichnet wird. Mehrere Formatvorlagen gleichzeitig Nun stellt sich noch die Frage, wie man Texte auszeichnet, die mit mehreren Zeichenformatvorlagen gleichzeitig ausgezeichnet sind zum Beispiel fett und kursiv. Oder wo solche Formatierungen ineinander übergehen wie hier: Von fett über fett-kursiv zu kursiv Die Antwort lautet: Sie brauchen sich keine Sorgen darum zu machen. Wenn Sie konsequent mit Zeichenformatvorlagen arbeiten, benötigen Sie hier ja drei Vorlagen eine fette, eine fett-kursive und eine kursive. Dies sieht dann mit Auszeichnungen so aus: Von <b>fett über</b><b><i> fett-kursiv</ i></b><i>zu kursiv</i> Public Sub WordDokumentEinlesen(strDokument As String) Dim objdokument As Word.Document Dim objabsatz As Word.Paragraph Dim lngdokumentid As Long Set objdokument = DokumentOeffnen(strDokument) If objdokument Is Nothing Then Word-Dokument ausgeben An dieser Stelle wollen wir den Inhalt nun zunächst im Direktfenster des VBA- Editors ausgeben zusammen mit den HTML-Auszeichnungen für die Textformatierungen und mit der Angabe des jeweiligen Absatzformates. Dies sieht dann wie in Listing 5 aus. Die Prozedur erwartet als Parameter den Namen des einzulesenden Word-Dokuments, der Aufruf sieht beispielsweise wie folgt aus: WordDokumentEinlesen CurrentProject.Path & "\Beispiel.docx" MsgBox "Das Dokument wurde nicht gefunden." Exit Sub objword.visible = True FettErsetzen objdokument KursivErsetzen objdokument For Each objabsatz In objdokument.paragraphs Debug.Print objabsatz.range.paragraphstyle, objabsatz.range.text Next objabsatz DokumentSchliessen objdokument Listing 5: Fertige Prozedur zum Ausgeben des Dokument-Inhalts im Direktfenster Zusammenfassung und Ausblick Dieser Beitrag liefert die Grundlagen zum Einlesen von Word-Dokumenten, wobei diese bereits in einem bestimmten Format vorliegen sollten nämlich mit den entsprechenden Absatz- und Zeichenformatvorlagen ausgezeichnet. In einem weiteren Beitrag namens Von Word zu Access-Richtext/HTML (www. access-im-unternehmen.de/944) schauen wir uns an, wie Sie einen solchen Text in Access-Tabellen speichern und diesen dann in das Richtext-Format von Access konvertieren. Seite 33

36 INTERAKTIV DATENBANKEXPORT NACH MASS Von Word zu Access-Richtext/HTML Das mit Access 2007 eingeführte Richtext-Format ist für viele Anwendungen interessant. Die Eingabe und Formatierung ist aber nicht wirklich ergonomisch da sind Sie von Word mehr Komfort gewohnt. Warum aber nicht die Texte in Word eingeben und dann nach Access übernehmen? Alles, was Sie brauchen, sind einige Grundlagenkenntnisse über den Aufbau von Dokumenten in Word und die Absatz- und Zeichenvorlagen sowie zwei oder drei Tricks schon haben Sie den Inhalt eines Word-Dokuments samt Formatierungen in ein Memofeld mit Richtext-Formatierung überführt. Damit erschlagen Sie dann gleich zwei Fliegen mit einer Klappe: Sowohl bereits existierende als auch neu erstellte Dokumente können Sie so recht einfach nach Access überführen. Einzige Voraussetzung für die Verwendung der in diesem Beitrag vorgestellten Techniken ist, dass Sie die Absätze mit entsprechenden Absatzformatvorlagen versehen haben und hervorgehobene Texte mit entsprechenden Zeichenformatvorlagen. Wenn Sie das nicht tun, wäre nun ein guter Zeitpunkt, um damit zu beginnen! Schauen wir uns zunächst einmal an, welche Formatierungen uns das Richtext-Format von Access bietet. Eins vorweg: Wir können keine eigenen Formate definieren und auch nicht die gängigen Absatzformate wie h1, h2, h3 oder p verwenden. Deshalb ist das Übertragen von Inhalten mit benannten Absatz- und Zeichenformatvorlagen eine Einbahnstraße. Um die einzelnen Formatierungen zu erkennen, erstellen wir ein Experimentierformular mit zwei Textfeldern. Das Bild 1: Anzeige der HTML-Version der formatierten Texte erste zeigt den Inhalt im Richtext-Format an, das zweite soll den Inhalt mit HTML- Auszeichnungen anzeigen. In der Formularansicht sieht dies wie in Bild 1 aus. Der jeweils eingegebene oder formatierte Text wird gleich nach jeder Änderung im rechten Textfeld mit HTML-Auszeichnungen angezeigt. Dafür sorgt die folgende Ereignisprozedur, die durch das Ereignis Bei Änderung ausgelöst wird: Private Sub txtrichtext_change() Me!txtHTML = Me!txtRichtext.Text Damit das Betätigen der Eingabetaste im linken Textfeld zum Einfügen eines Zeilen umbruchs führt und nicht zum Wechsel zum rechten Textfeld, stellen Sie die Eigenschaft Eingabetastenverhalten auf den Wert Neue Zeile im Feld ein. Nun brauchen Sie nur noch die zur Verfügung stehenden Formatierungen anzuwenden und können dann rechts die jeweiligen HTML-Auszeichnungen abgreifen. Das Ergebnis sieht wie folgt aus hier zunächst für die Absatzformatierungen: <div>einfacher Absatz</div> Leerer Absatz: <div> </div> <div>links zentrierter Absatz.</ div> <div align=center>mittig zentrierter Absatz.</div> <div align=right>rechts zentrierter Absatz.</div> <ul> <li>auflistung, erster Punkt</li> <li>auflistung, zweiter Punkt</li> </ul> Seite 34

37 INTERAKTIV DATENBANKEXPORT NACH MASS <ol> <li>aufzählung, erster Punkt</li> <li>aufzählung, zweiter Punkt</li> </ol> <strong>fette </strong>schrift <em>kursiver </em>schrift <u>unterstrichener </u>schrift <strong><em>fette, kursive </ em></strong>schrift zugreifen und diese nach Wunsch beispielsweise mit verschiedenen Formatierungen ausstatten. Die Tabelle für diesen Zweck soll nicht nur ein Dokument gleichzeitig aufnehmen, sondern gegebenenfalls auch einmal mehrere deshalb müssen wir der Tabelle noch ein Feld hinzufügen, welches einen Hinweis auf das Ursprungsdokument liefert. Da dies typischerweise der Dateiname sein wird und wir dieses nicht in jedem Datensatz für dieses Dokument wiederholen möchten, gliedern wir den Dateinamen in eine eigene Tabelle aus, die wir dann mit der Tabelle mit den Inhalten verknüpfen. Diese Tabelle heißt tbldokumente und enthält neben dem Primärschlüsselfeld DokumentID lediglich ein weiteres Feld namens Dokument, welches den Pfad zu der betroffenen Datei enthält. Dieses Feld versehen wir mit einem eindeutigen Index, damit jedes Dokument nur einmal angegeben werden kann (s. Bild 2). <font face="arial Narrow">andere </font>schriftart <font size=4>andere </ font>schriftgröße <font color=red>farbige </ font>schrift <font style="background- COLOR:#FFFF00">farbiger </ font>hintergrund <div>absatz mit weichem<br> Zeilenumbruch</div> (Achtung: Bei einer weiteren Änderung des Inhalts wird dieser durch </div><div> ersetzt!) Bild 2: Tabelle zum Speichern der Dokumentnamen <blockquote><div>zitatabsatz</ div></blockquote> Dokumentinhalt speichern Nun schauen wir uns an, wie wir das Dokument beziehungsweise die einzelnen Absätze in der Datenbank speichern können. Die einfachste Möglichkeit ist, einfach für jeden Absatz einen neuen Datensatz zu einer Tabelle hinzuzufügen. Auf diese Weise können wir später ganz einfach auf die einzelnen Absätze Bild 3: Tabelle zum Speichern der Absätze eines Dokuments Seite 35

38 INTERAKTIV DATENBANKEXPORT NACH MASS Die Tabelle tblabsaetze, welche die Inhalte der einzelnen Absätze aufnehmen soll, finden Sie im Entwurf in Bild 3. Diese Tabelle enthält natürlich ein Fremdschlüsselfeld, mit dem das Dokument aus der Tabelle tbldokumente ausgewählt werden kann, zu dem der Absatz gehört. Außerdem besitzt die Tabelle das Feld Inhalt zum Speichern des Inhalts des jeweiligen Absatzes. Schließlich fehlt noch das Feld AbsatzformatID. Genau wie bei den Dokumenten könnten wir die Bezeichnung des Absatzformats direkt in ein Textfeld der Tabelle tblabsaetze eintragen. Allerdings verwenden wir auch hier eine Lookup-Tabelle, die in diesem Fall noch weitere Felder enthält zum Beispiel HTML-Tags, die wir später zum Erzeugen eines Dokuments im Richtext-Format von Access nutzen Bild 4: Tabelle zum Speichern der Absatzformate eines Dokuments können (dazu später mehr). Diese Tabelle heißt tblab satz formate und sieht in der Entwurfsansicht wie in Bild 4 aus. Auch für das Feld Absatzformat dieser Tabelle legen wir einen eindeutigen Index fest, damit jede Bezeichnung eines Absatzformats nur einmal in der Tabelle gespeichert werden kann. Bild 5: Formular zum Einlesen und Anzeigen der Dokumente Seite 36

39 INTERAKTIV DATENBANKEXPORT NACH MASS Private Sub cmddateiauswaehlen_click() Dim strdokument As String Dim lngdokumentid As Long strdokument = OpenFileName(CurrentProject.Path, "Dokument auswählen", "Word-Dokumente (*.doc;*.docx)") lngdokumentid = WordDokumentEinlesen(strDokument) If Len(lngDokumentID) > 0 Then Me.Requery Me.Recordset.FindFirst "DokumentID = " & lngdokumentid Me!sfmDokumente.Form.Requery Me!txtRichtext = RichTextErstellen(Me!DokumentID) Listing 1: Auswählen und Einlesen des Inhalts eines Word-Dokuments Formular zum Einlesen der Dokumente Um die Absätze, Formate und den Richtext für die eingelesenen Dokumente anzuzeigen, verwenden wir das Formular frmdokumente aus Bild 5. Dieses verwendet die Tabelle tbldokumente als Datenherkunft. Das Unterformular in diesem Formular wiederum heißt sfmdokumente und soll die in der Tabelle tblabsaetze gespeicherten Absätze zu dem im Hauptformular angezeigten Formular darstellen. Es verwendet die Tabelle tblabsaetze als Datenherkunft. Damit das Unterformular jeweils die passenden Datensätze anzeigt, stellen Sie die beiden Eigenschaften Verknüpfen von und Verknüpfen Bild 6: Darstellung eines Dokuments mit den einzelnen Absätzen und mit dem resultierenden Richtext-Dokument Seite 37

40 INTERAKTIV DATENBANKEXPORT NACH MASS nach des Unterformular-Steuerelements auf den Wert DokumentID ein. Neben dem Textfeld txtdokument, das an das Feld Dokument der Datenherkunft gebunden ist, finden Sie eine Schaltfläche namens cmddateiauswaehlen. Diese ruft die Prozedur aus Listing 1 auf. Die Prozedur öffnet zunächst einen Datei öffnen-dialog und weist das Ergebnis der Variablen strdokument zu. Dann ruft es eine Funktion namens Word- DokumentEinlesen auf und übergibt dieser den Namen des einzulesenden Word-Dokuments. Diese Funktion steuert das Einlesen des Word-Dokuments und trägt die Inhalte der einzelnen Absätze in die Tabellen tbldokumente und tblabsaetze ein. War dieser Vorgang erfolgreich, liefert sie den Primärschlüsselwert des neuen Datenatzes der Tabelle tbldokumente zurück. Die Prozedur cmddateiaus waehlen_click aktualisiert dann seine Datenherkunft, stellt den Datensatzzeiger auf den neuen Datensatz ein und aktualisiert auch den Inhalt des Unterformulars. Dann erstellt sie mithilfe der Funktion RichtextErstellen aus den gewonnenen Daten einen Text im Richtext-Format von Access. In der Praxis sollte dies dann wie in Bild 6 aussehen. Dokument einlesen Die grundlegenden Techniken zum Öffnen und Einlesen des Word-Dokuments sowie zum Ausstatten des Word-Dokuments mit entsprechenden Format- und Zeichenformatvorlagen finden Sie im Beitrag Word-Dokumente einlesen ( de/943). Die dort vorgestellte Prozedur WordDokumentEinlesen haben wir noch etwas verfeinert, sodass diese nun wie in Listing 2 aussieht. Auch hier benötigen wir eine Variable, die den Verweis auf die Word-Instanz speichert diese deklarieren wir wie folgt: Dim objword As Word.Application Die Prozedur erwartet den Namen der einzulesenden Datei als Parameter. Die Prozedur DokumentOeffnen (siehe im oben genannten Beitrag) öffnet das Dokument und übergibt einen Verweis auf dieses Dokument an die Objektvariable objdokument. Sollte diese Variable anschließend leer sein, konnte das Dokument offensichtlich nicht geöffnet werden, was in einer entsprechenden Meldung resultiert. Das Word-Fenster wird eingeblendet (dies können Sie auch weglassen) und die beiden Prozeduren FettErsetzen und KursivErsetzen sorgen dafür, dass die entsprechenden formatierten Passagen im Dokument mit öffnenden und schlie- Public Sub WordDokumentEinlesen(strDokument As String) Dim objdokument As Word.Document Dim objabsatz As Word.Paragraph Dim lngdokumentid As Long Set objdokument = DokumentOeffnen(strDokument) If objdokument Is Nothing Then MsgBox "Das Dokument wurde nicht gefunden." Exit Sub objword.visible = True FettErsetzen objdokument KursivErsetzen objdokument lngdokumentid = DokumentSpeichern(strDokument) If Not lngdokumentid = 0 Then For Each objabsatz In objdokument.paragraphs AbsatzSpeichern objabsatz.range.paragraphstyle, objabsatz.range.text, lngdokumentid Next objabsatz DokumentSchliessen objdokument Listing 2: Steuerungsprozedur zum Einlesen des Word-Dokuments Seite 38

41 INTERAKTIV DATENBANKEXPORT NACH MASS ßenden HTML-Tags wie <b>...</b> oder <i>...</i> eingeschlossen werden. Dann legt die Prozedur über die Funktion DokumentSpeichern einen neuen Datensatz für das Dokument in der Tabelle tbldokumente an. Die Funktion liefert den Primärschlüsselwert des neuen Datensatzes zurück, damit diese beim Anlegen der Datensätze in der Tabelle tblabsaetze als Fremdschlüsselwert verwendet werden kann. Mehr zu dieser Funktion lesen Sie weiter unten. Sollte die Funktion einen Primärschlüsselwert ungleich 0 zurückliefern, durchläuft die Prozedur in einer For Each-Schleife alle Absätze des Word-Dokuments, welche über die Paragraphs-Auflistung bereitgestellt werden. Innerhalb der Schleife ruft die Prozedur dann die Routine AbsatzSpeichern auf, die sich um das Anlegen des Datensatzes für den aktuellen Absatz in der Tabelle tblabsaetze kümmert siehe weiter unten. Anschließend ruft sie die Prozedur DokumentSchliessen auf, um das soeben eingelesene Dokument zu schließen. Dokument-Datensatz speichern Die Funktion DokumentSpeichern erwartet als Parameter die Angabe des Dokumentpfades, für das ein neuer Datensatz in der Tabelle tbldokumente angelegt werden soll (s. Listing 3). Sie versucht dann zunächst, einen Datensatz mit dem angegebenen Dokumentpfad in der Tabelle tbldokument zu speichern und zwar etwa mit der folgenden INSERT INTO-Anweisung: Public Function DokumentSpeichern(strDokument As String) As Long Dim db As DAO.Database Dim interror As Integer Dim lngdokumentid As Long Set db = CurrentDb On Error Resume Next db.execute "INSERT INTO tbldokumente(dokument) VALUES('" & strdokument & "')", dbfailonerror interror = Err.Number On Error GoTo 0 Select Case interror Case 3022 If MsgBox("Ein gleichnamiges Dokument ist bereits vorhanden. Überschreiben (Ja) oder unter neuem " _ & "Namen speichern (Nein)?", vbyesno, "Dokument vorhanden") = vbyes Then lngdokumentid = DLookup("DokumentID", "tbldokumente", "Dokument = '" & strdokument & "'") db.execute "DELETE FROM tblabsaetze WHERE DokumentID = " & lngdokumentid Else If BezeichnungErmitteln(strDokument, "tbldokumente", "DokumentID", "Dokument") = True Then db.execute "INSERT INTO tbldokumente(dokument) VALUES('" & strdokument & "')" lngdokumentid = Case 0 lngdokumentid = Case Else MsgBox "Fehler " & Err.Number & ", '" & Err.Description & "'" End Select DokumentSpeichern = lngdokumentid Set db = Nothing End Function Listing 3: Speichern des Datensatzes für das Word-Dokument Seite 39

42 INTERAKTIV DATENBANKEXPORT NACH MASS INSERT INTO tbldokumente(dokument) VALUES('c:\Beispiel.docx') Wenn bereits ein Datensatz mit diesem Dokumentpfad vorhanden ist, löst dies einen Fehler aus, da das Feld Dokument mit einem eindeutigen Index ausgestattet wurde. Damit dies nicht die übliche Fehlermeldung hervorruft, haben wir mit der Anweisung On Error Resume Next die Fehlerbehandlung deaktiviert. Stattdessen speichern wir nach dem Ausführen der INSERT INTO-Anweisung die Fehlernummer in der Variablen interror und aktivieren die Fehlerbehandlung mit On Error Goto 0 wieder. Danach analyisiert die Prozedur den Inhalt von interror in einer Select Case- Bedingung. Hat interror den Wert 3022, war offensichtlich bereits ein Datensatz mit diesem Dokument vorhanden. In diesem Fall fragt die Anwendung, ob dieser Datensatz überschrieben werden soll (Ja) oder ob der Benutzer einen neuen Pfad angeben möchte (Nein). Im ersten Fall haben wir es leicht: Wir müssen einfach nur den Primärschlüsselwert für den bereits vorhandenen Datensatz per DLookup ermitteln und die bereits in der Tabelle tblabsaetze gespeicherten Datensätze löschen, da diese ja gleich neu eingelesen werden sollen. Dies erledigt beispielsweise die folgende SQL-Anweisung: DELETE FROM tblabsaetze WHERE DokumentID = 5 Möchte der Benutzer hingegen einen neuen Dokumentpfad angeben, müssen wir diesen zunächst abfragen. Dazu nutzen wir die Funktion Bezeichnung- Ermitteln, die den Benutzer solange nach einem neuen Dokumentpfad fragt, bis diese einen noch nicht in der Tabelle enthaltenen Text angegeben oder den Vorgang abgebrochen hat. Die Funktion schreibt die neue Bezeichnung in die Variable strdokument, die anschließend weiter verwendet wird aber nur, wenn BezeichnungErmitteln den Wert True zurückliefert. Diese Funktion schauen wir uns im Beitrag Neue Werte in eindeutigen Feldern ( an. Wenn der Benutzer einen gültigen Dokumentpfad angegeben hat, fügt die Prozedur den entsprechenden Datensatz per INSERT INTO-Abfrage zur Tabelle tbldokumente hinzu. Den Wert des Primärschlüssels für den neuen Datensatz ermittelt sie dann mit der Abfrage und speichert diesen in der Variablen lngdokumentid. Tritt beim ersten Versuch, den Datensatz zur Tabelle hinzuzufügen, kein Fehler auf (interror hat dann den Wert 0), ermittelt die Prozedur den neuen Primärschlüsselwert auf die gleiche Weise. Für alle anderen Werte von interror gibt die Funktion eine entsprechende Fehlermeldung aus. In jedem Fall gibt die Funktion aber den Wert der Variablen lngdokumentid zurück. Absätze speichern Die Prozedur AbsatzSpeichern speichert jeweils einen Absatz in der Tabelle tblabsaetze (s. Listing 4). Dabei erwartet sie folgende Parameter: strabsatzformat: Bezeichnung des Absatzformats aus dem Word- Dokument strabsatzinhalt: Text des Absatzes lngdokumentid: Primärschlüsselwert des Datensatzes der Tabelle tbldokumente, in dem der Dokument-Datensatz gespeichert ist Die Prozedur ermittelt zunächst den Primärschlüsselwert des Datensatzes der Tabelle tblabsatzformate, welcher der Bezeichnung des Absatzes aus strabsatzformat entspricht. Public Sub AbsatzSpeichern(strAbsatzformat As String, strabsatzinhalt As String, lngdokumentid As Long) Dim db As DAO.Database Dim lngabsatzformatid As Long Set db = CurrentDb lngabsatzformatid = LookupIDErmitteln("tblAbsatzformate", "AbsatzformatID", "Absatzformat", strabsatzformat) db.execute "INSERT INTO tblabsaetze(dokumentid, AbsatzformatID, Inhalt) VALUES(" & lngdokumentid & ", " _ & lngabsatzformatid & ", '" & Replace(Replace(Replace(strAbsatzinhalt, "'", "''"), vbcr, ""), vblf, "") _ & "')", dbfailonerror Set db = Nothing Listing 4: Speichern eines einzelnen Absatzes in der Tabelle tblabsaetze Seite 40

43 INTERAKTIV DATENBANKEXPORT NACH MASS Bild 7: Daten des Word-Dokuments in den drei Tabellen der Beispieldatenbank Dies erledigt eine weitere Hilfsfunktion namens LookupID Ermitteln, die wir ebenfalls im Beitrag Neue Werte für eindeutig indizierte Felder beschreiben. Diese ermittelt den Primärschlüsselwert eines gegebenenfalls vorhandenen Datensatzes oder erstellt diesen gleich neu und liefert dessen Primärschlüsselwert zurück. In jedem Fall ist nach dieser Anweisung die Variable lngabsatzformatid mit einem Wert ungleich 0 gefüllt, sodass wir alle Informationen zum Anlegen eines neuen Datensatzes in der Tabelle tblabsaetze ermittelt haben. Diesen legt die Prozedur dann auch gleich mit einer entsprechenden INSERT INTO-Anweisung an, die etwa wie folgt lautet: INSERT INTO tblabsaetze(dokumentid, AbsatzformatID, Inhalt) VALUES(37, 2, 'Einleitung des Textes') Bei der Zusammenstellung der INSERT INTO-Anweisung ist zu beachten, dass dort drei verschachtelte Replace-Anweisungen einige Ersetzungen vornehmen: Einfache Hochkommata (') durch doppelte Hochkommata (''), da einfache Hochkommata in SQL-Ausdrücken, die ohnehin schon in Paare einfacher Hochkommate eingefasst sind, das Ende der Zeichenkette signalisieren was durch die danach fortgesetzte Zeichenkette zu einem Fehler führt. vbcr durch eine leere Zeichenkette ("") dies entfernt eventuell enthaltene Zeilenumbrüche. vblf durch eine leere Zeichenkette ("") siehe vblf. Auf diese Weise legt die Prozedur einen Absatz nach dem anderen in Form eines Datensatzes der Tabelle tblabsaetze in der Datenbank an. Damit haben wir bereits alle benötigten Daten in die Tabellen der Datenbank geschrieben. Für unsere kleine Beispieldatei sieht dies wie in Bild 7 aus. Die Richtext-Auszeichnungen Bevor wir uns an die Aufgabe heranwagen, aus den in der Tabelle tblabsaetze gespeicherten Daten Richtext zu erstellen, müssen wir uns noch um die dazu benötigten HTML-Tags kümmern. Immerhin muss der nun zu erstellende Code ja wissen, in welche HTML-Tags er etwa einen Absatz einfassen soll, die mit der Formatvorlage h1 formatiert wurde. Das Problem ist, dass das Richtext-Format von Access nicht alle HTML-Tags unterstützt. Das zweite ist, dass wir diese dementsprechend selbst Seite 41

44 INTERAKTIV DATENBANKEXPORT NACH MASS Bild 8: HTML-Auszeichnungen in der Tabelle tblabsatzformate definieren und an geeigneter Stelle in der Datenbank hinterlegen müssen. Welcher Ort aber bietet sich dafür mehr an als die Tabelle tblabsatzformate? Hier legen wir also zunächst zwei neue Felder an, die HTMLAuszeichnungStart und HTMLAuszeichnungEnde heißen. Diese füllen wir dann beispielsweise für das Absatzformat h1 mit den Werten <div><font size=4> und </font></ div>. Aber reicht dies aus? Nein: Es gibt ein paar verschachtelte Formate, zum Beispiel die Auflistungen mit und ohne Nummerierungen. Wenn Sie eine solche mit HTML-Tags im Richtext abbilden möchten, fassen Sie nicht nur die einzelnen Absätze in <li> und </li>-tags ein. Sie müssen auch noch die zusammengehörenden Auflistungspunkte entweder mit <ol> und </ol> (Auflistung ohne Nummerierung) beziehungsweise <ul> und </ul> einfassen (Auflistung mit Nummerierung). Für eine Auflistung mit Nummerierung sieht das beispielsweise wie folgt aus: <ul> <li>erster Auflistungspunkt</li> <li>zweiter Auflistungspunkt</li> </ul> Für diese absatzübergreifenden Markierungen fügen wir der Tabelle tblabsatzformate also zwei weitere Felder namens HTMLAuszeichnungStartExtra und HTMLAuszeichnungEndeExtra hinzu. Die Tabelle sieht dann, mit den für unser Beispieldokument notwendigen Absätzen, wie in Bild 8 aus. Richtext erstellen Nun wollen wir noch den Inhalt der drei Tabellen in ein Dokument im Richtext- Format von Access umwandeln. Dies erledigt die Prozedur RichtextErstellen, die als einzigen Parameter den Primärschlüssel des Datensatzes der Tabelle tbldokumente erwartet (s. Listing 5). Die Prozedur öffnet dann zunächst ein Recordset auf Basis der Abfrage qry- AbsaetzeHTML, welche die benötigten Informationen zusammenfasst. Den Entwurf dieser Abfrage finden Sie in Bild 9. Bild 9: Diese Abfrage liefert alle zur Erstellung eines Richtext-Dokuments nötigen Informationen Seite 42

45 INTERAKTIV DATENBANKEXPORT NACH MASS Public Function RichTextErstellen(lngDokumentID As Long) As String Dim db As DAO.Database Dim rst As DAO.Recordset Dim strtext As String Dim strformatvorher As String Dim strextraauszeichnungvorherigerabsatz As String Set db = CurrentDb Set rst = db.openrecordset("select * FROM qryabsaetzehtml WHERE DokumentID = " & lngdokumentid, dbopendynaset) Do While Not rst.eof If Len(rst!HTMLAuszeichnungStartExtra) > 0 Then If Not rst!absatzformat = strformatvorher Then If Len(strExtraAuszeichnungVorherigerAbsatz) > 0 Then strtext = strtext & strextraauszeichnungvorherigerabsatz & vbcrlf strtext = strtext & rst!htmlauszeichnungstartextra & vbcrlf strextraauszeichnungvorherigerabsatz = rst!htmlauszeichnungendeextra & vbcrlf Else If Not Len(strExtraAuszeichnungVorherigerAbsatz) = 0 Then strtext = strtext & strextraauszeichnungvorherigerabsatz strextraauszeichnungvorherigerabsatz = "" strtext = strtext & rst!htmlauszeichnungstart strtext = strtext & Replace(Replace(rst!Inhalt, vbcr, ""), vblf, "") strtext = strtext & rst!htmlauszeichnungende & vbcrlf strformatvorher = rst!absatzformat rst.movenext Loop If Len(strExtraAuszeichnungVorherigerAbsatz) > 0 Then strtext = strtext & strextraauszeichnungvorherigerabsatz & vbcrlf Set db = Nothing RichTextErstellen = strtext End Function Listing 5: Erstellen eines Dokuments im Richtext-Format von Access Dann durchläuft die Prozedur alle Datensätze dieser Abfrage in einer Do While-Schleife. Der Inhalt dieser Do While-Schleife wäre einfach gestrickt, wenn die Struktur eines Richtext-Dokuments lediglich aus aufeinanderfolgenden Absätzen bestehen würde. Leider müssen Aufzählungen aber in die bereits weiter oben erwähnten <ol> und </ ol> beziehungsweise <ul> und </ul>- Tags eingeschlossen werden. Deshalb enthält die Do While-Schleife einige Bedingungen, die prüfen, ob gerade das erste Element einer solchen Auflistung behandelt wird und dementsprechend das umfassende HTML-Element vorangestellt werden muss oder ob es sich um das letzte Elemente einer Auflistung handelt dies würde das Anhängen des schließenden Auflistungs-Tags nach sich ziehen. Außerdem verwenden wir ein paar Hilfsvariablen, die über den jeweiligen Status Auskunft erteilen. Beginnen wir mit der ersten If... Then-Bedingung: Diese prüft, ob das Feld HTMLAuszeichnungStartExtra des aktuellen Datensatzes einen Text enthält, was darauf hindeutet, dass es sich um ein Auflistungselement handelt, Seite 43

46 INTERAKTIV DATENBANKEXPORT NACH MASS dem gegebenenfalls ein öffnendes oder schließendes Auflistungstag vorangestellt oder angefügt werden muss. Im einfachsten Fall liefert diese If... Then-Bedingung den Wert False. Dann folgt der Else-Teil der Bedingung. Dort prüft die Prozedur, ob die Variable strextraauszeichnungvorherigerabsatz einen Wert enthält was nur der Fall ist, wenn der vorherige Absatz ein Auflistungsabsatz war. In diesem Fall gehen wir nicht davon aus, weil es sich beispielsweise um den ersten Absatz überhaupt handelt. Dann beginnt die Prozedur mit der Zusammenstellung des Richtext-Textes in der Variablen strtext, indem sie den HTML-Tag aus dem Feld HTMLAuszeichnungStart des aktuellen Datensatzes hinzufügt. Dann folgt der Inhalt des Absatzes aus dem Feld Inhalt und schließlich noch das schließende HTML- Tag aus dem Feld HTMLAuszeichnung- Ende. Nach dem ersten Durchlauf der Do While-Schleife könnte strtext also beispielsweise den folgenden Text enthalten: <div><font size=4>hauptüberschrift </font></div> Das Format dieses Absatzes aus dem Feld Absatzformat wird nun in der Variablen strformatvorher gespeichert. Erstes Auflistungselement Schauen wir uns den Fall an, dass es sich um das erste einer Reihe von Auflistungselementen handelt. In diesem Fall ist die äußere If...Then-Bedingung wahr. Die nächste If...Then-Bedingung prüft, ob das aktuelle Absatzformat nicht mit dem des vorherigen Datensatzes übereinstimmt. Dies ist hier der Fall, also folgt auch noch die Prüfung der innersten If...Then-Bedingung. Diese prüft anhand der Variablen strextra- AuszeichnungVorherigerDatensatz, ob bereits der vorherige Datensatz ein Auflistungselement war. Dies ist hier nicht der Fall, also wird direkt die zweite Anweisung der mittleren If...Then- Bedingung ausgeführt. Diese fügt strtext das HTML-Tag für die komplette Auflistung hinzu, also beispielsweise <ol> oder <ul>. Außerdem wird die Variable strauszeichnungvorherigerdatensatz mit der entsprechenden abschließenden Auszeichung gefüllt, also etwa </ol> oder </ul>. Auch hier folgt nun das Anfügen der eigentlichen HTML-Tags, sodass dieser Teil etwa so aussieht: <ul> <li>nummerierungspunkt 1</li> Nun folgt entweder ein weiterer Auflistungspunkt oder ein Element eines anderen Absatzformats. Bei einem weiteren Auflistungspunkt gleichen Absatzformats ist die erste If...Then-Bedingung erfüllt, die zweite aber bereits nicht mehr, denn es folgt ja nun ein Absatz mit dem gleichen Absatzformat wie der vorherige Absatz. Hier wird also wiederum nur das schließende Element der Auflistung in der Variablen strextraauszeichnungvorherigerabsatz gefüllt. Anschließend folgen die HTML- Tags und der Absatz selbst etwa so: <li>nummerierungspunkt 2</li> Interessanterweise läuft die Prozedur auch beim letzten Auflistungspunkt genauso ab. Nach dem Abarbeiten des letzten Punktes der Auflistung wird also nicht etwa das schließende Auflistungs- Tag angefügt, also beispielsweise </ul>. Stattdessen fährt die Prozedur einfach mit dem folgenden Absatz fort. Hier trifft die Prozedur gleich bei der ersten If...Then-Bedingung wieder auf den Wert True (das Feld HTMLAuszeichnungStartExtra enthält einen Wert und es handelt sich um ein Auflistungselement). Die Prozedur prüft nun weiter, ob das aktuelle Absatzformat dem vorherigen Absatzformat entspricht. Das aktuelle ermitteln wir wieder aus dem Feld Absatzformat des Datensatzes, das vorherige wird immer für den aktuellen Datensatz in der Variablen strformat- Vorher abgespeichert. Schließlich folgt noch eine dritte If...Then-Bedingung, die prüft, ob auch der vorherige Absatz bereits ein Auflistungsabsatz war. Ist dies der Fall, stellen wir fest: Der vorherige Absatz war ein Auflistungsabsatz. Der aktuelle Absatz ist ein Auflistungsabsatz. Der aktuelle Absatz weist ein anderes Absatzformat auf als der vorherige. Dies führt zu der Schlussfolgerung, dass der aktuelle Absatz eine neue Auflistung mit einem anderen Typ als im vorherigen Absatz beginnt also folgt hier beispielsweise eine Auflistung ohne Nummerierung auf eine Auflistung mit Nummerierung. Sprich: Wir ergänzen den aktuellen Text in strtext zunächst um das schließende Element für die vorherige Auflistung. Das fehlende schließende Element der vorherigen Auflistung wird also erst mit dem folgenden Element nachgereicht. Seite 44

47 INTERAKTIV DATENBANKEXPORT NACH MASS Fehlt noch der Fall, bei dem der letzte Absatz ein Auflistungselement ist. In diesem Fall müsste ja eigentlich das schließende Element der Auflistung fehlen. Dem beugen wir schlicht und einfach mit einer weiteren If...Then-Bedingung hinter dem Ende der Do While-Schleife vor. Diese prüft, ob strextraauszeichnungvorherigerabsatz noch einen Wert enthält. In diesem Fall fügen wir dem Inhalt der Variablen noch das in dieser Variablen gespeicherte schließende Element an, also etwa </ol> oder </ul>. Das Ergebnis der Funktion wird schließlich an die aufrufende Funktion zurückgegeben und in diesem Fall in das Feld Richtext des Formulars eingetragen. Das Ergebnis sieht schließlich wie in Bild 10 aus, wo wir das Originaldokument und das Richtext-Dokument nebeneinander gestellt haben. Mit etwas Fingerspitzengefühl lässt sich durch Umgestalten der Tags in der Tabelle tblabsatzformat noch mehr herausholen. Dem Formular frmdokumente haben wir noch eine Schaltfläche namens cmdaktualisieren hinzugefügt, welches das Richtext-Dokument nach Änderungen direkt an den Absätzen oder Absatzformaten im Unterformular aktualisiert. Dieses löst die folgende Prozedur aus: Private Sub cmdaktualisieren_click() Dim strtext As String strtext = _ RichTextErstellen(Me!DokumentID) Me!txtRichtext = strtext Bild 10: Vergleich des Originaldokuments und der Richtext-Version Seite 45

48 SQL SERVER UND CO. SQLITE UND SQLite und Access Ist Ihnen der Begriff SQLite schon einmal untergekommen? Wenn nicht, so ist das nicht weiter verwunderlich, handelt es sich dabei doch um ein Datenbanksystem, das Sie als Access-Benutzer wahrscheinlich nicht brauchen und viele Ähnlichkeiten mit Access aufweist. Wenn doch, so könnte das der Verbreitung von Android-Smartphones und -Tablets geschuldet sein, denn unter Android ist SQLite das Standarddatenbankformat mit Systemunterstützung - die allermeisten Apps verwenden es, wenn größere Datenmengen zu speichern sind. Grund genug, dieses Format einmal genauer unter die Lupe zu nehmen. Was ist SQLite? SQLite gibt es bereits seit dem Jahr Es wird im Kern von drei Programmierern weiterentwickelt, jedoch von einer großen Schar von Unterstützern mitbetreut, da der Quellcode unter Public Domain steht. Einst war es als Datenbanksystem für einige wenige Aufgaben gedacht gewesen. Statt etwa Programmeinstellungen in INI-Dateien unterzubringen, sollte die Möglichkeit bestehen, diese komfortabler in einer Datenbank zu verwalten, ohne dafür extra eine umfangreiche Datenbank-Engine installieren zu müssen. Dieser Grundgedanke das Lite im Namen steht auch heute noch an erster Stelle. Wenn Sie sich auf der Website von sqlite.org [1] die Precompiled Binaries für Windows herunterladen, sind Sie vielleicht erstaunt, dass sich lediglich zwei Dateien im ZIP-Paket befinden. Die eine davon, die Kommandozeilenanwendung sqlite3.exe, reicht dabei schon völlig für die Verwaltung der Datenbankdateien aus. Inzwischen hat sich SQLite gemausert und steht den Möglichkeiten der Access Database Engine (vormals Jet-Engine) kaum nach. Die Größe der Datenbanken kann in die Gigabytes gehen, alle analogen Datentypen sind vorhanden, der SQL-Dialekt ist umfangreicher als der von Access und ähnelt eher dem größerer DBMS, Fremdschlüssel werden unterstützt und Mehrbenutzerzugriffe sind kein Problem. Auch Transaktionen, Views und Trigger fehlen nicht. Inzwischen sind zahlreiche Schnittstellen zu allerlei Programmierumgebungen entstanden. Viele namhafte Software-Firmen haben das Format inzwischen adaptiert und verwenden es auch jenseits Android. Um einige Programme zu nennen, die einen großen Teil ihrer Daten über SQLite verwalten: Firefox, Chrome, Skype, Dropbox, XNView. Um Missverständnisse zu vermeiden: SQLite ist ein Pendant zur Access Database Engine, nicht jedoch zu Access. Es gibt keine grafische Oberfläche wie für den Tabellenentwurf oder die Datenblattansicht unter Access, sondern mit der sqlite3.exe lediglich ein Kommandozeilen-Tool zur Anlage und Abfrage der Datenbanken, oder mit der sqlite3.dll eine Programmierschnittstelle, die per API angesprochen werden muss. Wer eine grafische Verwaltung haben will, der benötigt separate Editoren, von denen allerdings zahlreiche vorhanden sind. Dazu später mehr. Eine SQLite-Datenbank besteht aus nur einer Datei, die üblicherweise die Endung.sqlite oder.db aufweist, so, wie bei Access auch nur eine.mdb- oder.accdb-datei nötig ist. Intern kann die Datei allerdings mehrere Datenbanken enthalten, wovon die eigentlich genutzte meist den Namen main trägt, während die anderen Systemdatenbanken sind. Darunter befinden sich die Tabellen, die relational miteinander verknüpft sein können. Dabei können Fremdschlüsselbeziehungen angelegt oder Beschränkungen (Constraints) definiert werden. Beziehungen zwischen mehreren Datenbankdateien, so wie gelinkte Tabellen unter Access, sind jedoch nicht direkt möglich. Views schließlich dienen der gefilterten Ausgabe der Tabellendaten. SQLite-Views sind im Unterschied zu den meisten DBMS aber nicht aktualisierbar. Eine umfassende Darstellung des Formats und seiner Features kann an dieser Stelle nicht gegeben werden. Schauen Sie sich bei Interesse die durchaus gelungene Dokumentation auf der Seite der Herausgeber [1] an. Betrachten wir eher, was wir unter Access mit SQLite anfangen können. SQLite und Access Wie kommen die Daten einer SQLite-Datenbank in oder zu Access? Eine direkte Verknüpfung ist zunächst nicht möglich, da hierfür der entsprechende ISAM- Treiber fehlt. Per Code könnten Sie die sqlite3.dll ansprechen, benötigten dafür aber API-Deklarationen, die Sie für VB(A) vergeblich suchen werden. Zum Glück ist das aber überflüssig, da Olaf Schmidt mit dem vbrichclient eine kostenlose Schnittstelle zur Verfügung stellt, deren Seite 46

49 SQL SERVER UND CO. SQLITE UND Programmierung fast identisch zu der von DAO oder ADO ist. ODBC Um eine Datenbank eines fremden DBMS direkt in Access zu bekommen, braucht es einen ODBC-Treiber, wie ihn etwa der MS SQL-Server oder MYSQL mitbringen. Auch für SQLite existieren ODBC-Treiber, wobei sich jener von Christian Werner [2] quasi zum Standard entwickelt hat. Andere Treiber sind meist Modifikationen seines Treibers, der im Quellcode veröffentlicht ist. Laden Sie sich also das Installationspaket von dessen Seite herunter und starten das Setup. Danach finden Sie im ODBC- Datenquellen-Administrator von Windows einen neuen Eintrag SQLite3 ODBC Driver auf der Treiberseite, wie in Bild 1. Den Datenquellen-Administrator finden Sie in den neueren Windows-Versionen über Startmenü Verwaltung Datenquellen (ODBC). Bild 1: Der ODBC-Datenquellen-Administrator von Windows mit der Liste installierter Treiber Bild 2: Konfiguration einer SQLite-ODBC-Verbindung (DSN) Um anschließend eine neue Datenquelle (DSN) zu einer SQLite-Datenbank im Administrator anzulegen, öffnen Sie die Seite Benutzer-DSN, klicken Hinzufügen an und wählen dann den neuen Treibereintrag aus. Ein Klick auf Fertig stellen führt zum Konfigurationsdialog des Treibers (s. Bild 2). Die Voreinstellungen des Dialogs können Sie zunächst getrost übernehmen. Essentiell sind jedoch zwei Einträge: Unter Database Name geben Sie den Pfad zur SQLite- Datei an, den Sie auch über die Schaltfläche Browse ermitteln können. Was dann passiert, hängt aber vom Kontrollkästchen Don't create Database ab. Ist hier kein Häkchen gesetzt, so wird nach dem Klick auf OK automatisch eine leere SQLite-Datei erstellt, falls die angegebene Datei noch nicht existiert. Ansonsten wird eine Verbindung zur bestehenden Datei hergestellt. Ist das Häkchen gesetzt, so wird keine leere Datei angelegt. Existiert die angegebene dann Datei nicht, so erfolgt beim Verbinden eine Fehlermeldung des Treibers. Lassen Sie für unseren Test, abweichend zur Abbildung, das Häkchen weg. Die wichtigsten weiteren Parameter des Dialogs: No TXN bedeutet keine Transaktionen. Alle SQL- Anweisungen werden sofort ausgeführt, wenn hier ein Häkchen gesetzt ist. Andernfalls können Sie SQL-Anweisungen in BEGIN TRANSACTION.. COMMIT... ROLLBACK... END TRANSACTION-Blöcke einschließen. Foreigns Keys: Fremdschlüsselunterstützung eingeschaltet/ausgeschaltet. Standardmäßig ist dies deaktiviert. Lock Timeout: Hier können Sie einen Wert im Millisekunden angeben, die Zeitspanne, die eine Sperre zur SQLite-Datei maximal andauern darf, bevor der Treiber einen Schreibversuch abbricht. Für lokale Systeme hat dies normal keine Bedeutung. Nachdem Sie den Dialog mit OK bestätigt haben, finden Sie in der Liste der Benutzerdatenquellen den Eintrag, den Sie im Dialog unter Data Source Name vergeben hatten. Import/Export Schreiten Sie nun zur Tat und öffnen eine beliebige Access-Datenbank, etwa die Demodatenbank sqlite_demo.mdb zu diesem Beitrag. Markieren Sie eine Tabelle, in unserem Test die Tabelle tbl- Seite 47

50 SQL SERVER UND CO. SQLITE UND klicken Sie auf OK schon finden Sie die per ODBC verknüpfte Tabelle im Datenbankfenster oder Navigationsbereich vor. Bild 3: Originale Testtabelle unter Access und das ODBC-verknüpfte Pendant aus SQLite rechts im Entwurf Nun wird es spannend: was ist beim Export und Import aus den Feldern der ursprünglichen Tabelle geworden? Die tbltest enthält alle Feldtypen, die bis Access 2007 möglich waren. Öffnen Sie die Tabellen tbltest und tbltest2 parallel im Entwurf, sodass Sie das Schema vergleichen können siehe Bild 3. Test, und gehen Sie dann unter Access 2003 und früher auf Datei Exportieren, wählen als Dateityp ODBC-Datenbanken() und geben einen neuen Namen für die zu exportierende Tabelle an. Verwenden Sie hier tbltest2. Unter Access 2007 und neuer öffnen Sie stattdessen das Kontextmenü der Tabelle, gehen auf Exportieren ODBC- Datenbank. Anschließend öffnet sich der Datenquellendialog, in dem Sie unter Computerdatenquellen die zuvor angelegte SQLite-Verbindung auswählen. Im Normalfall sollte die Aktion nun erfolgreich quittiert werden schon haben Sie eine Access-Tabelle nach SQLite transferiert. Interessant ist, dass dies selbst bei Tabellen mit Anlagefeldern fehlerfrei funktioniert versuchen Sie es etwa mit der versteckten Systemtabelle MsysResources unter Access Allerdings werden Sie anschließend mit dem Anlagefeld, welches als Memofeld in die SQLite-Datei exportiert wurde, nicht viel anfangen können. Dort steht dann nur der Name der Anlage, nicht aber deren Binärdaten. Nachdem die Tabelle exportiert wurde, kommt jetzt der umgekehrte Vorgang. Wir möchten die Tabelle fürs Erste aber nicht reimportieren, sondern eine Verknüpfung zu ihr anlegen. Dazu gehen Sie unter Access 2003 auf Datei Externe Daten Tabellen verknüpfen und wählen wieder ODBC-Datenbanken(). Unter Access 2007 und neuer wählen Sie den Ribbon-Eintrag Externe Daten ODBC-Datenbank Verknüpfen. Nach Auswahl der SQLite-Verbindung sollten Sie mit einem Dialog konfrontiert werden, der die enthaltenen Tabellen anzeigt. Lief im vorherigen Schritt alles glatt, dann sollte hier die Tabelle tbltest2 aufgeführt sein. Markieren Sie diese und Tatsächlich sind die Felddefinitionen bis auf einige Ausnahmen identisch. Das ID-Feld wurde mit Long-Wert korrekt als Primärschlüssel übernommen. Der Autowert funktioniert ebenfalls, auch wenn er im Entwurf so nicht angezeigt wird. Abweichungen gibt es bei der GUID, die unter Access als Zahl/Replikations-ID definiert ist. In SQLite wurde diese als Binärfeld angelegt, was im Entwurf durch den Feldtyp OLE-Objekt ersichtlich wird. Das ist sicherlich tragbar, wird dieser Feldtyp doch sehr selten verwendet. Bei Dezimalfeld kam es zur Konvertierung in ein Memo-Feld, also ein Textfeld mit mehr als 255 Zeichen. Auch das ist zu verschmerzen, da der Dezimaltyp ohnehin weitere Angaben zur Genauigkeit und zur Kommaposition braucht. Der Single-Type kommt als Double zurück, was ebenfalls akzeptiert wird. Weniger Toleranz sollte dagegen die Wandlung von Währung in Memo Seite 48

51 SQL SERVER UND CO. SQLITE UND Bild 4: Anzeige der Daten aus originaler und verknüpfter Tabelle erfahren. Zwar ist der Sachverhalt hier ähnlich, wie beim Dezimalfeld, jedoch lässt sich mit einem Memofeld schlecht rechnen. Zur Erläuterung finden Sie im Absatz zu den SQLite-Datentypen weitere Informationen. Dass schließlich der proprietäre Typ Hyperlink nach Memo umgewandelt wird, ist nicht erstaunlich. Was außerdem fehlt, sind Standardwerte, die für Felder unter Access vergeben wurden. Zusammenfassend lässt sich feststellen, dass der Export/Import-Vorgang zwar brauchbar ist, jedoch verbesserungswürdig. Dazu kommen zwei Lösungen infrage: entweder Sie modifizieren die SQLite-Felder nach Export mit einem SQLite-Editor Ihrer Wahl, oder Sie legen die Tabellen gleich per VBA-Code als DDL-SQL-Anweisungen selbst an. Dazu finden sie in der Demodatenbank einige Routinen, die später gesondert besprochen werden. Machen wir als Nächstes den Test, was aus den Feldinhalten geworden ist. Öffnen Sie dazu die beiden Tabellen übereinander, wie in Bild 4. Mit dem Ergebnis kann man zufrieden sein. Allerdings schleichen sich beim Single-Feld offenbar Rundungsfehler ein. Dasselbe gilt übrigens für alle Fließkommazahlentypen, auch wenn das aus der Abbildung nicht hervorgeht. Es betrifft also alle Felder vom Type Single, Double und Currency. Erfreulich ist, dass offenbar auch das OLE- Objekt korrekt übernommen wurde: ein Doppelklick auf die Zelle öffnet das Icon, das in ihm verborgen ist. Wenn Sie mit den Datensätzen der verknüpften SQLite-Tabelle spielen, werden Sie bald feststellen, dass es zu seltsamen Fehlermeldungen kommt, ohne dass diese so einfach nachzuvollziehen wären. Einmal funktioniert das Editieren nicht, dann doch, und das Löschen der Datensätze scheint ebenfalls dem Zufallsprinzip zu unterliegen. Meist erscheint die Fehlermeldung aus Bild 5. Wer ist der andere Benutzer, der dieselben Daten verändern will, und das gar beim Löschen? Die Begründung für diese missverständliche Fehlermeldung liegt in den Untiefen der Access Database Engine verborgen. Ich ließ einen ODBC-Trace während des Löschvorgangs loggen. Ein ODBC-Trace ist ein genaues Protokoll der Vorgänge Seite 49

52 SQL SERVER UND CO. SQLITE UND Bild 5: Fehlermeldung beim Löschen von Datensätzen in der ODBC-verknüpften Tabelle zwischen Client und Server, hier also Access und dem SQLite-Treiber. Es lässt sich im ODBC-Administrator über Aktivierung der Ablaufverfolgung anlegen. Eine Analyse der Logs ergab, dass Access beim Löschen und Editieren den entsprechenden Datensatz über eine Kombination fast aller Felder zu ermitteln versucht, statt den Primärschlüssel heranzuziehen, warum auch immer. Die entscheidende Zeile im Protokoll sieht so aus: DELETE FROM "tbltest2" WHERE "ID" = AND "TextFeld" = 'Sascha Trowitzsch' AND "LongFeld" = 1 AND "IntFeld" = 1 AND "DoubleFeld" = 1.2 AND "SingleFeld" = AND "ByteFeld" = 5 AND "BoolFeld" = AND "DateFeld" = {ts ' :44:00'} Da aber die Rundungsfehler beim Double- und Singlefeld dazu führen, dass Abweichungen zu den Werten entstehen, die Access in der Tabellenansicht gespeichert hat, geht Access davon aus, dass die entsprechenden Daten mittlerweile von anderer Seite geändert wurden, und hält den Löschvorgang an. Wir kommen deshalb nicht umhin, uns mit den SQLite-Datentypen zu beschäftigen. SQLite Datentypen Zwar können unter SQLite zahlreiche Feldtypen definiert und für diese wiederum diverse Parameter eingestellt werden, doch intern gibt es lediglich vier sogenannte Storage-Klassen. Das sind Integer, Real, Text und Blob. Real ist dabei ein Fließkommawert mit 8 Bytes. Das bedeutet, dass jeglicher Datentyp auf einen dieser Grundtypen gecastet werden muss. SQLite nimmt diese Konvertierung selbst vor, wobei die eigentliche Felddefinition herangezogen wird. Genaueres dazu erfahren Sie auf der Seite zu Data Types auf sqlite.org. Dort finden Sie eine Zuordnungsmatrix der Feldtypen zu den Grundklassen. Diesen Umstand zu kennen, ist zweifellos wichtig, wenn es um die Interaktion von Access mit SQLite geht. Es leiten sich daraus einige Regeln ab, die zu beherzigen sind. Vermeiden Sie die Typen Single und Währung. Verwenden Sie stattdessen Double. Wenn die Genauigkeit von Double für Währungsberechnungen nicht ausreichen sollte, so benutzen Sie Text (varchar)! Unter Access werden Sie dann allerdings zusätzliche Routinen zur Konvertierung von Text in Currency unterbringen oder Abfragen umgestalten müssen. Nehmen Sie für die numerischen Typen Decimal und Numeric ebenfalls Text. Boolean, also Ja/Nein-Feld, kann bleiben, wie es ist. Datumstypen definieren Sie unter SQLite entweder als DateTime oder Timestamp. Versehen Sie jede Tabelle mit einem Primärschlüssel. Außerdem ist es, wie bei anderen DBMS nützlich, ihr ein zusätzliches indiziertes Timestamp-Feld zu spendieren, wie einige Programmierer berichten. Das Timestamp-Feld muss mit dem Default-Wert CURRENT_TIMESTAMP versehen sein. Größenangaben für Textfelder interessieren SQLite intern nicht. In einem Textfeld können beliebig viele Zeichen abgespeichert werden. Die Definition ist aber für Access wichtig, denn als varchar(255) definierte Felder werden von Access als Text verstanden, größere als Memo. Letzteres gilt auch für nur als varchar angelegte Felder. Alles in allem ist die Verbindung Access- SQLite nicht ganz so trivial, wie die zu anderen DBMS. Die Tabellen einer SQLite-Datenbank einfach verknüpfen und die Daten unter Access in Datenblättern und Formularen verwalten, reicht nicht aus. Der ODBC-Treiber ist nicht ganz konform mit der Access Database Engine. Um zusätzliche Programmierung und Workarounds kommen Sie nicht herum, wenn Sie die Dateninteg- Seite 50

53 SQL SERVER UND CO. SQLITE UND rität sicherstellen wollen. Beispiel: Das Löschen eines Datensatzes, was in der Datenblattansicht schiefgeht, lässt sich problemlos mit folgender Code-Zeile bewerkstelligen: CurrentDb.Execute "DELETE FROM µ tbltest WHERE ID=2" Programmierung Im Vorhergehenden hatten Sie die SQLite-Tabellen manuell mit Access verknüpft. Das geht jedoch auch per Code, und zwar mit der Anweisung DoCmd. TransferDatabase. Dieser können Sie als Parameter den ODBC-Connection- String übergeben, statt eine angelegte DSN zu verwenden. Alle Versuche dieser Art schlagen aber fehl. Access quittiert die Anweisung mit der Fehlermeldung -7778, Es gibt keine Meldung für diesen Fehler, obwohl der Treiber geladen wird. Sie können das im Modul mdls- QLiteODBC, Prozedur LinkSQLiteTable der Demo-Datenbank nachstellen. Die Lösung ist, zunächst eine manuelle Verknüpfung anzulegen und später beim Aktualisieren der Verknüpfung über das TableDef-Objekt nur den Connect- String zu ändern: sconx = "ODBC;Driver={SQLite3 ODBC Driver}; Database=c:\irgendwas\test_sqlite. db;" CurrentDb.TableDefs("tblTest2").Connect = sconx CurrentDb.TableDefs.Refresh ADO Aus der Sicht des Access-Programmierers sind die Möglichkeiten beschränkt, wenn es um ODBC-Datenbanken unter DAO geht. Konnte man unter Access 2003 noch über das Connection-Objekt von DAO aus direkten Zugriff auf ODBC- Objekte erhalten, so wie das Pass- Through-Abfragen ermöglichen, scheidet diese Methode seit Access 2007 aus. In der Access Database Engine wurde das Connection-Objekt gestrichen. Daher ist nun ADODB das Mittel der Wahl Für eine Verbindung per ADODB ist ein entsprechender ADO-Provider nötig. Es existieren zwar native SQLite-Provider, doch sind diese entweder nicht kompatibel mit SQLite 3 oder sie arbeiten wenig vertrauenswürdig. Deshalb verwenden Sie besser den ODBC-Provider, der wiederum auf unseren ODBC-Treiber von C. Werner aufsetzt. Ein rudimentärer ConnectionString für ein ADO-Connection-Objekt sieht so aus: "Provider=MSDASQL.1;Driver={SQLite3 ODBC Driver};Version=3;Database= c:\irgendwas\test_sqlite.db;" Die Angaben sind der ADO-Provider für ODBC, der SQLite-ODBC-Treiber, die SQLite-Version und der Pfad zur SQLite-Datei. Nachdem die Verbindung über Connection.Open zustande kam, können Sie über Connection.Execute beliebige SQL-Anweisungen absetzen: oconnection.execute "CREATE TABLE tbl- Test2 (...)" oconnection.execute "VACUUM;" Über die erste Anweisung wird eine SQLite-Tabelle erzeugt. Mit der zweiten wird die SQLite-Datei komprimiert. Das ist übrigens exakt der gleiche Vorgang, den Sie von Access und dem "Komprimieren und Reparieren"-Befehl her kennen. Auch SQLite-Dateien können sich nach umfangreichen Datenoperationen aufblähen. vbrichclient Der rührige Olaf Schmidt stellt mit seiner Bibliothek vbrichclient5 eine Freeware-Lösung [3] für den Umgang mit SQLite-Dateien zur Verfügung. Die Bibliothek enthält weitaus mehr als nur Datenbankobjekte. Wir verwenden sie jedoch nur dafür. Es handelt sich um eine ActiveX-DLL, die registriert und in die Verweise der Access-Datenbank gelinkt werden muss, um Objekte wie cconnection, crecordset, ccommand verwenden zu können, die allesamt ähnlich angesprochen werden, wie die ADO-Pendants. Im gleichen Verzeichnis der DLL sollte sich zudem die neueste Version der sqlite3.dll befinden. Der Vorteil dieser Lösung ist, dass sich über die Methoden und Properties dieser Library vieles ansprechen und einstellen lässt, was über ADO nur über umständliche SQL-Anweisungen möglich ist. Schauen Sie sich etwa die Liste der Funktionen des CConnection-Objekts im Objektkatalog von VBA an. Sie können da etwa die Grundeinstellungen einer neu erzeugten SQLite-Datenbank vorgeben, Transaktionen steuern, Datentypen umwandeln oder den zuletzt vergebenen Autowert ermitteln lassen. Sollten Sie in ihrer Systemumgebung nicht die Möglichkeit haben, ActiveX- Bibliotheken registrieren zu lassen, so bietet Olaf Schmidt mit seiner DirectCom-Schnittstelle eine Lösung an, um an die Bibliothek auch über eine Handvoll API-Deklarationen heranzukommen. Die Darstellung dieser Lösung Seite 51

54 SQL SERVER UND CO. SQLITE UND kann im Rahmen dieses Beitrags leider nicht erfolgen. SQLite-Editoren Man kann über die Oberfläche von Access keine Änderungen an SQLite- Datenbanken vornehmen. Über SQL- Anweisungen und den Zugriff über ADO oder den vbrichclient geht es zwar, es ist jedoch alles andere als komfortabel. Deshalb sollten Sie sich einen der Freeware-Editoren zulegen, die über die Links [4] bis [10] zu finden sind. Viele dieser Editoren sind in ihrem Umfang mit den Verwaltungsoberflächen anderer DBMS zu vergleichen. Mein Favorit ist derzeit der SQLite Expert [9] wegen seiner aufgeräumten Oberfläche und der Möglichkeit, mehrere Datenbanken parallel zu bearbeiten. Aber auch das Firefox Addon (!) SQLite Manager [10] macht eine gute Figur. Mit diesem können Sie zudem direkten Einblick in die Systemtabellen des Firefox erhalten. Smartphones zusätzliche Bedeutung. Da es bisher keine wirklich gute App für die Anzeige von Access-Datenbanken gibt, ist der Weg über einen SQLite-Export ein geeignetes Mittel. Umgekehrt werden Sie überrascht sein, wie viele SQLite-Datenbanken sich auf Ihrem Gerät befinden. Machen Sie etwa ein Komplett-Backup des Geräts auf Ihren Rechner über Software wie das Samsung-Kies oder den MyPhoneExplorer und lassen Sie die Routine FindSQLite-Files aus der Demo-Datenbank darüber laufen, auf die wir noch zu sprechen kommen. Ob Bildverwaltungen, Music Player, GPS- Anwendungen: fast alle speichern ihre Daten in SQLite-Datenbanken. Um Inhalte von SQLite-Datenbanken auf Android-Geräten anzuzeigen oder gar zu verwalten, bedienen Sie sich einer der Apps, die beispielhaft unter [11]-[14] aufgeführt sind. Mein Favorit hier ist der asqlitemanager Lite, sowohl von der Bedienung her, wie von den Features. Die Beispieldatenbank Sie finden in der Demo-Datenbank sqlite_demo.mdb einige Module, die den Zugriff auf SQLite-Datenbanken verdeutlichen. An dieser Stelle können sie nur kurz umrissen werden. Vertiefen Sie sich bei Interesse in den Code. Im Modul mdlsqliteodbc finden Sie eine Routine, die über ADO und ODBC unsere Tabelle tbltest2 korrekt erstellt (TestCreateSQLiteODBC). Außerdem eine, die den richtigen Connection- String für die ODBC-Verbindung produziert (ConnectionStringSQLite). Und weiter eine Prozedur, die den Link zu einer SQLite-Datei aktualisiert, und eine, die den Aufbau der Datenbank über die ADO-Methode OpenSchema ausgibt (TestSQLiteSchema). Im Modul mdlsqliterichclient finden sich ähnliche Prozeduren, nur dass Im Download zum Beitrag befindet sich außerdem ein einfacher SQLite-Editor, der von der Demo-Datenbank direkt aufgerufen wird, der sqlite3explorer. Es handelt sich bei ihm um lediglich eine Exe-Datei, die keine Installation erfordert. SQLite und Android Dass SQLite unter Android das Standarddatenbankformat ist, wurde bereits erwähnt. Insofern bekommt der Austausch von Daten zwischen Access und Android- Bild 6: Demo-Datenbank: Formular mit der Liste aller gefundenen SQLite-Datenbanken unter AppData Seite 52

55 SQL SERVER UND CO. SQLITE UND schon öffnet sich der SQLite3Explorer mit den Infos zur SQLite-Datei s. Bild 7. Die zweite Anwendung der Beispieldatenbank finden Sie über den Aufruf des Formulars frmbookmarks. Hier werden Sie zunächst mit einer leeren Oberfläche konfrontiert. Sollten Sie Firefox nicht installiert haben, dann können Sie das Formular nun wieder schließen, denn es geht in ihm darum, die Lesezeichen des Browsers zu ermitteln. Bild 7: Aufruf des SQLite3Explorers durch Doppelklick auf Eintrag im Formular diese über die Bibliothek vbrichclient abgewickelt werden. Eine bereits angesprochene Lösung enthält das Modul mdltest. Rufen Sie hier die Prozedur FindSQLiteFiles über das VBA-Direktfenster auf. Sie durchläuft alle Dateien im Verzeichnis der Wahl inklusive der Unterverzeichnisse und findet heraus, bei welchen davon es sich um SQLite-Datenbanken handelt. Vor allem im Benutzerprofilverzeichnis App- Data werden Sie fündig werden. SQLite-Dateien sind sehr einfach zu identifizieren: Am Anfang der Datei steht binär einfach der String "SQLite format 3". Beim Durchlaufen der Dateien werden alle Fundstellen in der Tabelle tblsamplefiles abgespeichert, die am Schluss über das Formular frmsamplefiles zur Ansicht gelangen, wie in Bild 6. Der Clou am Formular: Doppelklicken Sie einfach auf einen Pfadeintrag, und Bild 8: Auswahl des Firefox-Profils vor dem Import im Formular frmfirefox- Bookmarks Nach Klick auf den Button oben öffnet sich erst ein zusätzliches Formular, das die im Benutzersystem gefundenen Firefox-Profile zur Auswahl anbietet (s. Bild 8). Diese Profile ergeben sich aus der Verzeichnisstruktur unter (AppData)\Mozilla\Firefox\Profiles. Der eigentliche Vorgang läuft dann nach Bestätigen des Dialogs mit OK ab. Die SQLite-Datei places. sqlite im Profilverzeichnis wird geöffnet und die drei Tabellen moz_bookmarks, moz_places und moz_ favicons zum Aufbau des Lesezeichenbaums herangezogen, um anschließend das Treeview des Formulars zu befüllen. Dabei kommt die vbrichclient-bibliothek im Modul mdlfirefox zum Einsatz. Selbst die Seite 53

56 SQL SERVER UND CO. SQLITE UND Icons der Lesezeichen werden generiert, wobei dies ohne Umweg über das Dateisystem über unser viel genutztes Modul mdlgdiplus und dessen Funktion ArrayToPicture geschieht. Sie können nach Abschluss den Root-Node des Treeviews ausklappen und auf einen Eintrag des Lesezeichenbaums klicken, um die Website im rechts daneben befindlichen Webbrowser-Control aufzurufen (s. Bild 9). Links [1] SQLite Home: [2] SQLite ODBC Treiber: ch-werner.de/sqliteodbc/ [3] Nativer Zugriff über vbrichclient 5: htm Editoren: [4] Sqlite3Explorer: gr/sqlite/ [5] SQLiteSpy: [6] SQLite2009 Pro Manager: Nicht mehr vom Autor verfügbar > Googlen! [7] SQLite Administrator: [8] SQLite Database Browser: github.com/sqlitebrowser/sqlitebrowser/ releases [9] SQLite Expert Personal Edition: [10] SQLite Manager, Firefox Addon: addon/sqlitemanager/ Android / Google Play Store: google.com/store/apps [11] asqlitemanager [12] SQLiteMagic [13] SQLiteViewer [14] DB Browser Bild 9: Darstellung der Bookmarks im Treeview nach Import aus Firefox und Aufruf einer Website per Mausklick Seite 54

57 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS Lookup-Daten: Aus zwei mach eins Unter Lookup-Daten verstehen wir beispielsweise die Kategorien, denen ein Artikel zugeordnet werden kann, oder auch Anreden also Daten, die zur Vermeidung von Redundanzen in eine eigene Tabelle ausgelagert und anschließend wieder verknüpft wurden. Nun enthalten solche Tabellen, gerade wenn sie von anderswo importiert oder unsauber gepflegt wurden, nicht immer saubere Daten. Dieser Beitrag zeigt, wie Sie Lookup-Datensätze, die inhaltlich redundant sind, zusammenführen und dabei auch die damit verknüpften Daten berücksichtigen. Die Lösung kann aber auch genutzt werden, um beliebige Lookup-Datensätze zusammenzuführen. Es gibt mindestens zwei Gründe, die dazu führen, dass man seine Lookup- Tabellen aufräumen möchte: Die in der Lookup-Tabelle enthaltenen Daten sind redundant. Das kann entweder gleich beim Importieren der Daten aus einer bestehenden Datenquelle wie einer Exceltabelle oder auch einer anderen Datenbank geschehen oder auch zur Laufzeit. Gerade wenn Benutzer selbst Datensätze in Lookup-Tabellen anlegen und diesen die Datensätze der Detailtabelle zuweisen können, entsteht eine Reihe von Kategorien, die vermutlich besser zusammengefasst werden könnten. Ein weiterer Grund für das Zusammenführen von Lookup-Datensätzen ist, dass einfach eine Umstrukturierung der dort enthaltenen Daten nötig ist. Vielleicht möchte man beispielsweise seine Artikel nicht mehr nach allzu vielen Kategorien aufteilen. Dann überführt man entweder ein oder mehrere Datensätze der Lookup- Tabelle in einen anderen bestehenden Datensatz dieser Tabelle und passt dabei auch gleich die Inhalte der Fremdschlüsselfelder der verknüpften Tabelle an. Ein Beispiel für eine Lookup-Tabelle ist eine Tabelle mit Kategorien etwa namens tblkategorien, die als Lookup-Tabelle für eine Detailtabelle namens tblartikel dient. Über ein Fremdschlüsselfeld wählt man für jeden Artikel eine Kategorie aus der Tabelle tblkategorien aus und teilt die Artikel so in Kategorien ein. Bild 1: Lookup-Tabellen anpassen in zwei Schritten Seite 55

58 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS Wenn dort nun beispielsweise einige Artikel vorliegen, die aktuell einer Kategorie Alkohol. Getränke zugewiesen sind, kann es sein, dass der Benutzer die Kategorie nicht mehr benötigt und die enthaltenen Datensätze lieber der Kategorie Getränke zuweisen möchte. In diesem Fall sind gleich einige Schritte nötig (siehe auch Bild 1): Zuerst muss man für alle Datensätze der Tabelle tblartikel den Wert des Fremdschlüsselfeldes KategorieID auf die neue Zielkategorie einstellen, hier also Getränke. Erst dann löscht man die Kategorie Alkohol. Getränke, sofern diese definitiv nicht mehr benötigt wird. Der zweite dieser beiden Schritte sollte durch die entsprechende Definition der Beziehung zwischen den beiden Tabellen abgesichert werden. In diesem Fall definieren Sie die Beziehung mit referenzieller Integrität (s. Bild 2). Dadurch ist sichergestellt, dass der Benutzer nicht versehentlich eine Kategorie löscht, die noch mit einem der Artikel-Datensätze verknüpft ist. Wenn Sie bei dieser Konstellation versuchen, einen Datensatz aus einer Lookup- Tabelle wie tblkategorien zu löschen, während noch ein Datensatz der Tabelle tblartikel über das Fremdschlüsselfeld KategorieID auf diesen Datensatz verweist, erscheint eine entsprechende Meldung (s. Bild 3). Sie sollten in den Beziehungseigenschaften für die Beziehung zwischen den beiden Tabellen tblartikel und tblkategorien auf keinen Fall die Option Löschweitergabe an verwandte Bild 2: Verknüpfung zwischen Detailtabelle und Lookup-Tabelle Bild 3: Meldung beim Versuch, einen verknüpften Datensatz aus einer Lookup-Tabelle zu löschen Datensätze aktivieren. Wenn Sie bei dieser Einstellung einen der Datensätze der Tabelle tblkategorien löschen, werden auch automatisch alle Datensätze der Tabelle tblartikel gelöscht, die mit diesem Datensatz verknüpft sind zumindest wenn dies nicht noch durch andere mit referenzieller Integrität definierte Beziehungen verhindert wird. Die nachfolgend vorgestellte Lösung soll jedoch ohnehin nur solche Lookup-Daten löschen, die keinem Datensatz einer Detailtabelle mehr zugeordnet sind. Lookup-Daten zusammenführen per Mausklick Das Formular aus Bild 4 enthält alle Steuerelemente, um das Zusammenführen zweier oder mehrerer Lookup- Datensätze mit wenigen Mausklicks durchzuführen. Dazu wählen Sie zunächst mit dem oberen Kombinationsfeld die Lookup- oder Mastertabelle aus in diesem Fall die Tabelle tblkategorien. Dies füllt automatisch die beiden folgenden Kombina- Seite 56

59 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS tionsfelder mit den Namen der Felder dieser Tabelle. Damit wählen Sie dann erst das Primärschlüsselfeld dieser Tabelle aus und dann das Lookup-Feld, also das Feld, dessen Daten etwa in Nachschlagefeldern angezeigt werden. Die beiden darunter befindlichen Listenfelder liefern dann alle Datensätze der ausgewählten Lookup-Tabelle und beide Listenfelder zeigen genau die gleichen Daten an. Das ändert sich allerdings, wenn Sie ein oder mehrere Einträge des linken Listenfeldes auswählen. Dann verschwinden die dort markierten Einträge nämlich aus dem rechten Listenfeld. Der Grund ist einfach: Wir wollen ja kein Lookup-Feld in sich selbst übertragen. Auf der rechten Seite markieren Sie dann die Kategorie, in welche die auf der linken Seite markierten Kategorien überführt werden sollen. Nun kommen wir zu den Daten der Detailtabelle: Das erste Kombinationsfeld mit der Beschriftung Detailtabelle ermöglicht die Auswahl der Tabelle, die mit der Lookup-Tabelle verknüpft ist und deren Daten im Fremdschlüsselfeld an die neue Kategorie angepasst werden sollen. Um die Verknüpfung genauer zu spezifizieren, wählen Sie mit dem folgenden Kombinationsfeld Bild 4: Formular zum Zusammenführen von Lookup-Werten samt Anpassung verknüpfter Datensätze mit der Beschriftung Anzeigefeld ein Feld aus, das den jeweiligen Datensatz beschreibt hier also etwa Artikelname. Diese Auswahl hat nur informativen Charakter, damit Sie in den unteren beiden Listenfeldern erkennen können, welche Datensätze der ausgewählten Tabelle mit den aktuell zum Zusammenführen markierten Kategorien verknüpft sind. Das dritte Kombinationsfeld mit der Beschriftung Fremdschlüsselfeld wird auch tatsächlich zum Überführen der Daten der Detailtabelle zum neuen Datensatz der Lookup-Tabelle benötigt. Damit geben Sie nämlich den Namen des Fremdschlüsselfeldes an, über das die Tabelle mit der Lookup-Tabelle verknüpft ist. Die unteren beiden Listenfelder zeigen nun die Datensätze der Detailtabelle, die mit den Datensätzen der Lookup- Tabellen aus den darüber ausgewählten Listenfeldern verknüpft sind. Nach der Auswahl aller notwendigen Informationen klicken Sie einfach auf die Schaltfläche unten, deren Beschriftung dynamisch an die Einstellungen angepasst wird. Diese Schaltfläche sorgt für folgende Schritte: Einstellen der Fremdschlüsselwerte der Datensätze, die mit den im linken Kombinationsfeld ausgewählten Lookup- Datensätzen verknüpft sind, auf den im rechten Listenfeld ausgewählten Lookup-Datensatz. Löschen der Einträge der Lookup- Tabelle, die im rechten Listenfeld markiert sind. Formular erstellen Schauen wir uns nun an, wie das Formular erstellt wird und wie die enthaltenen Techniken funktionieren. Alle Tabellen im Kombinationsfeld anzeigen Für dieses Formular sind keine Einstellungen erforderlich, die etwa beim Laden durch eine Ereignisprozedur vorgenom- Seite 57

60 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS men werden müssen. Die einzige Vorbereitung ist, dass Sie die Datensatzherkunft des Kombinationsfeldes cbomastertabelle auf eine entsprechende Abfrage einstellen müssen (s. Bild 5). Diese sehen Sie in Bild 6. Diese Abfrage verwendet die Systemtabelle MSysObjects als Datenherkunft und soll nur die Daten des Feldes Name zurückliefern. Das Feld Type dient lediglich zur Auswahl der richtigen Objekte aus dieser Tabelle. Die Typen 1, 4 und 6 liefern alle Tabellenarten von Access, daher verwenden wir als Kriterium den Ausdruck IN (1, 4, 6). Lookup-Tabelle auswählen Wenn der Benutzer einen der Einträge des Kombinationsfeldes cbomastertabelle auswählt, löst dies das Ereignis Nach Aktualisierung aus. Die dafür hinterlegte Ereignisprozedur finden Sie in Listing 1. Die Prozedur stellt zunächst zwei Eigenschaften des Kombinationsfeldes zur Auswahl des Primärschlüsselfeldes Bild 5: Ausstatten des Kombinationsfeldes mit einer Datensatzherkunft Bild 6: Datensatzherkunft für das Kombinationsfeld cbomastertabelle Private Sub cbomastertabelle_afterupdate() With Me!cboPrimaerschluesselfeld.RowSourceType = "Field List".RowSource = Me!cboMastertabelle End With With Me!cboLookupfeld.RowSourceType = "Field List".RowSource = Me!cboMastertabelle End With LookuplisteFuellen LookuplisteErsetzenFuellen Listing 1: Füllen der Steuerelemente nach der Auswahl der Mastertabelle der zuvor bestimmten Tabelle namens cbo- Pri maerschluesselfeld ein. Die erste heißt RowSourceType und legt mit dem Wert Field List fest, dass die Felder einer mit der zweiten Eigenschaft RowSource festgelegten Tabelle als Werte des Kombinationsfeldes angezeigt werden sollen. Diese Eigenschaft stellt die Prozedur logischerweise auf den Namen der soeben ausgewählten Tabelle ein. Damit zeigt das erste Kombinationsfeld schon einmal die Felder der ausgewählten Tabelle an (s. Bild 7). Die folgenden Anweisungen führen die gleichen Schritte für das zweite Kombinationsfeld cbolookupfeld durch. Dieses Kombinationsfeld soll das Feld aufnehmen, dessen Werte die Lookup- Tabelle ausmachen. Schließlich folgt noch der Aufruf zwei weiterer Prozeduren, nämlich LookuplisteFuellen und LookuplisteErsetzenFuellen. Der Aufruf erfolgt in diesem Fall nur, damit die beiden Listenfelder lstlookupwerte und lstlookupwerteersetzen Seite 58

61 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS geleert werden, wenn der Benutzer eine neue Tabelle auswählt wir schauen uns die beiden Prozeduren weiter unten in Detail an. Primärschlüsselfeld und Lookup-Feld auswählen Die beiden folgenden Kombinationsfelder cboprimaer schluessel feld und cbolookup feld sind nun mit den Feldern der im oberen Kombinationsfeld ausgewählten Tabelle gefüllt. Damit können Sie nun festlegen, welches der Felder die Aufgabe des Primärschlüsselfeldes und welches die des Lookupfeldes mit dem angezeigten Feld übernimmt. Die Auswahl dieses Feldes löst die Prozedur aus Listing 2 aus. Auch diese aktualisiert über die beiden Prozeduren LookuplisteFuellen und LookuplisteErsetzenFuellen die beiden darunter befindlichen Listenfelder. Das Kombinationsfeld cbolookupfeld löst nach der Aktualisierung ebenfalls eine Ereignisprozedur aus, welche die Bild 7: Anzeige der Felder der oben ausgewählten Tabelle per Kombinationsfeld Private Sub cboprimaerschluesselfeld_afterupdate() LookuplisteFuellen LookuplisteErsetzenFuellen Listing 2: Aktualisieren der Listenfelder durch cboprimaerschluesselfeld Private Sub cbolookupfeld_afterupdate() LookuplisteFuellen LookuplisteErsetzenFuellen Listing 3: Aktualisieren der Listenfelder durch cbolookupfeld gleichen Anweisungen enthält wie die von cboprimaerschluesselfeld ausgelöste (s. Listing 3). Füllen der Lookup-Liste Damit wird es nun endgültig Zeit, einen Blick auf die Prozedur LookuplisteFuellen zu werfen. Diese ist für das Füllen des linken Listenfeldes zur Anzeige des zu überführendenlookupdatensatzes verantwortlich. Die Prozedur finden Sie in Listing 4. Sie prüft zunächst, ob für jedes der drei oberen Kombinationsfelder ein Eintrag ausgewählt wurde. Falls ja, stellt die Prozedur eine SELECT-Abfrage zusammen, welche die beiden in den Kombinationsfeldern cboprimaerschluesselfeld und cbolookupfeld angegebenen Felder der Tabelle aus cbo- Mastertabelle liefert. Für unser Beispiel sieht der so in der Variablen strsql gespeicherte Ausdruck nun wie folgt aus: SELECT KategorieID, Kategoriename FROM tblkategorien Dieser Abfrage wird nun über die Eigenschaft RowSource als Datensatzherkunft des Listenfeldes lstlookupwerte eingestellt. Sollte eines der drei Kombinationsfelder noch leer sein, leert die Prozedur auch das Listenfeld lstlookupwerte, in dem sie die Eigenschaft RowSource dieses Steuerelements auf eine leere Zeichenkette ("") einstellt. Ermitteln der zu überführenden Lookup-Datensätze Nun wird es interessant: Die Prozedur LookuplisteErsetzenFuellen soll die Private Sub LookuplisteFuellen() Dim strsql As String If Not Nz(Me!cboMastertabelle) = 0 And Not Nz(Me!cboLookupfeld) = 0 And Not Nz(Me!cboPrimaerschluesselfeld) = 0 Then strsql = "SELECT " & Me!cboPrimaerschluesselfeld & ", " & Me!cboLookupfeld & " FROM " & Me!cboMastertabelle Me!lstLookupwerte.RowSource = strsql Else Me!lstLookupwerte.RowSource = "" Listing 4: Füllen der Lookupliste Seite 59

62 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS Einträge des Listenfeldes lstlookupwerteersetzen zusammenstellen (s. Listing 5). Normalerweise sollte man denken, dass man dort auch einfach alle Datensätze der Lookup-Tabelle einträgt. Allerdings sollen ja genau diejenigen Datensätze, die bereits als zu überführende Datensätze im linken Listenfeld markiert wurden, nicht als Ziel-Lookup-Datensätze angeboten werden. Also müssen wir die dortigen Datensätze noch etwas weiter einschränken. Die Prozedur prüft wiederum, ob überhaupt alle drei der obigen Kombinationsfelder einen Wert enthalten. Falls ja, beginnt die Prozedur mit der Zusammenstellung des SQL-Ausdrucks, der als Datensatzherkunft für das Listenfeld dient. Die Basisabfrage sieht wieder genauso aus wie die für das linke Listenfeld. Dann folgt allerdings eine For Each-Schleife, welche alle markierten Datensätze des linken Listenfeldes durchläuft. Diese werden über die Auflistung ItemsSelected des Steuerelements bereitgestellt, welche jeweils den Index der markierten Einträge liefert. Diesen speichern wir mit jedem Schleifendurchlauf in der Variablen var. Wenn wir der ItemData-Eigenschaft des Listenfeldes den Wert von var, also den Index eines der markierten Einträge übergeben, erhalten wir den Wert der gebundenen Spalte zurück. Diesen speichern wir zusammen mit einem führenden Komma in einer String-Variablen namens strlookupwerte. Wenn der Benutzer mehrere Einträge markiert hat, also mehrere Kategorien zu der im rechten Listenfeld markierten Kategorie überführen möchte, entsteht auf diese Weise in strlookupwerte ein Ausdruck wie der folgende:, 1, 4, 8 Nach dem Durchlaufen aller markierten Einträge in der For Each-Schleife prüft die Prozedur dann, ob die in strlookupwerte gespeicherte Zeichenfolge länger als 0 Zeichen ist. Falls ja, schneidet die folgende Anweisung mit der Mid-Funktion die vorderen beiden Zeichen, also das Komma und das Leerzeichen ab, sodass diese Zeichenkette übrig bleibt: 1, 4, 8 Damit stellt die Prozedur dann den Rest der SQL-Abfrage zusammen und fügt diesen zu dem bereits in strsql gespeicherten Basisausdruck hinzu. Das Ergebnis sieht dann etwa wie folgt aus: SELECT KategorieID, Kategoriename FROM tblkategorien WHERE KategorieID NOT IN (1, 4, 8) Diese Abfrage weist die Prozedur dann dem Listenfeld lstlookupwerteersetzen über die Eigenschaft RowSource als Datensatzherkunft zu. Enthalten die zu Beginn der Prozedur geprüften drei Private Sub LookuplisteErsetzenFuellen() Dim strsql As String Dim var As Variant Dim strlookupwerte As String If Not Nz(Me!cboMastertabelle) = 0 And Not Nz(Me!cboLookupfeld) = 0 And Not Nz(Me!cboPrimaerschluesselfeld) = 0 Then strsql = "SELECT " & Me!cboPrimaerschluesselfeld & ", " & Me!cboLookupfeld & " FROM " & Me!cboMastertabelle For Each var In Me!lstLookupwerte.ItemsSelected strlookupwerte = strlookupwerte & ", " & Me!lstLookupwerte.ItemData(var) Next var If Not Len(strLookupwerte) = 0 Then strlookupwerte = Mid(strLookupwerte, 3) strsql = strsql & " WHERE " & Me!cboPrimaerschluesselfeld & " NOT IN (" & strlookupwerte & ")" Me!lstLookupwerteErsetzen.RowSource = strsql Me!lstLookupwerteErsetzen = Null Else Me!lstLookupwerteErsetzen.RowSource = "" Listing 5: Füllen der Listenfelder Seite 60

63 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS Private Sub lstlookupwerte_afterupdate() LookuplisteErsetzenFuellen DetaildatensaetzeFuellen Me!cmdErsetzen.Caption = "Linken Lookupwert durch rechten Lookupwert ersetzen" Listing 6: Aktualisieren der Auswahl im linken Listenfeld Kombinationsfelder nicht alle einen Wert, stellt diese die Datensatzherkunft des Listenfeldes auf eine leere Zeichenkette ein. Zu überführende Lookupwerte markieren Damit Sie überhaupt mehrere Lookup werte im linken Listenfeld namens lstlookupwerte markieren können, haben wir die Eigenschaft Mehrfachauswahl dieses Steuerelements auf den Wert Einzeln eingestellt (s. Bild 8). Je nach Ihren Wünschen können Sie hier auch die Einstellung Erweitert vornehmen. Unabhängig davon, ob der Benutzer nun einen oder mehrere Listenfeldeinträge markiert, wird bei jeder Änderung der markierten Einträge das Ereignis Nach Aktualisierung des Listenfeldes ausgelöst und somit auch die Ereignisprozedur lstlookup werte_afterupdate (s. Listing 6). Diese Prozedur ruft zunächst die weiter oben bereits beschriebene Prozedur LookuplisteErsetzenFuellen auf, um im rechten Listenfeld die Einträge auszublenden, die im linken Listenfeld ausgewählt wurden. Dann ruft sie eine weitere Prozedur namens DetaildateisaetzeFuellen auf, um das linke Listenfeld im unteren Bereich des Formulars mit Daten zu füllen. Dieses soll alle Datensätze der Detailtabelle liefern, welche über das angegebene Fremdschlüsselfeld mit den im Listenfeld ausgewählten Lookup-Werten verknüpft sind. Dies geschieht jedoch nur, wenn die Kombinationsfelder im unteren Bereich bereits einen Wert aufweisen diese legen ja fest, in welcher Weise Bild 8: Vorbereitung des Listenfeldes auf die Mehrfachauswahl von Einträgen die verknüpften Detaildatensätze in den Listenfeldern dargestellt werden sollen. Also schauen wir uns die Prozedur weiter unten an, nach dem wir die Kombinationfselder cbodetailtabelle, cboanzeigefeld und cbofremdschlues sel feld betrachtet haben. Wenn der Benutzer nun einen Eintrag im rechten Listenfeld auswählt und damit festlegt, in welche Kategorie die Einträge des linken Listenfeldes überführt werden sollen, löst dies die folgende Prozedur aus. Diese ruft wiederum die Prozedur DetaildatensaetzeFuellen auf, die wir später beschreiben. Private Sub lstlookupwerteersetzen_µ DetaildatensaetzeFuellen AfterUpdate() Detailtabelle auswählen Wenn der Benutzer festgelegt hat, welche die Lookup-Tabelle ist und welche Datensätze der Lookup-Tabelle in andere Lookup-Datensätze überführt werden sollen, geht es an die Konfiguration der Tabelle, die mit der Lookup-Tabelle verknüpft ist. In unserem Beispiel handelt es sich dabei um die Tabelle tblartikel, die über das Fremdschlüsselfeld Kategorie- ID mit dieser Tabelle verknüpft ist. Wenn der Benutzer die Detailtabelle mit dem Kombinationsfeld cbodetailtabelle ausgewählt hat, löst er damit die folgende Ereignisprozedur aus: Private Sub cbodetailtabelle_click() With Me!cboFremdschluesselfeld.RowSourceType = "Field List".RowSource = Me!cboDetailtabelle Seite 61

64 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS End With With Me!cboAnzeigefeld.RowSourceType = "Field List".RowSource = Me!cboDetailtabelle End With DetaildatensaetzeFuellen Diese legt die Datensatzherkunft für die beiden folgenden Kombinationsfelder fest, die beide wie bereits die beiden Kombinationsfelder im oberen Bereich mit der Liste der verfügbaren Felder der mit cbodetailtabelle ausgewählten Tabelle gefüllt werden. Danach ruft auch diese Routine wieder die Prozedur DetaildatensaetzeFuellen auf. Bevor wir diese beschreiben, schauen wir uns noch zwei weitere Prozeduren an, die diese Routine aufrufen nämlich jene, die durch das Auswählen der Felder mit den Kombinationsfeldern cboanzeigefeld und cbofremdschluesselfeld ausgelöst werden: Private Sub cboanzeigefeld_click() DetaildatensaetzeFuellen Private Sub cbofremdschluesselfeld_click() DetaildatensaetzeFuellen Detaildatensätze füllen Die Prozedur DetaildatensaetzeFuellen aus Listing 7 analysiert die Inhalte der beiden Listenfelder mit den zu überführenden Lookup-Werten sowie die betroffene Detailtabelle und zeigt dann in den zwei unteren Listenfeldern links die Daten an, die den zu überführenden Lookup-Einträgen zugeordnet sind und rechts die Daten, die zu dem Ziel-Lookup-Eintrag gehören. Dabei durchläuft sie ähnlich wie die Prozedur LookuplisteErsetzenFuellen die markierten Listenfeldeinträge und setzt daraus ein Kriterium zusammen, mit dem die mit den Kategorien verknüpften Datensätze der Detailtabelle ermittelt werden können. Das Gleiche geschieht parallel für die Einträge der Detailtabelle (hier die Artikel), die mit der Zielkategorie verknüpft sind. Dadurch sieht man schließlich in den unteren beiden Listenfeldern, welche Detail-Datensätze in der Zielkategorie zusammengeführt werden. Private Sub DetaildatensaetzeFuellen() Dim strsql As String, var As Variant, strlookupwerte As String, strsqlzuersetzen As String If Not Nz(Me!cboDetailtabelle) = 0 And Not Nz(Me!cboFremdschluesselfeld) = 0 And Not Nz(Me!cboAnzeigefeld) = 0 Then strsql = "SELECT " & Me!cboAnzeigefeld & " FROM " & Me!cboDetailtabelle For Each var In Me!lstLookupwerte.ItemsSelected strlookupwerte = strlookupwerte & ", " & lstlookupwerte.itemdata(var) Next var If Not Len(strLookupwerte) = 0 Then strlookupwerte = Mid(strLookupwerte, 3) strsql = strsql & " WHERE " & Me!cboFremdschluesselfeld & " IN(" & strlookupwerte & ")" Me!lstDetaildaten.RowSource = strsql strsqlzuersetzen = "SELECT " & Me!cboAnzeigefeld & " FROM " & Me!cboDetailtabelle If Not IsNull(Me!lstLookupwerteErsetzen) Then strsqlzuersetzen = strsqlzuersetzen & " WHERE " & Me!cboFremdschluesselfeld & " = " & Me!lstLookupwerteErsetzen Me!lstDetaildatenZuErsetzen.RowSource = strsqlzuersetzen Else Me!lstDetaildaten.RowSource = "" Me!lstDetaildatenZuErsetzen.RowSource = "" Me!cmdErsetzen.Caption = "Kategorie '" & Me!lstLookupwerte.Column(1) & "' durch Kategorie '" _ & Me!lstLookupwerteErsetzen.Column(1) & "' ersetzen" Listing 7: Füllen der Listenfelder mit den Detaildatensätzen nach der Aktualisierung diverser Steuerelemente Seite 62

65 LÖSUNGEN LOOKUP-DATEN: AUS ZWEI MACH EINS Lookup-Einträge zusammenführen Fehlt nur noch die Ereignisprozedur, die durch das Anklicken der Schaltfläche cmdersetzen ausgelöst wird. Diese sieht wie in Listing 8 aus und durchläuft zunächst eine For Each-Schleife über die zu überführenden Lookup-Einträge. Dabei setzt sie eine Liste der Primärschlüsselwerte der enthaltenen Einträge wie bereits weiter oben beschrieben zusammen. Damit formuliert sie eine UPDATE-Abfrage, welche für alle Datensätze der Detailtabelle, die mit den zu überführenden Lookup-Werten verknüpft sind, den Wert des Fremdschlüsselfeldes auf den Primärschlüsselwert des Zieleintrags der Lookup-Tabelle einstellt. Diese sieht etwa so aus: UPDATE tblartikel SET KategorieID = 2 WHERE KategorieID IN(1, 4, 8) Dann stellt die Prozedur eine weitere SQL-Anweisung zusammen, die diesmal die Lookup-Einträge löschen soll, die soeben von verknüpften Datensätzen befreit wurden. Diese Abfrage sieht beispielsweise so aus: DELETE FROM tblkategorien WHERE KategorieID IN(1, 4, 8) Damit sind die Lookup-Einträge samt verknüpfter Detaildatensätze überführt. Die Prozedur aktualisiert nun noch durch den Aufruf der beiden Routinen LookuplisteFuellen und DetaildatensaetzeFuellen die übrigen Steuerelemente des Formulars. Zusammenfassung und Ausblick Diese Lösung setzen Sie ein, indem Sie das Formular in die Zieldatenbank kopieren und es aufrufen. Die übrigen Schritte wurden bereits weiter oben erläutert. Gegebenenfalls könnte man aus dieser Lösung noch ein Add-In erstellen, das sich dann bequem über die Liste der Menü-Add-Ins aufrufen ließe. Private Sub cmdersetzen_click() Dim db As DAO.Database Dim var As Variant Dim strdetailtabelle As String, strfremdschluesselfeld As String Dim lngalterfremdschluesselwert As Long, straltefremdschluesselwerte As String Dim lngneuerfremdschluesselwert As Long, strprimaerschluesselfeld As String Dim strmastertabelle As String Set db = CurrentDb strdetailtabelle = Me!cboDetailtabelle strfremdschluesselfeld = Me!cboFremdschluesselfeld strprimaerschluesselfeld = Me!cboPrimaerschluesselfeld For Each var In Me!lstLookupwerte.ItemsSelected lngalterfremdschluesselwert = Me!lstLookupwerte.ItemData(var) straltefremdschluesselwerte = straltefremdschluesselwerte & ", " & lngalterfremdschluesselwert Next var If Not Len(strAlteFremdschluesselwerte) = 0 Then straltefremdschluesselwerte = Mid(strAlteFremdschluesselwerte, 3) lngneuerfremdschluesselwert = Me!lstLookupwerteErsetzen strmastertabelle = Me!cboMastertabelle db.execute "UPDATE " & strdetailtabelle & " SET " & strfremdschluesselfeld & " = " & lngneuerfremdschluesselwert _ & " WHERE " & strfremdschluesselfeld & " IN(" & straltefremdschluesselwerte & ")", dbfailonerror db.execute "DELETE FROM " & strmastertabelle & " WHERE " & strprimaerschluesselfeld _ & " IN(" & straltefremdschluesselwerte & ")", dbfailonerror LookuplisteFuellen DetaildatensaetzeFuellen Listing 8: Ersetzen der Einträge der Lookup-Tabelle im Fremdschlüsselfeld und Löschen der nun überflüssigen Lookup-Einträge Seite 63

66 LÖSUNGEN QR-CODES MIT ERZEUGEN QR-Codes mit Access erzeugen, Teil II Im ersten Teil dieser Beitragsreihe haben wir bereits den Ausdruck codiert, der später in Form eines QR- Codes grafisch abgebildet werden soll. Der weitaus interessantere Teil folgt noch: Die Berechnung des Fehlerkorrekturcodes. Schließlich folgt dann im letzten Teil der Beitragsreihe noch die Erstellung der eigentlichen Grafik, die Sie dann beispielsweise als Bilddatei speichern und etwa in einem Bericht einer Access-Datenbank weiterverwenden können. Der Stand nach dem ersten Teil der Beitragsreihe ist, dass wir eine codierte Zeichenkette vorliegen haben unabhängig davon, ob diese als Zahl, als reiner Text oder als alphanumerische Zeichenkette daherkommt. Außerdem haben wir eine Möglichkeit geschaffen, den Grad der Fehlerkorrektur auszuwählen und die Version, also die Kantenlänge des zu erzeugenden QR-Codes. Dieser wird schließlich bereits aus der Länge der zu kodierenden Zeichenkette, dem Fehlerkorrekturgrad und der Codiermethode ermittelt. Bild 1: Entwurf der Tabelle tblversions Als Nächstes benötigen wir den Fehlerkorrekturcode. Um diesen zu ermitteln, legen wir zunächst die Länge des Fehlerkorrekturcodes fest. Dies geschieht mithilfe einer Tabelle namens tblnumberofdatacodewords (Entwurf siehe Bild 1). Die Tabelle liefert die folgenden wichtigen Informationen, wobei wir den Datensatz aus der Tabelle nutzen, dessen Felder VersionID und ErrorCorrectionLevelID für unsere Codierung zutreffen (s. Bild 2): Wir teilen den Code für unseren Text in ein, zwei oder vier Blöcken mit einer bestimmten Anzahl von Codewörtern mit je acht binären Zeichen auf. Wenn NumberOf- BlocksInGroup1 den Wert 1 enthält, verwenden wir nur einen Block, der die im Feld NumberOfDataCodewordsInGroup1Blocks angegebene Anzahl von Codewörtern enthält. Wenn NumberOfBlocksInGroup1 den Wert 2 oder einen größeren Wert enthält, benötigen wir zwei oder mehr Blöcke mit der entsprechenden Anzahl von Codewörtern in der ersten Gruppe. Es gibt noch eine zweite Gruppe, für welche die beiden Felder NumberOf- BlocksInGroup2 und NumberOfDataCodewordsInGroup1Blocks die Anzahl der Blöcke und Codewörter angeben. Bild 2: Die Tabelle tblversions mit einigen Werten Wenn wir also beispielsweise für VersionID den Wert 5 und für die Fehlerkorrektur den Level Q ausgewählt haben, haben wir zwei Gruppen, wobei die erste zwei Blöcke Seite 64

67 LÖSUNGEN QR-CODES MIT ERZEUGEN mit je 15 Codewörtern aufnimmt und die zweite zwei Blöcke mit je 16 Codewörtern. Wenn Sie also beispielsweise einen Text codieren, der mit den oben genannten Parametern um einen Fehlerkorrekturcode erweitert werden soll, wird dieser Text 2 x 15 x x 16 x 8 Zeichen lang sein, also 496 Zeichen lang sein was auch der Fall ist. Wir brechen dies auf ein einfacheres Beispiel herunter, zum Beispiel zur Codierung der Zeichenkette HELLO WORLD. Wir verwenden den Fehlerkorrekturgrad M und die kleinste Version mit 21 x 21 Zeichen. Die codierte Zeichenfolge sieht nach der Beschreibung aus dem ersten Teil der Beitragsreihe so aus: Steuerung der Codierung Die Codierung starten Sie mit einem Klick auf die Schaltfläche cmdaktualisieren des Formulars frmqrcodes, das nun wie in Bild 3 aussieht. Dieses Formular erlaubt, wie schon im ersten Teil erläutert, die Eingabe des zu codierenden Textes in das Textfeld txtausgangstext, die Auswahl des Zeichensatzes mit dem Kombinationsfeld cbomodeid, den Grad der Fehlerkorrektur mit cboerrorcorrectionid und die Version beziehungsweise Kantenlänge mit cboversionid. Die Schaltfläche cmdaktualisieren löst nun die Prozedur aus Listing 1 aus. Diese erledigt zunächst die bereits im ersten Teil der Beitragsreihe beschriebenen Aufgaben, sprich: den binären Code zu ermitteln, welcher die Information über die Codierung, die Anzahl der Zeichen und den Text selbst in binärer Form enthält. Dann speichert die Prozedur die Werte der beiden Kombinationsfelder cbo- VersionID und cboerrorcorrectionid in entsprechenden Variablen, bevor sie darauf basierend ein Recordset öffnet. Dieses enthält den Datensatz der Tabelle tblnumberofdatacodewords, der den mit den Kombinationsfeldern ausgewählten Parametern entspricht. Aus dieser Tabelle entnehmen wir einige Werte und speichern diese in weiteren Variablen: lngblocksgruppe1 Die Tabelle tblnumberof- DataCodewords liefert die Information, dass diese Zeichenfolge in eine Gruppe mit 16 Blöcken á acht Zeichen aufgeteilt wird, also etwa so: Diese Zahlen benötigen wir später, wenn wir ein Polynom für den zu codierenden Text erstellen müssen. Die Zahlen dienen dann als Koeffizienten. Bild 3: Das Formular frmqrcodes Seite 65

68 LÖSUNGEN QR-CODES MIT ERZEUGEN lngwoertergruppe1 lngblocksgruppe2 lngwoertergruppe2 intanzahlfehlerkorrekturwoerter Nun folgt der interessante, in diesem Teil der Beitragsreihe beschriebene Part: Die Umwandlung des binären Codes in einen sogenannten interleaved Code und das Ermitteln und Hinzufügen des Fehlerkorrekturcodes. Dies geschieht in folgenden Schritten: Vermischen des Codes zum interleaved Code mit der Funktion InterleavedCode, Ermitteln des Fehlerkorrekturcodes auf Basis des Ausgangscodes und Vermischen zum interleaved Fehlerkorrekturcode mit der Funktion InterleavedErrorCode und Zusammenfügen der beiden Teile im Textfeld txtqrcode. Schauen wir uns nun an, wie diese Funktionen arbeiten. Code weiter bearbeiten Den Code aus dem Textfeld txtcode- Inhalt beziehungsweise der Variablen strcode verwenden wir nun nicht einfach so als ersten Teil des QR-Codes. Stattdessen wird dieser erste Teil gegebenenfalls noch etwas durcheinandergewürfelt allerdings nach fest vorgegebenen Regeln. Dies geschieht allerdings nur ab einer gewissen Länge des Codes beziehungsweise wenn die Private Sub cmdaktualisieren_click() Dim strcode As String Dim lngblocksgruppe1 As Long Dim lngwoertergruppe1 As Long Dim lngblocksgruppe2 As Long Dim lngwoertergruppe2 As Long Dim lngversionid As Long Dim lngerrorcorrectionlevelid As Long Dim db As DAO.Database Dim rst As DAO.Recordset Dim intanzahlfehlerkorrekturwoerter As Integer Set db = CurrentDb strcode = strcode & GetModeIndicator strcode = strcode & GetCharacterCountIndicator strcode = strcode & Encode(Me!txtAusgangstext, Me!cboModeID) strcode = AddPadBytes(strCode, Me!cboVersionID, Me!cboErrorCorrectionID) Me!txtCodeInhalt = strcode lngversionid = Me!cboVersionID lngerrorcorrectionlevelid = Me!cboErrorCorrectionID Set rst = db.openrecordset("select * FROM tblnumberofdatacodewords WHERE VersionID = " & lngversionid _ & " AND ErrorCorrectionLevelID = " & lngerrorcorrectionlevelid) lngblocksgruppe1 = rst!numberofblocksingroup1 lngwoertergruppe1 = rst!numberofdatacodewordsingroup1blocks lngblocksgruppe2 = Nz(rst!NumberOfBlocksInGroup2, 0) lngwoertergruppe2 = Nz(rst!NumberOfDataCodewordsInGroup2Blocks, 0) intanzahlfehlerkorrekturwoerter = rst!eccodewordsperblock Me!txtInterleavedCode = InterleavedCode(strCode, lngblocksgruppe1, lngwoertergruppe1, lngblocksgruppe2, lngwoertergruppe2) Me!txtInterleavedErrorCorrectionCode = InterleavedErrorCode(strCode, lngblocksgruppe1, lngwoertergruppe1, _ lngblocksgruppe2, lngwoertergruppe2, intanzahlfehlerkorrekturwoerter) Me!txtQRCode = Me!txtInterleavedCode & Me!txtInterleavedErrorCorrectionCode Listing 1: Diese Prozedur steuert die komplette Codierung. Seite 66

69 LÖSUNGEN QR-CODES MIT ERZEUGEN einzelnen achtstelligen Binärzahlen auf mehr als nur die erste Gruppe aufgeteilt werden also wenn in der Tabelle tbl- NumberOfDataCodewords in der Spalte für die aktuelle Konfiguration (aus Version und Fehlerkorrektur) auch ein Wert in den Spalten NumberOfBlocksInGroup2 beziehungsweise NumberOfDataCodewordsInGroup2Blocks vorliegen. Dies ist zum Beispiel der Fall, wenn Sie die Version 5 (37 x 37 Pixel) und den Fehlerkorrekturgrad Q wählen. Dieser Code hat insgesamt 62 Codewörter, wobei die erste Gruppe zwei Blocks mit je 15 Codewörtern enthält und die zweite Gruppe zwei Blocks mit je 16 Codewörtern. Die Prozedur InterleavedCode aus Listing 2 erwartet drei Parameter: strcode: Code in binärer Form lngblocksgruppe1: Anzahl der Blöcke in der ersten Gruppe lngwoertergruppe1: Anzahl der Codewörter in der ersten Gruppe lngblocksgruppe2: Anzahl der Blöcke in der zweiten Gruppe (gegebenenfalls 0) lngwoertergruppe2: Anzahl der Codewörter in der zweiten Gruppe (gegebenenfalls 0) Die hinteren vier Werte wurden von der aufrufenden Prozedur cmdaktualisieren aus der Tabelle tblnumberofdata- Codewords für die aktuelle Kombination aus Version und Fehlerkorrekturgrad ausgewählt. Die Prozedur ruft zunächst eine weitere Routine auf, die eine wichtige Aufgabe beim Mischen der Codewörter erfüllt sie erstellt nämlich ein Array, aus dem Public Function InterleavedCode(ByVal strcode As String, lngblocksgruppe1 As Long, lngwoertergruppe1 As Long, _ lngblocksgruppe2 As Long, lngwoertergruppe2 As Long) Dim i As Integer Dim j As Integer Dim strtempneu As String Dim strtemp As String Dim intcodewoertermatrix() As Integer intcodewoertermatrix = CodeWoerterMatrix(lngBlocksGruppe1, lngwoertergruppe1, lngwoertergruppe2, lngblocksgruppe2) strtemp = Trim(strCode) If lngwoertergruppe2 <> 0 Then For j = 1 To lngwoertergruppe2 For i = 1 To lngblocksgruppe1 + lngblocksgruppe2 If intcodewoertermatrix(i, j) <> 0 Then strtempneu = strtempneu + Mid$(strTemp, intcodewoertermatrix(i, j) * 8-7, 8) Next i Next j If lngwoertergruppe2 = 0 Then For j = 1 To lngwoertergruppe1 For i = 1 To lngblocksgruppe1 + lngblocksgruppe2 If intcodewoertermatrix(i, j) <> 0 Then strtempneu = strtempneu + Mid$(strTemp, intcodewoertermatrix(i, j) * 8-7, 8) Next i Next j InterleavedCode = strtempneu End Function Listing 2: Vermischen des Binärcodes für den Ausgangstext Seite 67

70 LÖSUNGEN QR-CODES MIT ERZEUGEN später die neue Reihenfolge für die Codewörter entnommen wird. Diese Prozedur heißt CodeWoerterMatrix und erwartet die soeben ermittelten vier Werte für die gleichnamigen Parameter lngblocksgruppe1, lngwoertergruppe1, lngwoertergruppe2 und lng- BlocksGruppe2 (s. Listing 3). Wenn die zweite Gruppe aus der Tabelle tblnumberofdatacodewords mindestens einen Block enthält, ist der erste Teil der If...Then-Bedingung der Prozedur CodeWoerterMatrix erfüllt. Dieser Teil füllt nun beispielsweise die laufenden Nummern (im Falle von Version 5, Fehlerkorrekturlevel Q von 1 bis 62) in ein zweidimensionales Array namens intcodewoertermatrix(). Dieses wird zuvor mit der ReDim-Anweisung mit dem Bereich 1 bis für die erste Dimension und dem Bereich 1 bis 16 für die zweite Dimension dimensioniert. Die Zahlen werden dann einfach wie folgt eingetragen also in vier Zeilen und 16 Spalten wie in der folgenden schematischen Darstellung: Zuvor wird das Array noch komplett mit Nullen gefüllt. Sollte die Tabelle tblnumberofdatacodewords keinen einzigen Block in der Zeile NumberOfBlocksIn- Group2 für die gewählte Konfiguration aus Version und Fehlerkorrekturgrad liefern, führt die Prozedur CodeWoerterMatrix den zweiten Teil der If...Then- Bedingung aus. In diesem Fall legt die Prozedur nur für die Elemente der ersten Gruppe die laufenden Nummern der Codewörter an. In unserem einfachen Beispiel von oben sieht dies so aus also nur mit einer Zeile und 16 Spalten: Das entsprechende Array intcodewoertermatrix() wird nun als Rückgabewertan die aufrufende Prozedur zurückgegeben. Diese speichert nun zunächst den Code aus dem Parameter strcode in der Variablen strtemp. Wenn die aktuelle Konfiguration Wörter in der zweiten Gruppe hergibt, werden die Anweisungen in der ersten If...Then-Bedingung ausgeführt. Hier werden zwei For... Next-Schleifen durchlaufen, die äußere über die Anzahl der Wörter der zweiten Gruppe (hier 16), die innere die Anzahl der Blocks der ersten Gruppe plus die der zweiten Gruppe (hier 2 + 2). Sollte das Array intcodewoertermatrix für die Laufvariablen i und j einen Wert ungleich 0 enthalten, beginnt die Prozedur mit dem Erstellen einer neuen Code-Zeichenkette in der Variablen strtempneu. Dabei wird jeweils der Teil bestehend aus acht Zeichen aus strtemp eingelesen und an strtempneu angehängt, welcher der laufenden Nummer des Elements von intcodewoertermatrix() mit den Werten für i und j entspricht. Das heißt, dass das Array nun nicht zunächst spalten- und dann zeilenweise durchlaufen wird (also 1, 2, 3 und so weiter), sondern erst zeilen- und dann spaltenweise. Die Reihenfolge der neu zusammengesetzten Elemente lautet also: 1, 16, 31, 47, 2, 17, 32, 48, 3, 18, 33, 49, 4, 19, 34, 50, 5, 20, 35, 51, 6, 21, 36, 52, 7, 22, 37, 53, 8, 23, 38, 54, 9, 24, 39, 55, 10, 25, 40, 56, 11, 26, 41, 57, 12, 27, 42, 58, 13, 28, 43, 59, 14, 29, 44, 60, 15, 30, 45, 61, 46, 62. Sprich: Der Ausdruck in strtempneu beginnt mit dem ersten Codewort aus strtemp, dann folgt das 16. Codewort, dann das 31. und so weiter. Sollte die zweite Gruppe keinen einzigen Block enthalten, durchläuft die Prozedur wiederum in zwei Schleifen die Werte des Arrays intcodewoertermatrix(). Wenn intcodewoertermatrix an dieser Stelle nur eine Zeile mit Daten enthält, ist strtempneu gleich der mit strcode übergebenen Zeichenkette. Das Ergebnis wird dann als Ergebnis der Funktion InterleavedCode an die aufrufende Routine zurückgeliefert. Fehlerkorrekturcode ermitteln Der Fehlerkorrekturcode wird nach einer recht komplizierten Vorgehensweise erstellt. Dafür benötigen wir zwei Polynome eines für den zu verschlüsselnden Text und ein sogenanntes generator polynomial. Das Polynom für den zu verschlüsselnden Text erzeugen wir auf Basis der oben ermittelten Codewörter. Ausgangspunkt für die Erstellung des Fehlerkorrekturcodes ist die Funktion InterleavedErrorCode aus Listing 4. Sie erwartet die gleichen Parameter wie die oben beschriebene Funktion InterleavedCode plus einen weiteren, nämlich intanzahlfehlerkorrekturwoerter. Dies ist ein weiterer Wert, der bereits in cmdaktualisieren_click für die aktuelle Konfiguration aus Version und Fehlerkorrekturgrad aus der Tabelle tblnumberofdatacodewords ermittelt wurde und zwar aus dem Feld ECCodewordsPerBlock. Dieser Wert gibt an, wie viele binäre Wörter aus acht Zeichen noch an die bereits ermittelte Zeichenkette angehängt werden müssen, um den Code für den QR-Code Seite 68

71 LÖSUNGEN QR-CODES MIT ERZEUGEN zu vervollständigen. Das Verhältnis der Anzahl der Fehlerkorrekturwörter zu den Codewörtern steigt mit wachsendem Fehlerkorrekturgrad. Die Prozedur ermittelt zunächst über die Funktion FehlerwoerterMatrix ein ähnliches Array wie zuvor die Funktion CodewoerterMatrix. Diese liefert wieder die Grundlage für die später zu verwendende Reihenfolge für das Durchmischen des Fehlerkorrekturcodes, um den interleaved Code zu erhalten. Das Public Function CodeWoerterMatrix(lngBlocksGruppe1 As Long, lngwoertergruppe1 As Long, lngwoertergruppe2 As Long, _ lngblocksgruppe2 As Long, strdatacodewords() As Variant) Dim i As Integer Dim j As Integer Dim intzaehler As Integer If lngwoertergruppe2 <> 0 Then ReDim strdatacodewords(1 To lngblocksgruppe1 + lngblocksgruppe2, 1 To lngwoertergruppe2) For j = 1 To lngwoertergruppe2 For i = 1 To lngblocksgruppe1 + lngblocksgruppe2 strdatacodewords(i, j) = 0 Next i Next j intzaehler = 1 For i = 1 To lngblocksgruppe1 For j = 1 To lngwoertergruppe1 strdatacodewords(i, j) = intzaehler intzaehler = intzaehler + 1 Next j Next i For i = lngblocksgruppe1 + 1 To lngblocksgruppe1 + lngblocksgruppe2 For j = 1 To lngwoertergruppe2 strdatacodewords(i, j) = intzaehler intzaehler = intzaehler + 1 Next j Next i Else ReDim strdatacodewords(1 To lngblocksgruppe1 + lngblocksgruppe2, 1 To lngwoertergruppe1) For j = 1 To lngwoertergruppe1 For i = 1 To lngblocksgruppe1 strdatacodewords(i, j) = 0 Next i Next j intzaehler = 1 For i = 1 To lngblocksgruppe1 For j = 1 To lngwoertergruppe1 strdatacodewords(i, j) = intzaehler intzaehler = intzaehler + 1 Next j Next i End Function Listing 3: Hilfsfunktion zum vermischen der Codewörter Seite 69

72 LÖSUNGEN QR-CODES MIT ERZEUGEN Ergebnis speichert die Prozedur in der Variablen intfehlerwoertermatrix(). Die Funktion FehlerwoerterMatrix finden Sie in Listing 5. Sie erwartet die Anzahl der Blöcke in Gruppe 1 und Grupp2 (lngblocksgruppe1 und lng- BlocksGruppe2) sowie die Anzahl der Fehlerkorrekturwörter (intanzahlfehlerkorrekturwoerter). Das Integer-Array inttemp nimmt die Werte auf. Dazu wird es mit der ReDim- Anweisung vordimensioniert, und zwar in der ersten Dimension für die Summe der Blöcke der beiden Gruppen und in der zweiten Dimension für die Anzahl der Fehlerkorrekturwörter. Dann durchläuft eine erste äußere Schleife alle Werte von 1 bis zur Anzahl der Fehlerkorrekturwörter (intanzahl- Fehlerkorrekturwoerter) und eine innere Schleife die Werte von 1 bis zur Summe von lngblocksgruppe1 und lngblocksgruppe2. Innerhalb der beiden verschachtelten Schleifen wird das Array inttemp für die aktuellen Laufvariablen i und j mit dem Wert 0 gefüllt. In der zweiten Schleife folgen dann die tatsächlichen Werte. Dazu verwenden wir wieder eine Zählervariable namens intzaehler, die mit dem Wert 1 beginnt und bei jedem Schleifendurchlauf der inneren Schleife um 1 erhöht wird. Die For...Next-Anweisungen sehen genauso aus wie beim Füllen des Array mit Nullen. Innerhalb der Schleife schreibt eine Anweisung jeweils den Wert von intzaehler in das aktuelle durch i und j gekennzeichnete Feld des Arrays inttemp. Dieses Array wird dann schließlich als Funktionswert an die aufrufende Variable zurückgegeben. Nun beginnt die eigentliche Arbeit. Wir schauen uns dies am bereits oben erwähnten einfachen Beispiel des Textes HELLO WORLD an, wobei wir die Version 1 verwenden (21 x 21 Pixel) und den Fehlerkorrekturlevel M. Das Ziel der Aktion ist, zwei Polynome zu ermitteln und diese durcheinander zu teilen. Das ist nicht unkompliziert und erfordert Rechenschritte, die Otto Normalverbraucher wohl nicht aus dem Ärmel schüttelt (der Autor schließt sich hier nicht aus...). Das Ergebnis ist dann wiederum ein Polynom, dessen Koeffizienten die Hauptrolle beim Zusammenstellen des Fehlerkorrekturcodes spielen. Zur Auffrischung der mathematischen Kenntnisse ein Polynom sieht etwa so aus: a 0 + a 1 x + a 2 x a n-1 x n-1 + a n x n Polynom für den Text ermitteln Die Ermittlung des Polynoms für den Text ist relativ einfach. Als Grundlage dient der Binärcode, den wir für den Text ermittelt haben inklusive der zwölf Zeichen, welche die Codierungsmethode und die Anzahl der Zeichen beschreiben und teilen diesen auf jeweils acht Zeichen auf. Dies sieht dann so aus: = = = = = = = = = = = = = = = = 17 Die entsprechenden Zahlenwerte, also 32, 91, 11 und so weiter liefern dabei die Koeffizienten für unser erstes Polynom, dass wie folgt aussieht: 32x x x x x x x x x x x x x x x x Polynom für den Fehlercode Das zweite Polynom lässt sich ebenso einfach ermitteln. Das heißt: Eigentlich ist es nicht ganz so einfach. Aber zum Glück gibt es fertige Tabellen, die wir schnell in unsere Beispieldatenbank übertragen konnten und die uns die benötigten Informationen liefern. In diesem Fall heißt die Tabelle tblgeneratorpolynomical aus Bild 4 (siehe [1]). Die Spalte ECCodewordsPerBlock gibt die benötigte Anzahl der Koeffizienten an, die in der Prozedur InterleavedErrorCode mit dem Parameter intanzahl- Seite 70

73 LÖSUNGEN QR-CODES MIT ERZEUGEN Public Function InterleavedErrorCode(ByVal strcode As String, lngblocksgruppe1 As Long, lngwoertergruppe1 As Long, _ lngblocksgruppe2 As Long, lngwoertergruppe2 As Long, intanzahlfehlerkorrekturwoerter As Integer) Dim i As Integer, j As Integer, intstart As Integer, intende As Integer Dim intmessagepolynomial() As Integer, intanzahlkoeffizientenmessage As Integer, intblock As Integer Dim strtemp As String, strcodeteil As String, strtempinterleaved As String Dim intfehlerwoertermatrix() As Variant, intcodeteil As Integer intfehlerwoertermatrix() = FehlerwoerterMatrix(lngBlocksGruppe1, lngblocksgruppe2, intanzahlfehlerkorrekturwoerter) intstart = 1 intende = lngwoertergruppe1 * 8 intblock = 0 Do intanzahlkoeffizientenmessage = 1 If intblock < lngblocksgruppe1 Then For i = intstart To intende Step 8 strcodeteil = Mid$(strCode, i, 8) intcodeteil = BinaerZuDezimal(strCodeTeil) ReDim Preserve intmessagepolynomial(intanzahlkoeffizientenmessage) As Integer intmessagepolynomial(intanzahlkoeffizientenmessage) = intcodeteil intanzahlkoeffizientenmessage = intanzahlkoeffizientenmessage + 1 Next i intstart = intende + 1 If intblock = lngblocksgruppe1-1 Then intende = intende + lngwoertergruppe2 * 8 Else intende = intende + lngwoertergruppe1 * 8 If intblock >= lngblocksgruppe1 Then For i = intstart To intende Step 8 strcodeteil = Mid$(strCode, i, 8) intcodeteil = BinaerZuDezimal(strCodeTeil) ReDim Preserve intmessagepolynomial(intanzahlkoeffizientenmessage) As Integer intmessagepolynomial(intanzahlkoeffizientenmessage) = intcodeteil intanzahlkoeffizientenmessage = intanzahlkoeffizientenmessage + 1 Next i intstart = intende + 1 intende = intende + lngwoertergruppe2 * 8... Listing 4: Erstellen des Fehlerkorrekturcodes (Teil I) FehlerkorrekturWoerter übergeben wurde in diesem Fall 10. Nun benötigen wir nur noch die Exponenten für dieses Polynom, die wir der Tabelle entnehmen können. Das Feld Alphakoeffizient liefert den Exponenten für den Koffezient, das Feld XKoeffizient den Exponent für x. Damit erhalten wir für den Start dieses Polynom: a 0 x 10 + a 1 x 9 + a 216 x 8 + a 194 x 7 + a 159 x 6 + a 111 x 5 + a 199 x 4 + a 94 x 3 + a 95 x 2 + a 113 x 1 + a 157 x + a 193 x Nun werden noch einige Operationen an diesem Polynom durchgeführt Stichworte sind Galois Field, Logs und Antilogs mehr dazu im Dokument aus [1]. Tatsache ist: Die Division der beiden Polynome liefert ein neues Polynom, Seite 71

74 LÖSUNGEN QR-CODES MIT ERZEUGEN... intanzahlkoeffizientenmessage = intanzahlkoeffizientenmessage - 1 strtemp = Fehlerkorrekturcode(intMessagePolynomial, intanzahlkoeffizientenmessage, intanzahlfehlerkorrekturwoerter, strtemp) intblock = intblock + 1 Loop Until intblock = lngblocksgruppe1 + lngblocksgruppe2 strtemp = Trim(strTemp) For j = 1 To intanzahlfehlerkorrekturwoerter For i = 1 To lngblocksgruppe1 + lngblocksgruppe2 If varfehlerwoertermatrix(i, j) <> 0 Then strtempinterleaved = strtempinterleaved + Mid$(strTemp, varfehlerwoertermatrix(i, j) * 8-7, 8) Next i Next j InterleavedErrorCode = strtempinterleaved End Function Listing 4 (Fortsetzung): Erstellen des Fehlerkorrekturcodes dessen Koeffizienten wiederum Zahlen von 1 bis 255 enthalten und zwar genauso viele, wie im Feld ECCodewordsPerBlock der Tabelle tbl- NumberOfDataCodewords für unsere Kombination aus Version und Fehlerkorrekturgrad angegeben wurden (in diesem Fall 10). Zurück zur Prozedur InterleavedError- Code: Diese durchläuft eine Do...Loop Until-Schleife solange, bis die Laufvariable intblock der Anzahl der Blöcke aus Gruppe 1 und Gruppe 2 entspricht. Darin stellt sie zunächst in intmessagepolynomial ein Array zusammen, das die Koeffizienten für das Polynom für den zu codierenden Text enthält hier beispielsweise so: Danach folgt eine Schleife, welche die Exponenten für die jeweiligen Elemente des Polynoms zusammenstellt: Dies sind die Daten, die das bereits oben beispielhaft dargestellte Polynom ergeben allerdings mit um den Wert aus intanzahlfehlerkorrekturwoerter (hier 10) erhöhtem Exponent, was mit der folgenden Division der beiden Polynome zu tun hat: 32x x x x x x x x x x x x x x x x 10 Bild 4: Diese Tabelle liefert die Koeffizienten für das zweite Polynom. Mit einigen der in der Schleife ermittelten Daten, unter anderem dem Array mit dem Koeffizienten für das Code- Polynom, geht es nun in die Funktion Seite 72

Werte zu Kombinationsfeldern hinzufügen

Werte zu Kombinationsfeldern hinzufügen Kombinationsfelder bieten meist Daten aus Lookup-Tabellen zur Auswahl an. Das bedeutet, dass Sie damit etwa die Anrede oder den Titel einer Person festlegen können, wobei Anreden und Titel in separaten

Mehr

ACCESS. Formulare per VBA referenzieren FORMULARE MIT VBA PROGRAMMIEREN FORMULARE PER VBA REFERENZIEREN BASICS

ACCESS. Formulare per VBA referenzieren FORMULARE MIT VBA PROGRAMMIEREN FORMULARE PER VBA REFERENZIEREN BASICS Formulare per VBA referenzieren Wenn Sie Formulare und Steuerelemente programmieren wollen, müssen Sie wissen, wie Sie diese referenzieren. Nicht immer geschieht dies vom Klassenmodul des Formulars selbst

Mehr

Verknüpfte Daten kopieren

Verknüpfte Daten kopieren Das Kopieren einfacher Datensätze ist schnell erledigt. Markieren, kopieren, einfügen schon liegt der neue Datensatz vor. Was aber geschieht, wenn an dem zu kopierenden Datensatz noch weitere Daten hängen

Mehr

Zweitens über eine Abfrage, welche die Tabellen tblartikel und tbllieferanten verknüpft. Auf diese Weise würde das Kombinationsfeld

Zweitens über eine Abfrage, welche die Tabellen tblartikel und tbllieferanten verknüpft. Auf diese Weise würde das Kombinationsfeld Filterkriterien für Formulare, Teil III: Kombinationsfelder In den ersten beiden Teilen dieser Artikelreihe haben Sie erfahren, wie Sie Felder der verschiedenen Datentypen filtern. Nun geht es ans Eingemachte:

Mehr

ACCESS. Kombinationsfeld um Suche erweitern FORMULARE FÜR DIE DATENEINGABE KOMBINATIONSFELD UM SUCHE ERWEITERN BASICS

ACCESS. Kombinationsfeld um Suche erweitern FORMULARE FÜR DIE DATENEINGABE KOMBINATIONSFELD UM SUCHE ERWEITERN BASICS Kombinationsfeld um Suche erweitern Kombinationsfelder sind schon eine praktische Einrichtung: Sie erlauben nicht nur die Auswahl von Einträgen, die schon nach dem Alphabet voreingestellt sind, sondern

Mehr

ACCESS. Aufgabenplaner LÖSUNGEN AUFGABENPLANER BASICS

ACCESS. Aufgabenplaner LÖSUNGEN AUFGABENPLANER BASICS Aufgabenplaner Im Artikel Berichtsansicht haben Sie eine neue Ansicht für Berichte kennen gelernt, die ganz neue Möglichkeiten eröffnet. Sie können damit hierarchische Daten anzeigen, ohne das Tree- View-Steuerelement

Mehr

ACCESS. Berechnete Felder in Tabellen TABELLEN ENTWERFEN BERECHNETE FELDER IN TABELLEN BASICS

ACCESS. Berechnete Felder in Tabellen TABELLEN ENTWERFEN BERECHNETE FELDER IN TABELLEN BASICS Berechnete Felder in Tabellen Berechnete Felder in Tabellen sind ein Feature, das mit der Version 2010 von Access hinzugekommen ist. Dabei handelt es sich um die Möglichkeit, die Inhalte der übrigen Felder

Mehr

ACCESS. 1:1-Beziehungen TABELLEN ENTWERFEN 1:1-BEZIEHUNGEN BASICS

ACCESS. 1:1-Beziehungen TABELLEN ENTWERFEN 1:1-BEZIEHUNGEN BASICS 1:1-Beziehungen 1:1-Beziehungen können für eine ganze Reihe von Anwendungzwecken sinnvoll sein. Sie können damit beispielsweise die Liefer- und/oder die Rechnungsanschrift für einen Kundendatensatz in

Mehr

ACCESS. Access-Daten nach Excel verknüpfen INTERAKTIV ACCESS-DATEN NACH EXCEL VERKNÜPFEN BASICS

ACCESS. Access-Daten nach Excel verknüpfen INTERAKTIV ACCESS-DATEN NACH EXCEL VERKNÜPFEN BASICS -DATEN NACH EXCEL VERKNÜPFEN Access-Daten nach Excel verknüpfen Wir haben uns bereits in verschiedenen Artikeln angesehen, wie Sie von Access aus auf die Daten einer Excel-Datei zugreifen können ob per

Mehr

Access Programmierung. Ricardo Hernández García. 1. Ausgabe, November 2013 ACC2013P

Access Programmierung. Ricardo Hernández García. 1. Ausgabe, November 2013 ACC2013P Access 2013 Ricardo Hernández García 1. Ausgabe, November 2013 Programmierung ACC2013P Die VBA-Entwicklungsumgebung 5 Weitere Eingabehilfen Im Menü Bearbeiten finden Sie noch weitere Hilfen, die Ihnen

Mehr

4 Artikel verwalten. 4.1 Übersichtsformular erstellen

4 Artikel verwalten. 4.1 Übersichtsformular erstellen 4 Artikel verwalten Ich glaube, dass ich es bereits vor einigen Kapiteln erwähnt habe: Faule Access-Entwickler kommen grundsätzlich schneller zum Ziel als andere. So werden wir es uns beim Gestalten der

Mehr

Formulare für die Dateneingabe Mehrere Formularinstanzen anzeigen

Formulare für die Dateneingabe Mehrere Formularinstanzen anzeigen Die Datensätze einer Tabelle zeigen Sie meist in einer Übersicht wie einem Datenblatt oder einem Listenfeld an. Für die Bearbeitung öffnen Sie den gewünschten Datensatz in einem Detailformular, das die

Mehr

ACCESS. Laufende Summen in Tabellen TABELLEN ENTWERFEN LAUFENDE SUMMEN IN TABELLEN BASICS

ACCESS. Laufende Summen in Tabellen TABELLEN ENTWERFEN LAUFENDE SUMMEN IN TABELLEN BASICS Laufende Summen in Tabellen Immer wieder taucht die Frage auf, wie man in Access die Inhalte von Zahlenfelder mehrerer Datensätze aufsummiert. Unter Excel ist das einfach dort trägt man einfach die Summe

Mehr

Access [basics] Aktionsabfragen per VBA ausführen. Beispieldatenbank. Aktionsabfragen. Die Execute-Methode. Datenzugriff per VBA

Access [basics] Aktionsabfragen per VBA ausführen. Beispieldatenbank. Aktionsabfragen. Die Execute-Methode. Datenzugriff per VBA Aktionsabfragen lassen sich bequem mit der Entwurfsansicht für Abfragen zusammenstellen. Sie können damit Daten an Tabellen anfügen, bestehende Daten ändern oder löschen und sogar gleich die passende Tabelle

Mehr

Inhaltsverzeichnis/Impressum

Inhaltsverzeichnis/Impressum Inhaltsverzeichnis/Impressum Formulare und Steuerelemente Kombinationsfeld mit Bearbeitungsfunktion 2 Steuerelemente an Datenblatt anpassen 11 TreeView: Neue Elemente anlegen 17 Sicherheit Frontend/Backend

Mehr

Erstellung von abhängigen Kombinationsfeldern mit dazugehörigen Unterformular...1

Erstellung von abhängigen Kombinationsfeldern mit dazugehörigen Unterformular...1 Erstellung von abhängigen Kombinationsfeldern mit dazugehörigen Inhaltsverzeichnis Erstellung von abhängigen Kombinationsfeldern mit dazugehörigen...1 Inhaltsverzeichnis...1 Einleitung...1 Vorgaben...1

Mehr

MiniPPS - Systembeschreibung

MiniPPS - Systembeschreibung MiniPPS - Systembeschreibung Hans-Christian Walter Beuth Hochschule für Technik Einführungsbeispiel für Access Version 04.11.2012 Inhalt 1. Access einrichten 2 2. Tabellen 5 3. Abfrage 9 4. Formulare 10

Mehr

Arrays. Arrays werden verwendet, wenn viele Variablen benötigt werden. Der Vorteil in Arrays liegt darin, dass man nur eine Variable deklarieren muss

Arrays. Arrays werden verwendet, wenn viele Variablen benötigt werden. Der Vorteil in Arrays liegt darin, dass man nur eine Variable deklarieren muss Arrays FTI 41 2005-09-09 Arrays werden verwendet, wenn viele Variablen benötigt werden. Der Vorteil in Arrays liegt darin, dass man nur eine Variable deklarieren muss z.b. Dim Werte(x) As Single. Wobei

Mehr

IM UNTERNEHMEN EINFACH NAVIGIEREN LOOKUP-DATEN: AUS ZWEI MACH EINS

IM UNTERNEHMEN EINFACH NAVIGIEREN LOOKUP-DATEN: AUS ZWEI MACH EINS Ausgabe 04/2014 EINFACH NAVIGIEREN Sie sind während der Bearbeitung einer Bestellung schnell zur nächsten gesprungen, weil ein neuer Kunde anruft? Und dann wollen Sie schnell wieder zurück zur vorherigen

Mehr

Bilderverwaltung mit Access

Bilderverwaltung mit Access Bilderverwaltung mit Access (1) Tabelle anlegen Der große Nachteil beim Speichern von Bilddateien in Datenbanken ist, dass sie nicht in dem herkömmlichen Format, sondern in einem Windows-internen Format

Mehr

[basics] Das Access-Magazin für alle, die schnell von 0 auf 100 wollen

[basics]  Das Access-Magazin für alle, die schnell von 0 auf 100 wollen Access [basics] Das Access-Magazin für alle, die schnell von 0 auf 100 wollen Access [basics]-news Mit der neuen Ausgabe von Access [basics] stellen wir die Beispieldateien von einer großen Datenbank auf

Mehr

ACCESS IM UNTERNEHMEN

ACCESS IM UNTERNEHMEN Ausgabe 02/2017 ACCESS STÜCKLISTEN, TEIL II Erweitern Sie die Stücklisten-Lösung um komfortable Kontextmenüs (ab S. 46). In diesem Heft: TICKETSYSTEM Beantworten Sie Kundenanfragen mit vorgefertigten und

Mehr

Kennen, können, beherrschen lernen was gebraucht wird

Kennen, können, beherrschen lernen was gebraucht wird Formulare Formulare erstellen Was ist ein Formular? Formulare sind standardisierte Dokumente (z.b. Vordrucke, Formblätter) In Formularen sind spezielle Bereiche dafür vorgesehen, mit Informationen gefüllt

Mehr

Access [basics] Lookup-Daten per Formular verwalten. Beispieldatenbank. Ausgangsformular. Formular zum Bearbeiten von Lookup- Daten

Access [basics] Lookup-Daten per Formular verwalten. Beispieldatenbank. Ausgangsformular. Formular zum Bearbeiten von Lookup- Daten Lookup-Daten per Formular verwalten Wenn Sie Daten wie Anreden, Funktionen, Abteilungen et cetera in Lookup-Tabellen speichern und diese Daten in weiteren Tabellen per Nachschlagefeld verfügbar machen,

Mehr

Vorwort...10 Einleitung...12 Lernen Üben Anwenden...12 Inhalt und Aufbau des Buches...13 Inhalt...13 Aufbau Access 2007 (fast) alles ist

Vorwort...10 Einleitung...12 Lernen Üben Anwenden...12 Inhalt und Aufbau des Buches...13 Inhalt...13 Aufbau Access 2007 (fast) alles ist Vorwort...10 Einleitung...12 Lernen Üben Anwenden...12 Inhalt und Aufbau des Buches...13 Inhalt...13 Aufbau...14 1 Access 2007 (fast) alles ist neu...16 Sinnvolle Optionseinstellungen...17 Standarddatenbankordner

Mehr

öffnen den Tabellenverknüpfungs-Manager. Bild 1: Verknüpfte Tabellen in einer Datenbank Bild 2: Fehlgeschlagener Zugriff auf eine verknüpfte Tabelle

öffnen den Tabellenverknüpfungs-Manager. Bild 1: Verknüpfte Tabellen in einer Datenbank Bild 2: Fehlgeschlagener Zugriff auf eine verknüpfte Tabelle Der neue Tabellenverknüpfungs-Manager Still und heimlich wurde der alte Tabellen-Verknüpfungsmanager von Microsoft ausgetauscht. Der Tabellenverknüpfungs-Manager dient dazu, bestehende Verknüpfungen mit

Mehr

13 Unterprogramme erstellen

13 Unterprogramme erstellen 13 Unterprogramme erstellen»non prendere il lavoro come un nemico, e non farne nemmeno l'unica ragione della tua vita. Betrachte die Arbeit nicht als Feind und mache sie auch nicht zum einzigen Grund deines

Mehr

ACCESS IM UNTERNEHMEN GOOGLE MAPS. In diesem Heft: EINFACH FILTERN S MIT VORLAGE DYNAMISCHER ADRESSBLOCK SEITE 14 SEITE 53 SEITE 24

ACCESS IM UNTERNEHMEN GOOGLE MAPS. In diesem Heft: EINFACH FILTERN  S MIT VORLAGE DYNAMISCHER ADRESSBLOCK SEITE 14 SEITE 53 SEITE 24 Ausgabe 03/2017 GOOGLE MAPS Zeigen Sie Adressdaten mit Google Maps direkt im Access-Formular an (ab S. 37). In diesem Heft: EINFACH FILTERN Erweitern Sie Formulare um eine einfache Funktion zum Speichern

Mehr

Axel Tüting Version 1.1 zeit für das wesentliche TUTORIAL: SCHNELBAUSTEINE

Axel Tüting Version 1.1 zeit für das wesentliche TUTORIAL: SCHNELBAUSTEINE 2014 www.time4mambo.de Axel Tüting Version 1.1 zeit für das wesentliche TUTORIAL: SCHNELBAUSTEINE Inhalt Schnellbausteine... 3 Eigene Schnellbausteine... 4 Die verschiedenen Steuerelemente... 8 www.time4mambo.de

Mehr

Microsoft Access 2010 Bilder

Microsoft Access 2010 Bilder Microsoft Access 2010 Bilder Hyperlinks... arbeiten ähnlich wie ein Link in einer Webseite. sind ein Verweis auf eine Datei (access2010\material\beispiel\tabledevelop\automat.accdb). können ein Verweis

Mehr

Access [basics] Kunden per Bezeichnung verwalten. Beispieldatenbank. Kundenbezeichnung erwünscht. Separates Bezeichnungsfeld

Access [basics] Kunden per Bezeichnung verwalten. Beispieldatenbank. Kundenbezeichnung erwünscht. Separates Bezeichnungsfeld Beim Umgang mit Kundendaten benötigen Sie oft eine allgemeine Bezeichnung eines Kunden. Diese soll beispielsweise als Überschrift im Formular zur Verwaltung der Stammdaten eines Kunden dargestellt werden

Mehr

Funktionen in JavaScript

Funktionen in JavaScript Funktionen in JavaScript Eine Funktion enthält gebündelten Code, der sich in dieser Form wiederverwenden lässt. Mithilfe von Funktionen kann man denselben Code von mehreren Stellen des Programms aus aufrufen.

Mehr

1. Benennen Sie die Elemente der Access-Benutzeroberfläche: Wann müssen Sie einer Datenbank einen Namen geben?

1. Benennen Sie die Elemente der Access-Benutzeroberfläche: Wann müssen Sie einer Datenbank einen Namen geben? Fragenkatalog Access 2007 501 Benutzeroberfläche 1. Benennen Sie die Elemente der Access-Benutzeroberfläche: 2. Wann müssen Sie einer Datenbank einen Namen geben? o Beim Erstellen o Spätestens beim Schließen

Mehr

Access Grundlagen für Anwender. Sabine Spieß, Andrea Weikert. 1. Ausgabe, 1. Aktualisierung, September Trainermedienpaket ACC2010_TMP

Access Grundlagen für Anwender. Sabine Spieß, Andrea Weikert. 1. Ausgabe, 1. Aktualisierung, September Trainermedienpaket ACC2010_TMP Sabine Spieß, Andrea Weikert Access 2010 Grundlagen für Anwender 1. Ausgabe, 1. Aktualisierung, September 2012 Trainermedienpaket ACC2010_TMP 3 Access 2010 - Grundlagen für Anwender 3 Daten in Formularen

Mehr

1 Excel Schulung Andreas Todt

1 Excel Schulung Andreas Todt 1 Excel Schulung Andreas Todt Inhalt 1 Darum geht es hier... 1 2 So wird es gemacht... 1 2.1 Zellen und Blatt schützen... 1 2.2 Arbeitsmappe schützen... 5 2.3 Schritt für Schritt... 6 1 Darum geht es hier

Mehr

Kennen, können, beherrschen lernen was gebraucht wird

Kennen, können, beherrschen lernen was gebraucht wird Inhaltsverzeichnis Inhaltsverzeichnis... 1 Inhaltsverzeichnisse... 2 Ein Inhaltsverzeichnis erstellen... 2 Ein individuell erzeugtes Inhaltsverzeichnis erzeugen... 2 Ein Inhaltsverzeichnis mit manuell

Mehr

1. Benennen Sie die Elemente der Access-Benutzeroberfläche: Wann müssen Sie einer Datenbank einen Namen geben?

1. Benennen Sie die Elemente der Access-Benutzeroberfläche: Wann müssen Sie einer Datenbank einen Namen geben? Fragenkatalog Access 2003 Daniela Wagner 501 Benutzeroberfläche 1. Benennen Sie die Elemente der Access-Benutzeroberfläche: 2. Wann müssen Sie einer Datenbank einen Namen geben? o Beim Erstellen o Spätestens

Mehr

ACCESS GRAFIKEN MIT SVG IM UNTERNEHMEN. In diesem Heft: VEREINSVERWALTUNG: FORMULARE, TEIL 1 REFACTORING EINFACHE REIHENFOLGE SEITE 66

ACCESS GRAFIKEN MIT SVG IM UNTERNEHMEN. In diesem Heft: VEREINSVERWALTUNG: FORMULARE, TEIL 1 REFACTORING EINFACHE REIHENFOLGE SEITE 66 Ausgabe 03/2018 GRAFIKEN MIT SVG Peppen Sie Ihre Anwendung mit grafischen Darstellungen Ihrer Daten auf und nutzen Sie SVG dazu (ab S. 37) In diesem Heft: VEREINSVERWALTUNG: FORMULARE, TEIL 1 Entwickeln

Mehr

1. Einführung Hinweis:

1. Einführung Hinweis: 1. Einführung Sie haben die Möglichkeit, die verschiedenen Übersichten in orgamax um weitere Spalten zu ergänzen. Beispielsweise können Sie in der Kundenübersicht auch die Homepage des Kunden einblenden,

Mehr

Access [basics] Rechnen in Berichten. Beispieldatenbank. Datensatzweise berechnen. Berechnung im Textfeld. Reporting in Berichten Rechnen in Berichten

Access [basics] Rechnen in Berichten. Beispieldatenbank. Datensatzweise berechnen. Berechnung im Textfeld. Reporting in Berichten Rechnen in Berichten Berichte bieten die gleichen Möglichkeit zur Berechnung von Werten wie Formulare und noch einige mehr. Im Gegensatz zu Formularen bieten Berichte die Möglichkeit, eine laufende Summe zu bilden oder Berechnungen

Mehr

Rückgabewerte von Methoden

Rückgabewerte von Methoden OOP Rückgabewerte von Methoden Henrik Horstmann 14. September 2014 Inhaltsverzeichnis Inhaltsverzeichnis 1 Bedeutung der Symbole...1 2 Rückgabewerte von Methoden...2 3 Der freundliche Computer...2 3.1

Mehr

Schnellübersichten. ECDL Datenbanken mit Windows 10 und Access 2016

Schnellübersichten. ECDL Datenbanken mit Windows 10 und Access 2016 Schnellübersichten ECDL Datenbanken mit Windows 10 und Access 2016 1 Access kennenlernen 2 2 Access verwenden 3 3 Tabellen 4 4 Informationen abfragen 5 5 Formulare 6 6 Outputs 7 1 Access kennenlernen Datenbank

Mehr

OLConnector Programmierung

OLConnector Programmierung Das Vorgehen, um Outlook zu automatisieren, unterscheidet sich mit dem nur geringfügig vom üblicherweise dafür eingesetzten. Um irgendwelche Aktionen ausführen zu können, benötigt man die laufende Instanz

Mehr

Allgemeine Hinweise zum Erstellen einer einfachen Datenbank

Allgemeine Hinweise zum Erstellen einer einfachen Datenbank Allgemeine Hinweise zum Erstellen einer einfachen Datenbank 1. Tabellen Öffnen Sie das Programm MS-ACCESS durch Doppelklick auf das Symbol oder durch Auswahl des Programms in der Taskleiste Start Programme

Mehr

Funktionen nur wenn dann

Funktionen nur wenn dann Funktionen nur wenn dann Funktionen können auch nur in bestimmten Fällen angewendet werden. Code wird nur in einem bestimmten Fall ausgeführt Code Ja Code Block wahr if wahr? Nein else Code Block Alternative

Mehr

4 Makros ausprobieren

4 Makros ausprobieren 4 Makros ausprobieren Getreu dem Motto:»Val più la pratica che la grammatica Die Praxis ist mehr wert als die Grammatik«haben Sie jetzt zuerst einmal die Gelegenheit, die Funktionsweise von Makros auszuprobieren.

Mehr

Sage 50. Inventur. Impressum. Sage GmbH Emil-von-Behring-Str Frankfurt am Main

Sage 50. Inventur. Impressum. Sage GmbH Emil-von-Behring-Str Frankfurt am Main Sage 50 Inventur Impressum Sage GmbH Emil-von-Behring-Str. 8-14 60439 Frankfurt am Main Copyright 2016 Sage GmbH Die Inhalte und Themen in dieser Unterlage wurden mit sehr großer Sorgfalt ausgewählt, erstellt

Mehr

Word 2010 Formulare erstellen mit Inhaltssteuerelementen

Word 2010 Formulare erstellen mit Inhaltssteuerelementen WO.020, Version 1.0 23.09.2013 Kurzanleitung Word 2010 Formulare erstellen mit en Bei der Erstellung von Word-Formularen werden in den meisten Fällen sogenannte Formularfelder eingesetzt, also Platzhalter

Mehr

17 VBA-Praxisbeispiel

17 VBA-Praxisbeispiel 17 VBA-Praxisbeispiel Nicht nur in unserer Ferienappartementsiedlung Casa Maria, auch im Rest der Toskana hält man sich an das alte Sprichwort»Di giove e di marte non si sposa e non si parte. Donnerstags

Mehr

GS-Auftrag/GS-Office

GS-Auftrag/GS-Office GS-Auftrag/GS-Office Inventur Impressum Sage GmbH Emil-von-Behring-Str. 8-14 60439 Frankfurt am Main Copyright 2016 Sage GmbH Die Inhalte und Themen in dieser Unterlage wurden mit sehr großer Sorgfalt

Mehr

Datenbank und Tabelle mit SQL erstellen

Datenbank und Tabelle mit SQL erstellen Datenbank und Tabelle mit SQL erstellen 1) Übung stat Mit dem folgenden Befehlen legt man die Datenbank stat an und in dieser die Tabelle data1 : CREATE DATABASE stat; USE stat; CREATE TABLE data1 ( `id`

Mehr

AG-VIP App Erste Schritte

AG-VIP App Erste Schritte AG-VIP App Erste Schritte Seite: 1 AG-VIP App Erste Schritte Stand 26.02.2019 17:12:00 Autor Markus Grutzeck Grutzeck-Software GmbH Inhalt 1 Einrichtung... 1 1.1 Verbindungseinstellungen... 1 1.2 Anmeldung...

Mehr

Access Grundlagen für Anwender. Sabine Spieß. 1. Ausgabe, Dezember 2015 ISBN ACC2016

Access Grundlagen für Anwender. Sabine Spieß. 1. Ausgabe, Dezember 2015 ISBN ACC2016 Access 2016 Grundlagen für Anwender Sabine Spieß 1. Ausgabe, Dezember 2015 ACC2016 ISBN 978-3-86249-483-5 3 Dateneingabe in Formulare 3 1. Dateneingabe in Formulare 3.1 Basiswissen Formulare Beispieldatei:

Mehr

Attributs-Wertverteilungen / Distribution of Distinct Values Seite 1 von 5

Attributs-Wertverteilungen / Distribution of Distinct Values Seite 1 von 5 Attributs-Wertverteilungen / Distribution of Distinct Values 0.06.06 Seite von 5 D:\Projekte\ Access geändert Tupel ' 9.06.06 Attribute Abfrage-Beschreibung zoomen Abfrage-Dokumentation Abfrage-Dokumentation

Mehr

Sie haben mehrere Möglichkeiten neue Formulare zu erstellen. Achten Sie darauf, dass das Objekt Formulare aktiviert ist: Klicken Sie auf.

Sie haben mehrere Möglichkeiten neue Formulare zu erstellen. Achten Sie darauf, dass das Objekt Formulare aktiviert ist: Klicken Sie auf. 9. FORMULARE Mit Formularen können Sie sehr komfortabel Daten in eine Tabelle eingeben und auch anzeigen lassen, da Sie viele Eingabemöglichkeiten zur Verfügung haben. EIN EINFACHES FORMULAR ERSTELLEN

Mehr

Im Original veränderbare Word-Dateien

Im Original veränderbare Word-Dateien Die Eingabe der Feldnamen der beiden Tabellen in unsere Datenbank Bücherei geht recht zügig, weil wir uns bereits im Vorfeld Gedanken gemacht haben, welche Informationen über Bücher uns wichtig sind und

Mehr

Das Erstellen einer Datenbank in MS Access 2010

Das Erstellen einer Datenbank in MS Access 2010 Das Erstellen einer Datenbank in MS Access 2010 1. Eine Datenbank anlegen und 1.1. eine Tabelle entwerfen Leere Datenbank, erstellen anklicken vorher Pfad und definieren 1. 2. Es erscheint dieser Bildschirm

Mehr

Whitepaper. Produkt: combit Relationship Manager 5. Import von Adressen nach Firmen und Personen. combit GmbH Untere Laube Konstanz

Whitepaper. Produkt: combit Relationship Manager 5. Import von Adressen nach Firmen und Personen. combit GmbH Untere Laube Konstanz combit GmbH Untere Laube 30 78462 Konstanz Whitepaper Produkt: combit Relationship Manager 5 Import von Adressen nach Firmen und Personen Import von Adressen nach Firmen und Personen - 2 - Inhalt Ausgangssituation

Mehr

Access 2000 Programmierung

Access 2000 Programmierung Access 2000 Programmierung Bearbeitet von Irene Bauder, Jürgen Bär 1. Auflage 1999. Buch. 1168 S. Hardcover ISBN 978 3 446 21101 8 Format (B x L): 17,7 x 24,5 cm Gewicht: 1990 g schnell und portofrei erhältlich

Mehr

Flexible Bestellverwaltung

Flexible Bestellverwaltung Nr. 3 Juni 2013 Flexible Bestellverwaltung Verarbeiten Sie bestellte Positionen flexibel in Lieferungen und stellen Sie die Rechnungen nach Kundenwunsch zusammen (ab S. 2 und S. 24) Datenbanken anzeigen

Mehr

1 Erste Schritte...13

1 Erste Schritte...13 Inhalt 1 Erste Schritte...13 1.1 Access starten, Datenbank öffnen... 14 1.2 Eine neue Datenbank erstellen... 17 Mit einer leeren Datenbank beginnen... 18 Dateiformate... 19 1.3 Bestandteile einer Access

Mehr

myfactory.go! - Dokumente

myfactory.go! - Dokumente Tutorial: Wie arbeite ich mit der Dokumentenverwaltung? Immer und überall auf Firmen-Unterlagen zugreifen zu können, kann in manchen Situationen einen Wettbewerbsvorteil darstellen. Dieses Tutorial zeigt

Mehr

Formulare. Textverarbeitung Professionell 71

Formulare. Textverarbeitung Professionell 71 Formulare Im folgenden Kapitel lernen Sie Formulare zu erstellen und bearbeiten. Sie werden Formulare speichern, schützen und ausfüllen. Weiterhin werden Sie Formulare ausdrucken und die Feldfunktionen

Mehr

INTELLISENSE IN TEXTFELDERN NUTZEN. Statten Sie Textfelder mit einer automatischen Ergänzung der Eingabe um zuletzt verwendete Werte aus.

INTELLISENSE IN TEXTFELDERN NUTZEN. Statten Sie Textfelder mit einer automatischen Ergänzung der Eingabe um zuletzt verwendete Werte aus. Ausgabe 01/2015 ONLINEBANKING PER WEBSERVICE Statten Sie Ihre Datenbank mit Funktionen aus, um per Webservice BLZ und Kontonummer in IBAN und BIC zu konvertieren, Kontostände und -umsätze abzurufen oder

Mehr

IMS-Audit Pro. Kurzanleitung 2 / 14

IMS-Audit Pro. Kurzanleitung 2 / 14 Schneller Einstieg Version 11.2018 2 / 14 Inhaltsverzeichnis Inhaltsverzeichnis 1 Einleitung... 4 1.1 Installation... 4 1.2 Bildschirm Übersichten... 4 2 Stammdaten eintragen... 5 2.1 Mandanten anlegen...

Mehr

Handbuch zum VivaWeb-Serienbrief-Programm

Handbuch zum VivaWeb-Serienbrief-Programm Handbuch zum VivaWeb-Serienbrief-Programm In 10 Schritten zum Serienbrief Das folgende Handbuch erläutert Ihnen die Nutzungsmöglichkeiten des ARV Serienbrief-Programms in all seinen Einzelheiten. Dieses

Mehr

SCHULSPEZIFISCHEN ROLLENRECHTE

SCHULSPEZIFISCHEN ROLLENRECHTE Bei BASISDATEN > ADMINISTRATION organisieren Sie, wer SOKRATES an Ihrer Schule mit welchen Rechten nutzen kann. Außerdem können unter ADMINISTRATION mit SOKRATES intern Texte an andere Schulen geschickt

Mehr

Das Schönste am Computer ist doch die Nutzung des Internets, speziell des World Wide Web, in dem Sie Webseiten zu allen denkbaren Themen sowie

Das Schönste am Computer ist doch die Nutzung des Internets, speziell des World Wide Web, in dem Sie Webseiten zu allen denkbaren Themen sowie Im Web surfen mit dem 6 Das Schönste am Computer ist doch die Nutzung des Internets, speziell des World Wide Web, in dem Sie Webseiten zu allen denkbaren Themen sowie weitere Inhalte finden. Zum Durchforsten

Mehr

KOPF- UND FUßZEILEN KOPF- UND FUßZEILEN HINZUFÜGEN

KOPF- UND FUßZEILEN KOPF- UND FUßZEILEN HINZUFÜGEN WORD 2007/2010/2013 Seite 1 von 5 KOPF- UND FUßZEILEN Kopf- und Fußzeilen enthalten Informationen, die sich am Seitenanfang oder Seitenende eines Dokuments wiederholen. Wenn Sie Kopf- und Fußzeilen erstellen

Mehr

Das Grundlagenbuch zu FileMaker Pro 7- Datenbanken erfolgreich anlegen und verwalten

Das Grundlagenbuch zu FileMaker Pro 7- Datenbanken erfolgreich anlegen und verwalten Das Grundlagenbuch zu FileMaker Pro 7- Datenbanken erfolgreich anlegen und verwalten SMART BOOKS Inhaltsverzeichnis..««... Vorwort 13 Kapitel 1 - Einführung 17 Crashkurs: FileMaker Pro 7 anwenden 19 Eine

Mehr

[basics] Das Access-Magazin für alle, die schnell von 0 auf 100 wollen

[basics]   Das Access-Magazin für alle, die schnell von 0 auf 100 wollen Access [basics] Das Access-Magazin für alle, die schnell von 0 auf 100 wollen Editorial In dieser Ausgabe schauen wir uns mal wieder ein sehr grundlegendes Thema an die Bearbeitung von Daten in der Datenblattansicht.

Mehr

Visual Basic Express Fehlerermittlung

Visual Basic Express Fehlerermittlung Inhalt Dokument Beschreibung... 1 Fehlermeldungen anzeigen... 1 Fehlerkorrektur mit Fehlerliste... 2 Laufzeitfehler... 3 Arbeiten mit Haltepunkten... 4 Dokument Beschreibung Bei der Programmierung können

Mehr

Inhalt: Brainex Ihre persönliche Wissensdatenbank. Brainex Update Tool. Datenbanken aktualisieren Datenbank hinzufügen/anlegen

Inhalt: Brainex Ihre persönliche Wissensdatenbank. Brainex Update Tool. Datenbanken aktualisieren Datenbank hinzufügen/anlegen Inhalt: Brainex Ihre persönliche Wissensdatenbank Arbeitsbereich Benutzer anlegen Ordner/Eintrag anlegen Einträge bearbeiten Einträge suchen Dateien hinzufügen Änderungsprotokoll Vorlagen Benutzergruppen

Mehr

Formulare. Datenbankanwendung 113

Formulare. Datenbankanwendung 113 Formulare Wenn Sie mit sehr umfangreichen Tabellen arbeiten, werden Sie an der Datenblattansicht von Access nicht lange Ihre Freude haben, sind dort doch immer zu wenig Felder gleichzeitig sichtbar. Um

Mehr

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme Bisher Datentypen: einfach Zahlen, Wahrheitswerte, Zeichenketten zusammengesetzt Arrays (Felder) zur Verwaltung mehrerer zusammengehörender Daten desselben Datentypes eindimensional, mehrdimensional, Array-Grenzen

Mehr

Funktionen in JavaScript

Funktionen in JavaScript Funktionen in JavaScript Eine Funktion enthält gebündelten Code, der sich in dieser Form wiederverwenden lässt. Es können ganze Programmteile aufgenommen werden. Mithilfe von Funktionen kann man denselben

Mehr

NAVIGATOR MODUL 1 BESCHREIBUNG. Mit dem Navigator hat man die Möglichkeit in ASV bestimmte Daten zu selektieren, zu suchen und -Daten zu filtern.

NAVIGATOR MODUL 1 BESCHREIBUNG. Mit dem Navigator hat man die Möglichkeit in ASV bestimmte Daten zu selektieren, zu suchen und -Daten zu filtern. Büro/Verwaltung Willibald Heßlinger Multiplikator für das Schulverwaltungsprogramm asv MODUL 05 NAVIGATOR 1 BESCHREIBUNG Mit dem Navigator hat man die Möglichkeit in ASV bestimmte Daten zu selektieren,

Mehr

Verlag: readersplanet GmbH Neuburger Straße Passau.

Verlag: readersplanet GmbH Neuburger Straße Passau. MICROSOFT ACCESS 2010 - BASISWISSEN Verlag: readersplanet GmbH Neuburger Straße 108 94036 Passau http://www.readersplanet-fachbuch.de info@readersplanet-fachbuch.de Tel.: +49 851-6700 Fax: +49 851-6624

Mehr

ProMaSoft.de WordPDFFormular Version (1.083) Seite 1 von 7

ProMaSoft.de WordPDFFormular Version (1.083) Seite 1 von 7 ProMaSoft.de WordPDFFormular Version (1.083) Seite 1 von 7 Beschreibung Mit dem Modul WordPDFFormular können Sie Ihre eigenen PDF-Formulare in Word erstellen. Nutzen Sie alle Funktionen und Möglichkeiten

Mehr

3 Die Tabellen von PROJEKT. Tabellenerstellung in der Entwurfsansicht Dateneingabe direkt in die Tabelle Tabellen mit dem Tabellen-Assistenten anlegen

3 Die Tabellen von PROJEKT. Tabellenerstellung in der Entwurfsansicht Dateneingabe direkt in die Tabelle Tabellen mit dem Tabellen-Assistenten anlegen PROJEKT 3 Die Tabellen von Access 2002 TRAINING Access 2002 ISBN 3-8272-6192-9 Lektion 1 Lektion 2 Lektion 3 Lektion 4 Lektion 5 Lektion 6 Lektion 7 Lektion 8 Lektion 9 Lektion 10 Lektion 11 Lektion 12

Mehr

Word 10. Verweise Version: Relevant für: IKA, DA

Word 10. Verweise Version: Relevant für: IKA, DA Word 10 Verweise Version: 170220 Relevant für: IKA, DA 10-Word-Grundlagen.docx Inhaltsverzeichnis 1 Hinweise 1.1 Zu diesem Lehrmittel... 3 1.1.1 Arbeitsdateien zum Lehrmittel... 3 1.1.2 Vorkenntnisse...

Mehr

Listenfeldauswahl speichern und wiederherstellen

Listenfeldauswahl speichern und wiederherstellen Wie Sie die durch einen Benutzer ausgewählten Einträgen im Listenfeld auslesen, haben Sie bereits im Artikel "Mehrfachauswahl in Listenfeldern auslesen" erfahren. In diesem Folgeartikel geht es darum,

Mehr

GEVITAS MobileCatalog

GEVITAS MobileCatalog GEVITAS MobileCatalog Inhalt 1. Allgemeines... 2 2. Funktionsweise der App... 2 3. Das Hauptmenü... 3 4. Neuen Warenkorb anlegen... 4 5. Warenkorb auswählen und bearbeiten... 5 6. Warenkorb anzeigen...

Mehr

Arbeitsmappe. Die Arbeitsblatt-Register. 31 Die Arbeitsblatt-Register 32 Der Umgang mit Arbeitsmappen 34 Mustervorlagen. ADer Einstieg in Excel 2000

Arbeitsmappe. Die Arbeitsblatt-Register. 31 Die Arbeitsblatt-Register 32 Der Umgang mit Arbeitsmappen 34 Mustervorlagen. ADer Einstieg in Excel 2000 2 Die Arbeitsmappe 31 Die Arbeitsblatt-Register 32 Der Umgang mit Arbeitsmappen 34 Mustervorlagen Abbildung 2.1: Die einzelnen Blätter können Sie auf den Registern anwählen Mehrere Blätter markieren: Umschalt-Taste

Mehr

Kurzanleitung. Zitiertil-Creator. Dokumentvorlagen Dokumente Formatvorlagen Format Zeichen Format Absatz

Kurzanleitung. Zitiertil-Creator. Dokumentvorlagen Dokumente Formatvorlagen Format Zeichen Format Absatz Dokumentvorlagen Dokumente Formatvorlagen Format Zeichen Format Absatz Datei Neu... Datei öffnen Datei schließen Beenden Suchen Suchen & Ersetzen Verknüpfungen Optionen Einfügen Inhalte einfügen Format

Mehr

Was Sie bald kennen und können

Was Sie bald kennen und können Kontakte 6 In diesem Kapitel erfahren Sie, wie Sie Kontaktadressen im Outlook- Ordner Kontakte bequem und übersichtlich verwalten können. Es ist beispielsweise möglich, einen Absender einer E-Mail direkt

Mehr

Vorwort Einführung in Power Query Erste Abfrage erstellen... 21

Vorwort Einführung in Power Query Erste Abfrage erstellen... 21 Vorwort... 11 1 Einführung in Power Query... 13 1.1 Power Query installieren und aktivieren... 13 1.2 Power Query aktivieren bzw. deaktivieren... 14 Was tun, wenn das Register nicht angezeigt wird... 16

Mehr

Inhaltsverzeichnis. 1 Grundsätzliche Überlegungen. 2 Was macht ActAccess?

Inhaltsverzeichnis. 1 Grundsätzliche Überlegungen. 2 Was macht ActAccess? Inhaltsverzeichnis 1 Grundsätzliche Überlegungen...1 2 Was macht ActAccess?...1 3 Hinweise zur Installation...2 4 Aufbau der Muster.mdb...4 4.1 Die Tabelle ACT_Vorgaben...6 4.2 Die Tabelle ACT_Kontakt...7

Mehr

Funktionen nur wenn dann

Funktionen nur wenn dann Funktionen nur wenn dann Funktionen können auch nur in bestimmten Fällen angewendet werden. Code wird nur in einem bestimmten Fall ausgeführt Code Ja Code Block wahr if wahr? Nein else Code Block Alternative

Mehr

11.1 Warum überhaupt VBA?

11.1 Warum überhaupt VBA? 11 VBA ausprobieren»chi lascia la strada vecchia per la nuova, sa quel che lascia ma non quel che trova. Wer die alte Straße wegen der neuen verlässt, weiß, was er verlässt, aber nicht, was er findet.«in

Mehr

1 Einführung Was macht eine Datenbank? Entwickler und Benutzer Warum sollten Sie mit Datenbanken arbeiten?

1 Einführung Was macht eine Datenbank? Entwickler und Benutzer Warum sollten Sie mit Datenbanken arbeiten? Inhalt 1 Einführung 19 1.1 Was macht eine Datenbank? 19 1.2 Entwickler und Benutzer 20 1.3 Warum sollten Sie mit Datenbanken arbeiten? 20 1.4 Wer sollte dieses Buch lesen? 21 1.5 Wie sind relationale Datenbanken

Mehr

zur Verfügung gestellt durch das OpenOffice.org Dokumentations-Projekt

zur Verfügung gestellt durch das OpenOffice.org Dokumentations-Projekt Serienbriefe Wie Sie einen Serienbrief mittels Calc und Writer erstellen zur Verfügung gestellt durch das OpenOffice.org Dokumentations-Projekt Inhaltsverzeichnis 1. Erzeugen einer Quelldatei 2. Erzeugung

Mehr

Indizes. Index. Datenfeld Normale Tabelle. Gesucht wird: Zugriff. 3. Zugriff 1. Zugriff.

Indizes. Index. Datenfeld Normale Tabelle. Gesucht wird: Zugriff. 3. Zugriff 1. Zugriff. Indizes Gesucht wird: 44791 Index Normale Tabelle 1. Zugriff 1 44789 2. Zugriff 2 44801 3. Zugriff 3 44797 4. Zugriff 4 44388 5. Zugriff 5 44746 6. Zugriff 6 44787 7. Zugriff 7 44793 8. Zugriff 8 44799

Mehr

Verteilte Datenbanken

Verteilte Datenbanken Verteilte Datenbanken André Minhorst, Duisburg Kleinere Datenbankanwendungen mit wenigen Inhalt Benutzern und nicht allzu großem Datenverkehr 1 Gründe für verteilte Anwendungen... 1 können auch im Netzwerk

Mehr

Die SQL-Schnittstelle

Die SQL-Schnittstelle Die SQL-Schnittstelle Merlin 16 Version 16.0 vom 09.10.2012 Inhalt Die SQL-Export-Schnittstelle... 4 Der Menüpunkt Abfrage durchführen... 4 Beschreibung Fenster Abfrage durchführen... 4 Schaltflächen Fenster

Mehr

Word 10. Verweise: Textmarken, Hyperlinks, Querverweise Version: Relevant für:

Word 10. Verweise: Textmarken, Hyperlinks, Querverweise Version: Relevant für: Word 10 Verweise: Textmarken, Hyperlinks, Querverweise Version: 171014 Relevant für: 10-Word.docx Inhaltsverzeichnis 1 Hinweise 1.1 Zu diesem Lehrmittel... 3 1.1.1 Arbeitsdateien zum Lehrmittel... 3 1.1.2

Mehr