Base
 

Kapitel 9
Makros

Copyright

Dieses Dokument unterliegt dem Copyright © 2015. Die Beitragenden sind unten aufgeführt. Sie dürfen dieses Dokument unter den Bedingungen der GNU General Public License (http://www.­gnu.org/licenses/gpl.html), Version 3 oder höher, oder der Creative Commons Attribution License (http://creativecommons.org/licenses/by/3.0/), Version 3.0 oder höher, verändern und/oder weitergeben.

Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt.

Fast alle Hardware- und Softwarebezeichnungen und weitere Stichworte und sonstige Angaben, die in diesem Buch verwendet werden, sind als eingetragene Marken geschützt.

Da es nicht möglich ist, in allen Fällen zeitnah zu ermitteln, ob ein Markenschutz besteht, wird das Symbol (R) in diesem Buch nicht verwendet.

Mitwirkende/Autoren

Robert Großkopf

Jost Lange

Jochen Schiffers

Jürgen Thomas

Michael Niedermair

 

Rückmeldung (Feedback)

Kommentare oder Vorschläge zu diesem Dokument können Sie in deutscher Sprache an die Adresse discuss@de.libreoffice.org senden.

Vorsicht

Alles, was an eine Mailingliste geschickt wird, inklusive der E-Mail-Adresse und anderer persönlicher Daten, die die E-Mail enthält, wird öffentlich archiviert und kann nicht gelöscht werden. Also, schreiben Sie mit Bedacht!

Datum der Veröffentlichung und Softwareversion

Veröffentlicht am 15.2.2017. Basierend auf der LibreOffice Version 5.3.

Anmerkung für Macintosh Nutzer

Einige Tastenbelegungen (Tastenkürzel) und Menüeinträge unterscheiden sich zwischen der Macintosh Version und denen für Windows- und Linux-Rechnern. Die unten stehende Tabelle gibt Ihnen einige grundlegende Hinweise dazu. Eine ausführlichere Aufstellung dazu finden Sie in der Hilfedatei des jeweiligen Moduls.

Windows/Linux

entspricht am Mac

Effekt

Menü-Auswahl Extras → Optionen

LibreOffice → Einstellungen

Zugriff auf die Programmoptionen

Rechts-Klick

Control+Klick

Öffnen eines Kontextmenüs

Ctrl (Control)

oder Strg (Steuerung)

(Command)

Tastenkürzel in Verbindung mit anderen Tasten

F5

Shift++F5

Öffnen des Dokumentnavigator-Dialogs

F11

+T

Öffnen des Formatvorlagen-Dialogs

Inhalt

Allgemeines zu Makros

Der Makro-Editor

Benennung von Modulen, Dialogen und Bibliotheken

Makros in Base

Makros benutzen

Makros zuweisen

Ereignisse eines Formulars beim Öffnen oder Schließen des Fensters

Ereignisse eines Formulars bei geöffnetem Fenster

Ereignisse innerhalb eines Formulars

Bestandteile von Makros

Der «Rahmen» eines Makros

Variablen definieren

Arrays definieren

Zugriff auf das Formular

Zugriff auf Elemente eines Formulars

Zugriff auf die Datenbank

Datensätze lesen und benutzen

Datensätze bearbeiten – neu anlegen, ändern, löschen

Kontrollfelder prüfen und ändern

Englische Bezeichner in Makros

Eigenschaften bei Formularen und Kontrollfeldern

Methoden bei Formularen und Kontrollfeldern

Bedienbarkeit verbessern

Automatisches Aktualisieren von Formularen

Filtern von Datensätzen

Daten über den Formularfilter filtern

Daten aus Textfeldern auf SQL-Tauglichkeit vorbereiten

Werte in einem Formular vorausberechnen

Die aktuelle Office-Version ermitteln

Wert von Listenfeldern ermitteln

Listenfelder durch Eingabe von Anfangsbuchstaben einschränken

Datumswert aus einem Formularwert in eine Datumsvariable umwandeln

Suchen von Datensätzen

Suchen in Formularen und Ergebnisse farbig hervorheben

Rechtschreibkontrolle während der Eingabe

Kombinationsfelder als Listenfelder mit Eingabemöglichkeit

Textanzeige im Kombinationsfeld

Fremdschlüsselwert vom Kombinationsfeld zum numerischen Feld übertragen

Kontrollfunktion für die Zeichenlänge der Kombinationsfelder

Datensatzaktion erzeugen

Navigation von einem Formular zum anderen

Tabellen, Abfragen, Formulare und Berichte öffnen

Hierarchische Listenfelder

Zeiteingaben mit Millisekunden

Ein Ereignis – mehrere Implementationen

Abspeichern mit Nachfrage

Primärschlüssel aus Nummerierung und Jahreszahl

Datenbankaufgaben mit Makros erweitert

Verbindung mit Datenbanken erzeugen

Daten von einer Datenbank in eine andere kopieren

Direkter Import von Daten aus Calc

Zugriff auf Abfragen

Datenbanksicherungen erstellen

Datenbanken komprimieren

Tabellenindex heruntersetzen bei Autowert-Feldern

Drucken aus Base heraus

Druck von Berichten aus einem internen Formular heraus

Start, Formatierung, direkter Druck und Schließen des Berichts

Druck von Berichten aus einem externen Formular heraus

Serienbriefdruck aus Base heraus

Drucken über Textfelder

Aufruf von Anwendungen zum Öffnen von Dateien

Aufruf eines Mailprogramms mit Inhaltsvorgaben

Mauszeiger beim Überfahren eines Links ändern

Formulare ohne Symbolleisten präsentieren

Formulare ohne Symbolleisten in einem Fenster

Formulare im Vollbildmodus

Formular direkt beim Öffnen der Datenbankdatei starten

MySQL-Datenbank mit Makros ansprechen

MySQL-Code in Makros

Temporäre Tabelle als individueller Zwischenspeicher

Filterung über die Verbindungsnummer

Dialoge

Dialoge starten und beenden

Einfacher Dialog zur Eingabe neuer Datensätze

Dialog zum Bearbeiten von Daten in einer Tabelle

Fehleinträge von Tabellen mit Hilfe eines Dialogs bereinigen

Makrozugriff mit Access2Base

 

 

Allgemeines zu Makros

Prinzipiell kommt eine Datenbank unter Base ohne Makros aus. Irgendwann kann aber das Bedürfnis kommen,

Es ist natürlich jedem selbst überlassen, wie intensiv er/sie Makros in Base nutzen will. Makros können zwar die Bedienbarkeit verbessern, sind aber auch immer mit geringen, bei ungünstiger Programmierung auch stärkeren Geschwindigkeitseinbußen des Programms verbunden. Es ist immer besser, zuerst einmal die Möglichkeiten der Datenbank und die vorgesehenen Einstellmöglichkeiten in Formularen auszureizen, bevor mit Makros zusätzliche Funktionen bereitgestellt werden. Makros sollten deshalb auch immer wieder mit größeren Datenbanken getestet werden, um ihren Einfluss auf die Verarbeitungsgeschwindigkeit abschätzen zu können.

Makros werden über den Weg Extras → Makros → Makros verwalten → LibreOffice Basic... erstellt. Es erscheint ein Fenster, das den Zugriff auf alle Makros ermöglicht. Makros für Base werden meistens in dem Bereich gespeichert, der dem Dateinamen der Base-Datei entspricht.

 

Über den Button Neu im Fenster «LibreOffice Basic Makros» wird ein zweites Fenster geöffnet. Hier wird lediglich nach der Bezeichnung für das Modul (Ordner, in dem das Makro abgelegt wird) gefragt. Der Name kann gegebenenfalls auch noch später geändert werden.

Sobald dies bestätigt wird, erscheint der Makro-Editor und auf seiner Eingabefläche wird bereits der Start und das Ende für eine Prozedur angegeben:

REM  *****  BASIC  *****

 

Sub Main

 

End Sub

Um Makros, die dort eingegeben wurden, nutzen zu können, sind folgende Schritte notwendig:

Einige Grundprinzipien zur Nutzung des Basic-Codes in LibreOffice:

Zu weiteren Details siehe auch das Handbuch 'Erste Schritte Makros mit LibreOffice'.

Hinweis

Makros in diesem Kapitel sind entsprechend den Vorgaben aus dem Makro-Editor von LibreOffice eingefärbt:

Makro-Bezeichner
Makro-Kommentar
Makro-Operator
Makro-Reservierter-Ausdruck
Makro-Zahl
Makro-Zeichenkette

Der Makro-Editor

 

Der Objektkatalog auf der linken Seite zeigt alle zur Zeit verfügbaren Bibliotheken und darin Module an, die über ein Ereignis aufgerufen werden können. «Meine Makros & Dialoge» ist für alle Dokumente eines Benutzers verfügbar. «LibreOffice Makros & Dialoge» sind für alle Benutzer des Rechners und auch anderer Rechner nutzbar, da sie standardmäßig mit LibreOffice installiert werden. Hinzu kommen noch die Bibliotheken, die in dem jeweiligen Dokument, hier «Medien_mit_Makros.odb», abgespeichert sind.

Prinzipiell ist es zwar möglich, aus allen verfügbaren Bibliotheken die Module und die darin liegenden Makros zu nutzen. Für eine sinnvolle Nutzung empfiehlt es sich aber nicht, Makros aus anderen Dokumenten zu nutzen, da diese eben nur bei Öffnung des entsprechenden Dokumentes verfügbar sind. Ebenso ist es nicht empfehlenswert, Bibliotheken aus «Meine Makros & Dialoge» einzubinden, wenn die Datenbankdatei auch an andere Nutzer weitergegeben werden soll. Ausnahmen können hier Erweiterungen («Extensions») sein, die dann mit der Datenbankdatei weiter gegeben werden.

In dem Eingabebereich wird aus dem Modul «Aktualisierung» die Prozedur «Ausleihe_aktualisieren» angezeigt. Eingegebene Zeilen enden mit einem Return. Groß- und Kleinschreibung sowie Einrückung des Codes sind in Basic beliebig. Lediglich der Verweis auf Zeichenketten, z.B. "Filter", muss genau der Schreibweise in dem Formular entsprechen.

Makros können schrittweise für Testzwecke durchlaufen werden. Entsprechende Veränderungen der Variablen werden im Beobachter angezeigt.

Benennung von Modulen, Dialogen und Bibliotheken

Die Benennung von Modulen, Dialogen und Bibliotheken sollte erfolgen, bevor irgendein Makro in die Datenbank eingebunden wird. Sie definieren schließlich den Pfad, in dem das auslösende Ereignis nach dem Makro sucht.

Innerhalb einer Bibliothek kann auf alle Makros der verschiedenen Module zugegriffen werden. Sollen Makros anderer Bibliotheken genutzt werden, so müssen diese extra geladen werden:

GlobalScope.BasicLibraries.LoadLibrary("Tools")

lädt die Bibliothek «Tools», die eine Bibliothek von LibreOffice Makros ist.

 

Über Extras → Makros verwalten → LibreOffice Basic → Verwalten kann der obige Dialog aufgerufen werden. Hier können neue Module und Dialoge erstellt und mit einem Namen versehen werden. Die Namen können allerdings nicht hier, sondern nur in dem Makroeditor selbst verändert werden.

 

Im Makroeditor wird mit einem rechten Mausklick auf die Reiter mit der Modulbezeichnung direkt oberhalb der Suchleiste ein Kontextmenü geöffnet, das u.a. die Änderung des Modulnamens ermöglicht.

 

Neue Bibliotheken können innerhalb der Base-Datei angelegt werden. Die Bezeichnung «Standard» der ersten erstellten Bibliothek lässt sich nicht ändern. Die Namen der weiteren Bibliotheken sind frei wählbar, anschließend aber auch nicht änderbar. In eine Bibliothek können Makros aus anderen Bibliotheken importiert werden. Sollte also der dringende Wunsch bestehen, eine andere Bibliotheksbezeichnung zu erreichen, so müsste eine neue Bibliothek mit diesem Namen erstellt werden und sämtlicher Inhalt der alten Bibliothek in die neue Bibliothek exportiert werden. Dann kann anschließend die alte Bibliothek gelöscht werden.

Makros in Base

Makros benutzen

Der «direkte Weg» über Extras → Makros → Makros ausführen ist zwar auch möglich, aber bei Base-Makros nicht üblich. Ein Makro wird in der Regel einem Ereignis zugeordnet und durch dieses Ereignis gestartet.

Der «direkte Weg» ist vor allem dann nicht möglich auch nicht zu Testzwecken , wenn eines der Objekte thisComponent (siehe den Abschnitt «Zugriff auf das Formular») oder oEvent (siehe den Abschnitt «Zugriff auf Elemente eines Formulars») benutzt wird.

Makros zuweisen

Damit ein Makro durch ein Ereignis gestartet werden kann, muss es zunächst definiert werden (siehe den einleitenden Abschnitt «Allgemeines zu Makros»). Dann kann es einem Ereignis zugewiesen werden. Dafür gibt es vor allem zwei Stellen.

Ereignisse eines Formulars beim Öffnen oder Schließen des Fensters

Maßnahmen, die beim Öffnen oder Schließen eines Formulars erledigt werden sollen, werden so registriert:

Dann kann diese Zuweisung mit OK bestätigt werden.

Ereignisse eines Formulars bei geöffnetem Fenster

Nachdem das Fenster für die gesamten Inhalte des Formulars geöffnet wurde, kann auf die einzelnen Elemente des Formulars zugegriffen werden. Hierzu gehören auch die dem Formular zugeordneten Formularelemente.

Die Formularelemente können, wie in obigem Bild, über den Formularnavigator angesteuert werden. Sie sind genauso gut über jedes einzelne Kontrollfeld der Formularoberfläche über das Kontextmenü des Kontrollfeldes zu erreichen.

Die unter Formular-Eigenschaften → Ereignisse aufgezeigten Ereignisse finden statt während das Formularfenster geöffnet ist. Sie können für jedes Formular oder Unterformular des Formularfensters separat ausgewählt werden.

Hinweis

Der Gebrauch des Begriffes «Formular» ist bei Base leider nicht eindeutig. Der Begriff wird zum einen für das Fenster benutzt, das zur Eingabe von Daten geöffnet wird. Zum anderen wird der Begriff für das Element genutzt, das mit diesem Fenster eine bestimmte Datenquelle (Tabelle oder Abfrage) verbindet.

Es können auf einem Formularfenster sehr wohl mehrere Formulare mit unterschiedlichen Datenquelle untergebracht sein. Im Formularnavigator steht zuerst immer der Begriff «Formulare», dem dann in einem einfachen Formular lediglich ein Formular untergeordnet wird.

Ereignisse innerhalb eines Formulars

Alle anderen Makros werden bei den Eigenschaften von Teilformularen und Kontrollfeldern über das Register Ereignisse registriert.

Über mehrfaches OK wird diese Zuweisung bestätigt.

Bestandteile von Makros

In diesem Abschnitt sollen einige Teile der Makro-Sprache erläutert werden, die in Base vor allem bei Formularen immer wieder benutzt werden. (Soweit möglich und sinnvoll, werden dabei die Beispiele der folgenden Abschnitte benutzt.)

Der «Rahmen» eines Makros

Die Definition eines Makros beginnt mit dem Typ des Makros SUB oder FUNCTION   und endet mit END SUB bzw. END FUNCTION. Einem Makro, das einem Ereignis zugewiesen wird, können Argumente (Werte) übergeben werden; sinnvoll ist aber nur das Argument oEvent. Alle anderen Routinen, die von einem solchen Makro aufgerufen werden, können abhängig vom Zweck mit oder ohne Rückgabewert definiert werden und beliebig mit Argumenten versehen werden.

SUB Ausleihe_aktualisieren

END SUB

SUB Zu_Formular_von_Formular(oEvent AS OBJECT)

END SUB

FUNCTION Loeschen_bestaetigen(oEvent AS OBJECT) AS BOOLEAN

        Loeschen_bestaetigen = FALSE

END FUNCTION

Es ist hilfreich, diesen Rahmen sofort zu schreiben und den Inhalt anschließend einzufügen. Bitte vergessen Sie nicht, Kommentare zur Bedeutung des Makros nach dem Grundsatz «so viel wie nötig, so wenig wie möglich» einzufügen. Außerdem unterscheidet Basic nicht zwischen Groß- und Kleinschreibung. In der Praxis werden feststehende Begriffe wie SUB vorzugsweise groß geschrieben, während andere Bezeichner Groß- und Kleinbuchstaben mischen.

Variablen definieren

Im nächsten Schritt werden am Anfang der Routine mit der DIM-Anweisung die Variablen, die innerhalb der Routine vorkommen, mit dem jeweiligen Datentyp definiert. Basic selbst verlangt das nicht, sondern akzeptiert, dass während des Programmablaufs neue Variablen auftreten. Der Programmcode ist aber «sicherer», wenn die Variablen und vor allem die Datentypen festgelegt sind. Viele Programmierer verpflichten sich selbst dazu, indem sie Basic über Option Explicit gleich zu Beginn eines Moduls mitteilen: Erzeuge nicht automatisch irgendwelche Variablen, sondern nutze nur die, die ich auch vorher definiert habe.

 DIM oDoc AS OBJECT

 DIM oDrawpage AS OBJECT

        DIM oForm AS OBJECT

        DIM sName AS STRING

        DIM bOKEnabled AS BOOLEAN

        DIM iCounter AS INTEGER

        DIM dBirthday AS DATE

Für die Namensvergabe stehen nur Buchstaben (AZ oder az), Ziffern und der Unterstrich '_' zur Verfügung, aber keine Umlaute oder Sonderzeichen. (Unter Umständen ist das Leerzeichen zulässig. Sie sollten aber besser darauf verzichten.) Das erste Zeichen muss ein Buchstabe sein.

Üblich ist es, durch den ersten Buchstaben den Datentyp deutlich zu machen.2 Dann erkennt man auch mitten im Code den Typ der Variablen. Außerdem sind «sprechende Bezeichner» zu empfehlen, sodass die Bedeutung der Variablen schon durch den Namen erkannt werden kann.

Die Liste der möglichen Datentypen in Star-Basic steht im Anhang des Handbuches. An verschiedenen Stellen sind Unterschiede zwischen der Datenbank, von Basic und der LibreOffice-API zu beachten. Darauf wird bei den Beispielen hingewiesen.

Arrays definieren

Gerade für Datenbanken ist die Sammlung von mehreren Variablen in einem Datensatz von Bedeutung. Werden mehrere Variablen zusammen in einer gemeinsamen Variablen gespeichert, so wird dies als ein Array bezeichnet. Ein Array muss definiert werden, bevor Daten in das Array geschrieben werden können.

DIM arDaten()

erzeugt eine leeres Array.

arDaten = array("Lisa","Schmidt")

So wird ein Array auf eine bestimmte Größe von 2 Elementen festgelegt und gleichzeitig mit Daten versehen.

Über

print arDaten(0), arDaten(1)

werden die beiden definierten Inhalte auf dem Bildschirm ausgegeben. Die Zählung für die Felder beginnt hier mit 0.

DIM arDaten(2)

arDaten(0) = "Lisa"

arDaten(1) = "Schmidt"

arDaten(2) = "Köln"

Dies erstellt ein Array, in dem 3 Elemente beliebigen Typs gespeichert werden können, also z.B. ein Datensatz mit den Variablen «Lisa» «Schmidt» «Köln». Mehr passt leider in dieses Array nicht hinein. Sollen mehr Elemente gespeichert werden, so muss das Array vergrößert werden. Wird während der Laufzeit eines Makros allerdings die Größe eines Arrays einfach nur neu definiert, so ist das Array anschließend leer wie eben ein neues Array.

ReDIM Preserve arDaten(3)

arDaten(3) = "18.07.2003"

Durch den Zusatz Preserve werden die vorherigen Daten beibehalten, das Array also tatsächlich zusätzlich um die Datumseingabe, hier in Form eines Textes, erweitert.

Das oben aufgeführte Array kann leider nur einen einzelnen Satz an Daten speichern. Sollen stattdessen, wie in einer Tabelle einer Datenbank, mehrere Datensätze gespeichert werden, so muss dem Array zu Beginn eine zusätzliche Dimension hinzugefügt werden.

DIM arDaten(2,1)

arDaten(0,0) = "Lisa"

arDaten(1,0) = "Schmidt"

arDaten(2,0) = "Köln"

arDaten(0,1) = "Egon"

arDaten(1,1) = "Müller"

arDaten(2,1) = "Hamburg"

Auch hier gilt bei einer Erweiterung über das vorher definierte Maß hinaus, dass der Zusatz Preserve die vorher eingegebenen Daten mit übernimmt.

Zugriff auf das Formular

Das Formular liegt in dem momentan aktiven Dokument. Der Bereich, der dargestellt wird, wird als drawpage bezeichnet. Der Behälter, in dem alle Formulare aufbewahrt werden, heißt forms – im Formularnavigator ist dies sozusagen der oberste Begriff, an den dann sämtliche Formulare angehängt werden. Die o.g. Variablen erhalten auf diesem Weg ihre Werte:

 oDoc = thisComponent

 oDrawpage = oDoc.drawpage

 oForm = oDrawpage.forms.getByName("Filter")

Das Formular, auf das zugegriffen werden soll, ist hier mit dem Namen "Filter" versehen. Dies ist der Name, der auch im Formularnavigator in der obersten Ebene sichtbar ist. (Standardmäßig erhält das erste Formular den Namen "MainForm".) Unterformulare liegen – hierarchisch angeordnet – innerhalb eines Formulars und können Schritt für Schritt erreicht werden:

        DIM oSubForm AS OBJECT

        DIM oSubSubForm AS OBJECT

        oSubForm = oForm.getByName("Leserauswahl")

        oSubSubForm = oSubForm.getByName("Leseranzeige")

Anstelle der Variablen in den «Zwischenstufen» kann man auch direkt zu einem bestimmten Formular gelangen. Ein Objekt der Zwischenstufen, das mehr als einmal verwendet wird, sollte selbständig deklariert und zugewiesen werden. (Im folgenden Beispiel wird oSubForm nicht mehr benutzt.)

        oForm = thisComponent.drawpage.forms.getByName("Filter")

        oSubSubForm = oForm.getByName("Leserauswahl").getByName("Leseranzeige")

Hinweis

Sofern ein Name ausschließlich aus Buchstaben und Ziffern besteht (keine Umlaute, keine Leer- oder Sonderzeichen), kann der Name in der Zuweisung auch direkt verwendet werden:

        oForm = thisComponent.drawpage.forms.Filter
        oSubSubForm
= oForm.Leserauswahl.Leseranzeige

Anders als bei Basic sonst üblich, ist bei solchen Namen auf Groß- und Kleinschreibung genau zu achten.

Einen anderen Zugang zum Formular ermöglicht das auslösende Ereignis für das Makro.

Startet ein Makro über ein Ereignis des Formulars, wie z. B. Formular-Eigenschaften → Vor der Datensatzaktion, so wird das Formular selbst folgendermaßen erreicht:

SUB MakrobeispielBerechne(oEvent AS OBJECT)

        oForm = oEvent.Source

        ...

END SUB

Startet ein Makro über ein Ereignis eines Formularfeldes, wie z. B. Eigenschaften: Textfeld → Bei Fokusverlust, so kann sowohl das Formularfeld als auch das Formular ermittelt werden:

SUB MakrobeispielBerechne(oEvent AS OBJECT)

        oFeld = oEvent.Source.Model

        oForm = oFeld.Parent

        ...

END SUB

Die Zugriffe über das Ereignis haben den Vorteil, dass kein Gedanke darüber verschwendet werden muss, ob es sich bei dem Formular um ein Hauptformular oder Unterformular handelt. Auch interessiert der Name des Formulars für die Funktionsweise des Makros nicht.

Zugriff auf Elemente eines Formulars

In gleicher Weise kann man auf die Elemente eines Formulars zugreifen: Deklarieren Sie eine entsprechende Variable als object und suchen Sie das betreffende Kontrollfeld innerhalb des Formulars:

        DIM btnOK AS OBJECT  ' Button »OK»

        btnOK = oSubSubForm.getByName("Schaltfläche 1")   ' aus dem Formular Leseranzeige

Dieser Weg funktioniert immer dann, wenn bekannt ist, mit welchem Element das Makro arbeiten soll. Wenn aber im ersten Schritt zu prüfen ist, welches Ereignis das Makro gestartet hat, ist der o.g. Weg über oEvent sinnvoll. Dann wird die Variable innerhalb des Makro-"Rahmens" deklariert und beim Start des Makros zugewiesen. Die Eigenschaft Source liefert immer dasjenige Element, das das Makro gestartet hat; die Eigenschaft Model beschreibt das Kontrollfeld im Einzelnen:

SUB Auswahl_bestaetigen(oEvent AS OBJECT)

        DIM btnOK AS OBJECT

        btnOK = oEvent.Source.Model

END

Mit dem Objekt, das man auf diesem Weg erhält, werden die weiteren angestrebten Maßnahmen ausgeführt.

Bitte beachten Sie, dass auch Unterformulare als Bestandteile eines Formulars gelten.

Zugriff auf die Datenbank

Normalerweise wird der Zugriff auf die Datenbank über Formulare, Abfragen, Berichte oder die Serienbrief-Funktion geregelt, wie es in allen vorhergehenden Kapiteln beschrieben wurde. Wenn diese Möglichkeiten nicht genügen, kann ein Makro auch gezielt die Datenbank ansprechen, wofür es mehrere Wege gibt.

Die Verbindung zur Datenbank

Das einfachste Verfahren benutzt dieselbe Verbindung wie das Formular, wobei oForm wie oben bestimmt wird:

DIM oConnection AS OBJECT

oConnection = oForm.activeConnection()

Oder man holt die Datenquelle, also die Datenbank, durch das Dokument und benutzt die vorhandene Verbindung auch für das Makro:

DIM oDatasource AS OBJECT

DIM oConnection AS OBJECT

oDatasource = thisComponent.Parent.dataSource

oConnection = oDatasource.getConnection("","")

Ein weiterer Weg stellt sicher, dass bei Bedarf die Verbindung zur Datenbank hergestellt wird:

DIM oDatasource AS OBJECT

DIM oConnection AS OBJECT

oDatasource = thisComponent.Parent.CurrentController

IF NOT (oDatasource.isConnected()) THEN oDatasource.connect()

oConnection = oDatasource.ActiveConnection()

Die IF-Bedingung bezieht sich hier nur auf eine Zeile. Deshalb ist END IF nicht erforderlich.

Wenn das Makro durch die Benutzeroberfläche nicht aus einem Formulardokument heraus gestartet werden soll, ist folgende Variante geeignet:

DIM oDatasource AS OBJECT

DIM oConnection AS OBJECT

oDatasource = thisDatabaseDocument.CurrentController

IF NOT (oDatasource.isConnected()) THEN oDatasource.connect()

oConnection = oDatasource.ActiveConnection()

Der Zugriff auf Datenbanken außerhalb der aktuellen Datenbank ist folgendermaßen möglich:

DIM oDatabaseContext AS OBJECT

DIM oDatasource AS OBJECT

DIM oConnection AS OBJECT

oDatabaseContext = createUnoService("com.sun.star.sdb.DatabaseContext")

oDatasource = oDatabaseContext.getByName("angemeldeter Name der Datenbank in LO")

oConnection = oDatasource.GetConnection("","")

Auch die Verbindung zu nicht in LO angemeldete Datenbanken ist möglich. Hier muss dann lediglich statt des angemeldeten Namens der Pfad zur Datenbank mit «file:///..../Datenbank.odb» angegeben werden.

Ergänzende Hinweise zur Datenbankverbindung stehen im Abschnitt «Verbindung mit Datenbanken erzeugen».

SQL-Befehle

Die Arbeit mit der Datenbank erfolgt über SQL-Befehle. Ein solcher muss also erstellt und an die Datenbank geschickt werden; je nach Art des Befehls wird das Ergebnis ausgewertet und weiter verarbeitet. Mit der Anweisung createStatement wird das Objekt dafür erzeugt:

DIM oSQL_Statement AS OBJECT   ' das Objekt, das den SQL-Befehl ausführt

DIM stSql AS STRING            ' Text des eigentlichen SQL-Befehls

DIM oResult AS OBJECT          ' Ergebnis für executeQuery

DIM iResult AS INTEGER         ' Ergebnis für executeUpdate

oSQL_Statement = oConnection.createStatement()

Um Daten abzufragen, wird mit dem Befehl die Methode executeQuery aufgerufen und ausgeführt; das Ergebnis wird anschließend ausgewertet. Tabellennamen und Feldnamen werden üblicherweise in doppelte Anführungszeichen gesetzt. Diese müssen im Makro durch weitere doppelte Anführungszeichen maskiert werden, damit sie im Befehl erscheinen.

stSql = "SELECT * FROM ""Tabelle1"""

oResult = oSQL_Statement.executeQuery(stSql)

Um Daten zu ändern also für INSERT, UPDATE oder DELETE oder um die Struktur der Datenbank zu beeinflussen,  wird mit dem Befehl die Methode executeUpdate aufgerufen und ausgeführt. Je nach Art des Befehls und der Datenbank erhält man kein nutzbares Ergebnis (ausgedrückt durch die Zahl 0) oder die Anzahl der bearbeiteten Datensätze.

stSql = "DROP TABLE ""Suchtmp"" IF EXISTS"

iResult = oSQL_Statement.executeUpdate(stSql)

Der Vollständigkeit halber sei noch ein Spezialfall erwähnt: Wenn oSQL_Statement unterschiedlich für SELECT oder für andere Zwecke benutzt wird, steht die Methode execute zur Verfügung. Diese benutzen wir nicht; wir verweisen dazu auf die API-Referenz.

Vorbereitete SQL-Befehle mit Parametern

In allen Fällen, in denen manuelle Eingaben der Benutzer in einen SQL-Befehl übernommen werden, ist es einfacher und sicherer, den Befehl nicht als lange Zeichenkette zu erstellen, sondern ihn vorzubereiten und mit Parametern zu benutzen. Das vereinfacht die Formatierung von Zahlen, Datumsangaben und auch Zeichenketten (die ständigen doppelten Anführungszeichen entfallen) und verhindert Datenverlust durch böswillige Eingaben.

Bei diesem Verfahren wird zunächst das Objekt für einen bestimmten SQL-Befehl erstellt und vorbereitet:

DIM oSQL_Statement AS OBJECT   ' das Objekt, das den SQL-Befehl ausführt

DIM stSql AS STRING            ' Text des eigentlichen SQL-Befehls

stSql = "UPDATE Verfasser " _

      & "SET Nachname = ?, Vorname = ?" _

      & "WHERE ID = ?"

oSQL_Statement = oConnection.prepareStatement(stSql)

Das Objekt wird mit prepareStatement erzeugt, wobei der SQL-Befehl bereits bekannt sein muss. Jedes Fragezeichen markiert eine Stelle, an der später vor der Ausführung des Befehls ein konkreter Wert eingetragen wird. Durch das «Vorbereiten» des Befehls stellt sich die Datenbank darauf ein, welche Art von Angaben in diesem Fall zwei Zeichenketten und eine Zahl vorgesehen ist. Die verschiedenen Stellen werden durch die Position (ab 1 gezählt) unterschieden.

Anschließend werden mit passenden Anweisungen die Werte übergeben und danach der SQL-Befehl ausgeführt. Die Werte werden hier aus Kontrollfeldern des Formulars übernommen, können aber auch aus anderen Makro-Elementen stammen oder im Klartext angegeben werden:

oSQL_Statement.setString(1, oTextfeld1.Text)   ' Text für den Nachnamen

oSQL_Statement.setString(2, oTextfeld2.Text)   ' Text für den Vornamen

oSQL_Statement.setLong(3, oZahlenfeld1.Value)   ' Wert für die betreffende ID

iResult = oSQL_Statement.executeUpdate

Die vollständige Liste der Zuweisungen findet sich im Abschnitt «Parameter für vorbereitete SQL-Befehle».

Wer sich weiter über die Vorteile dieses Verfahrens informieren möchte, findet hier Erläuterungen:

Datensätze lesen und benutzen

Es gibt abhängig vom Zweck mehrere Wege, um Informationen aus einer Datenbank in ein Makro zu übernehmen und weiter zu verarbeiten.

Bitte beachten Sie: Wenn hier von einem «Formular» gesprochen wird, kann es sich auch um ein Unterformular handeln. Es geht dann immer über dasjenige (Teil-) Formular, das mit einer bestimmten Datenmenge verbunden ist.

Mithilfe des Formulars

Der aktuelle Datensatz und seine Daten stehen immer über das Formular zur Verfügung, das die betreffende Datenmenge (Tabelle, Abfrage, SELECT) anzeigt. Dafür gibt es mehrere Methoden, die mit get und dem Datentyp bezeichnet sind, beispielsweise diese:

DIM ID AS LONG

DIM sName AS STRING

DIM dValue AS CURRENCY

DIM dEintritt AS NEW com.sun.star.util.Date

ID = oForm.getLong(1)

sName = oForm.getString(2)

dValue = oForm.getDouble(4)

dEintritt = oForm.getDate(7)

Bei allen diesen Methoden ist jeweils die Nummer der Spalte in der Datenmenge anzugeben gezählt ab 1.

Hinweis

Bei allen Methoden, die mit Datenbanken arbeiten, wird ab 1 gezählt. Das gilt sowohl für Spalten als auch für Zeilen.

Möchte man anstelle der Spaltennummern mit den Spaltennamen der zugrundeliegenden Datenmenge (Tabelle, Abfrage, View) arbeiten, kann man die Spaltennummer über die Methode findColumn ermitteln hier ein Beispiel zum Auffinden der Spalte "Name":

DIM sName AS STRING

nName = oForm.findColumn("Name")

sName = oForm.getString(nName)

Man erhält immer einen Wert des Typs der Methode, wobei die folgenden Sonderfälle zu beachten sind.

Die vollständige Liste dieser Methoden findet sich im Abschnitt «Datenzeilen bearbeiten».

Tipp

Sollen Werte aus einem Formular für direkte Weiterverarbeitung in SQL genutzt werden (z.B. für die Eingabe der Daten in eine andere Tabelle), so ist es wesentlich einfacher, nicht nach dem Typ der Felder zu fragen.

Das folgende Makro, an Eigenschaften: Schaltfläche → Ereignisse → Aktion ausführen gekoppelt, liest das erste Feld des Formulars aus – unabhängig von dem für die Weiterverarbeitung in Basic erforderlichen Typ.

SUB WerteAuslesen(oEvent AS OBJECT)

        DIM oForm AS OBJECT

        DIM stFeld1 AS STRING

        oForm = oEvent.Source.Model.Parent

        stFeld1 = oForm.getString(1)

END SUB

Werden die Felder über getString() ausgelesen, so werden die Formatierungen beibehalten, die für eine Weiterverarbeitung in SQL notwendig sind. Ein Datum, das in einem deutschsprachigen Formular als '08.03.15' dargestellt wird, wird so im Format '2015-03-08' ausgelesen und kann direkt in SQL weiter verarbeitet werden.

Die Auslesung in dem dem Typ entsprechenden Format ist nur erforderlich, wenn im Makro Werte weiter verarbeitet, z.B. mit ihnen gerechnet werden soll.

Ergebnis einer Abfrage

In gleicher Weise kann die Ergebnismenge einer Abfrage benutzt werden. Im Abschnitt «SQL-Befehle» steht die Variable oResult für diese Ergebnismenge, die üblicherweise so oder ähnlich ausgelesen wird:

WHILE oResult.next         ' einen Datensatz nach dem anderen verarbeiten
        rem übernimm die benötigten Werte in Variablen
        stVar = oResult.getString(1)
        inVar = oResult.getLong(2)
        boVar = oResult.getBoolean(3)
        rem mach etwas mit diesen Werten

WEND

Je nach Art des SQL-Befehls, dem erwarteten Ergebnis und dem Zweck kann vor allem die WHILE-Schleife verkürzt werden oder sogar entfallen. Aber grundsätzlich wird eine Ergebnismenge immer nach diesem Schema ausgewertet.

Soll nur der erste Datensatz ausgewertet werden, so wird mit

oResult.next

zuerst die Zeile auf diesen Datensatz bewegt und dann mit

stVar = oResult.getString(1)

z.B. der Inhalt des ersten Datenfeldes gelesen. Die Schleife entfällt hier.

Die Abfrage zu dem obigen Beispiel hat in der ersten Spalte einen Text, in der zweiten Spalte einen Integer-Zahlenwert (Integer aus der Datenbank entspricht Long in Basic) und in der dritten Spalte ein Ja/Nein-Feld. Die Felder werden durch den entsprechenden Indexwert angesprochen. Der Index für die Felder beginnt hier, im Gegensatz zu der sonstigen Zählung bei Arrays, mit dem Wert '1'.

In dem so erstellten Ergebnis ist allerdings keine Navigation möglich. Nur einzelne Schritte zum nächsten Datensatz sind erlaubt. Um innerhalb der Datensätze navigieren zu können, muss der ResultSetType bei der Erstellung der Abfrage bekannt sein. Hierauf wird über

oSQL_Anweisung.ResultSetType = 1004

oder

oSQL_Anweisung.ResultSetType = 1005

zugegriffen. Der Typ 1004 - SCROLL_INTENSIVE erlaubt eine beliebige Navigation. Allerdings bleibt eine Änderung an den Originaldaten während des Auslesens unbemerkt. Der Typ 1005 – SCROLL_SENSITIVE berücksichtigt zusätzlich gegebenenfalls Änderungen an den Originaldaten, die das Abfrageergebnis beeinflussen könnten.

Die Anzahl der Zeilen, die die Ergebnismenge enthält, kann nur nach Wahl der entsprechenden Typen so bestimmt werden:

DIM iResult AS LONG

IF oResult.last                ' gehe zum letzten Datensatz, sofern möglich

        iResult = oResult.getRow    ' die laufende Nummer ist die Anzahl

ELSE

        iResult = 0

END IF

Mithilfe eines Kontrollfelds

Wenn ein Kontrollfeld mit einer Datenmenge verbunden ist, kann der Wert auch direkt ausgelesen werden, wie es im nächsten Abschnitt beschrieben wird. Das ist aber teilweise mit Problemen verbunden. Sicherer ist neben dem Verfahren «Mithilfe des Formulars» der folgende Weg, der für verschiedene Kontrollfelder gezeigt wird:

sValue = oTextField.BoundField.Text        ' Beispiel für ein Textfeld

nValue = oNumericField.BoundField.Value    ' Beispiel für ein numerisches Feld

dValue = oDateField.BoundField.Date        ' Beispiel für ein Datumsfeld

BoundField stellt dabei die Verbindung her zwischen dem (sichtbaren) Kontrollfeld und dem eigentlichen Inhalt der Datenmenge.

In einer Datenmenge navigieren

Im vorletzten Beispiel wurde mit der Methode Next von einer Zeile der Ergebnismenge zur nächsten gegangen. In gleicher Weise gibt es weitere Maßnahmen und Prüfungen, und zwar sowohl für die Daten eines Formulars angedeutet durch die Variable oForm als auch für eine Ergebnismenge. Beispielsweise kann man beim Verfahren «Automatisches Aktualisieren von Formularen» den vorher aktuellen Datensatz wieder markieren:

DIM loRow AS LONG

loRow = oForm.getRow()   ' notiere die aktuelle Zeilennummer

oForm.reload()          ' lade die Datenmenge neu

oForm.absolute(loRow)    ' gehe wieder zu der notierten Zeilennummer

Im Abschnitt «In einer Datenmenge navigieren» stehen alle dazu passenden Methoden.

Hinweis

Leider existiert seit den Anfängen von LO noch aus der OpenOffice-Zeit ein Bug in Formularen, der die aktuelle Zeilennummer bei Änderung von Daten in einem Formular auf '0' setzt: https://bugs.documentfoundation.org/show_bug.cgi?id=82591 . Hier kann folgende Ermittlung der aktuellen Zeilennummer an Formular → Eigenschaften → Ereignisse → Nach dem Datensatzwechsel gebunden werden

GLOBAL loRow AS LONG

SUB RowCounter(oEvent AS OBJECT)

        loRow = oEvent.Source.Row

END SUB

Die neue Zeilennummer wird ausgelesen und an die globale Variable loRow weiter gegeben. Diese Variable ist am Anfang aller Module zu positionieren und bleibt so lange mit ihrem Inhalt gefüllt, bis entweder Base beendet wird oder eben die Position durch erneutes Auslösen von RowCounter überschrieben wird.

Datensätze bearbeiten neu anlegen, ändern, löschen

Um Datensätze zu bearbeiten, müssen mehrere Teile zusammenpassen: Eine Information muss vom Anwender in das Kontrollfeld gebracht werden; das geschieht durch die Tastatureingabe. Anschließend muss die Datenmenge «dahinter» diese Änderung zur Kenntnis nehmen; das geschieht durch das Verlassen eines Feldes und den Wechsel zum nächsten Feld. Und schließlich muss die Datenbank selbst die Änderung erfahren; das erfolgt durch den Wechsel von einem Datensatz zu einem anderen.

Bei der Arbeit mit einem Makro müssen ebenfalls diese Teilschritte beachtet werden. Wenn einer fehlt oder falsch ausgeführt wird, gehen Änderungen verloren und «landen» nicht in der Datenbank. In erster Linie muss die Änderung nicht in der Anzeige des Kontrollfelds erscheinen, sondern in der Datenmenge. Es ist deshalb sinnlos, die Eigenschaft Text des Kontrollfelds zu ändern.

Bitte beachten Sie, dass nur Datenmengen vom Typ «Tabelle» problemlos geändert werden können. Bei anderen Datenmengen ist dies nur unter besonderen Bedingungen möglich.

Inhalt eines Kontrollfelds ändern

Wenn es um die Änderung eines einzelnen Wertes geht, wird das über die Eigenschaft BoundField des Kontrollfelds mit einer passenden Methode erledigt. Anschließend muss nur noch die Änderung an die Datenbank weitergegeben werden. Beispiel für ein Datumsfeld, in das das aktuelle Datum eingetragen werden soll:

DIM unoDate AS NEW com.sun.star.util.Date

unoDate.Year = Year(Date)

unoDate.Month = Month(Date)

unoDate.Day = Day(Date)

oDateField.BoundField.updateDate( unoDate )

oForm.updateRow()       ' Weitergabe der Änderung an die Datenbank

Für BoundField wird diejenige der updateXxx-Methoden aufgerufen, die zum Datentyp des Feldes passt hier geht es um einen Date-Wert. Als Argument wird der gewünschte Wert übergeben hier das aktuelle Datum, konvertiert in die vom Makro benötigte Schreibweise.

Zeile einer Datenmenge ändern

Wenn mehrere Werte in einer Zeile geändert werden sollen, ist der vorstehende Weg ungeeignet. Zum einen müsste für jeden Wert ein Kontrollfeld existieren, was oft nicht gewünscht oder sinnvoll ist. Zum anderen muss man sich für jedes dieser Felder ein Objekt «holen». Der einfache und direkte Weg geht über das Formular, beispielsweise so:

DIM unoDate AS NEW com.sun.star.util.Date

unoDate.Year = Year(Date)

unoDate.Month = Month(Date)

unoDate.Day = Day(Date)

oForm.updateDate(3, unoDate )

oForm.updateString(4, "ein Text")

oForm.updateDouble(6, 3.14)

oForm.updateInt(7, 16)

oForm.updateRow()

Für jede Spalte der Datenmenge wird die zum Datentyp passende updateXxx-Methode aufgerufen. Als Argumente werden die Nummer der Spalte (ab 1 gezählt) und der jeweils gewünschte Wert übergeben. Anschließend muss nur noch die Änderung an die Datenbank weitergegeben werden.

Zeilen anlegen, ändern, löschen

Die genannten Änderungen beziehen sich immer auf die aktuelle Zeile der Datenmenge des Formulars. Unter Umständen muss vorher eine der Methoden aus «In einer Datenmenge navigieren» aufgerufen werden. Es werden also folgende Maßnahmen benötigt:

  1. 1.Wähle den aktuellen Datensatz. 

  2. 2.Ändere die gewünschten Werte, wie im vorigen Abschnitt beschrieben. 

  3. 3.Bestätige die Änderungen mit folgendem Befehl:
    oForm.updateRow()  

  4. 4.Als Sonderfall ist es auch möglich, die Änderungen zu verwerfen und den vorherigen Zustand wiederherzustellen:
    oForm.cancelRowUpdates()  

Für einen neuen Datensatz gibt es eine spezielle Methode (vergleichbar mit dem Wechsel in eine neue Zeile im Tabellenkontrollfeld). Es werden also folgende Maßnahmen benötigt:

  1. 1.Bereite einen neuen Datensatz vor:
    oForm.moveToInsertRow()  

  2. 2.Trage alle vorgesehenen und benötigten Werte ein. Dies geht ebenfalls mit den updateXxx-Methoden, wie im vorigen Abschnitt beschrieben. 

  3. 3.Bestätige die Neuaufnahme mit folgendem Befehl:
    oForm.insertRow()  

  4. 4.Die Neuaufnahme kann nicht einfach rückgängig gemacht werden. Stattdessen ist die soeben neu angelegte Zeile wieder zu löschen. 

Für das Löschen eines Datensatzes gibt es einen einfachen Befehl; es sind also folgende Maßnahmen nötig:

  1. 1.Wähle wie für eine Änderung den gewünschten Datensatz und mache ihn zum aktuellen. 

  2. 2.Bestätige die Löschung mit folgendem Befehl:
    oForm.deleteRow()  

Tipp

Damit eine Änderung in die Datenbank übernommen wird, ist sie durch updateRow bzw. insertRow ausdrücklich zu bestätigen. Während beim Betätigen des Speicher-Buttons die passende Funktion automatisch ermittelt wird, muss vor dem Abspeichern ermittelt werden, ob der Datensatz neu ist (Insert) oder ein bestehender Datensatz bearbeitet wurde (Update).

IF oForm.isNew THEN

        oForm.insertRow()

ELSE

        oForm.updateRow()

END IF

Kontrollfelder prüfen und ändern

Neben dem Inhalt, der aus der Datenmenge kommt, können viele weitere Informationen zu einem Kontrollfeld gelesen, verarbeitet und geändert werden. Das betrifft vor allem die Eigenschaften, die im Kapitel «Formulare» aufgeführt werden. Eine Übersicht steht im Abschnitt «Eigenschaften bei Formularen und Kontrollfeldern».

In mehreren Beispielen des Abschnitts «Bedienbarkeit verbessern» wird die Zusatzinformation eines Feldes benutzt:

DIM stTag AS STRING

stTag = oEvent.Source.Model.Tag

Die Eigenschaft Text kann wie im vorigen Abschnitt erläutert nur dann sinnvoll geändert werden, wenn das Feld nicht mit einer Datenmenge verbunden ist. Aber andere Eigenschaften, die «eigentlich» bei der Formulardefinition festgelegt werden, können zur Laufzeit angepasst werden. Beispielsweise kann in einem Beschriftungsfeld die Textfarbe gewechselt werden, wenn statt einer Meldung ein Hinweis oder eine Warnung angezeigt werden soll:

SUB showWarning(oField AS OBJECT, iType AS INTEGER)

        SELECT CASE iType

                CASE 1

                        oField.TextColor = RGB(0,0,255)   ' 1 = blau

                CASE 2

                        oField.TextColor = RGB(255,0,0)   ' 2 = rot

                CASE ELSE

                        oField.TextColor = RGB(0,255,0)   ' 0 = grün (weder 1 noch 2)

        END SELECT

END SUB

Englische Bezeichner in Makros

Während der Formular-Designer in der deutschen Version auch deutsche Bezeichnungen für die Eigenschaften und den Datenzugriff verwendet, müssen in Basic englische Begriffe verwendet werden. Diese sind in den folgenden Übersichten aufgeführt.

Eigenschaften, die üblicherweise nur in der Formular-Definition festgelegt werden, stehen nicht in den Übersichten. Gleiches gilt für Methoden (Funktionen und Prozeduren), die nur selten verwendet werden oder für die kompliziertere Erklärungen nötig wären.

Die Übersichten nennen folgende Angaben:

Weitere Informationen finden sich vor allem in der API-Referenz mit Suche nach der englischen Bezeichnung des Kontrollfelds. Gut geeignet, um herauszufinden, welche Eigenschaften und Methoden denn eigentlich bei einem Element zur Verfügung stehen, ist auch das Tool Xray .

SUB Main(oEvent)

        Xray(oEvent)

END SUB

Hiermit wird die Erweiterung Xray aus dem Aufruf heraus gestartet.

Eigenschaften bei Formularen und Kontrollfeldern

Das «Modell» eines Kontrollfelds beschreibt seine Eigenschaften. Je nach Situation kann der Wert einer Eigenschaft nur gelesen und nur geändert werden. Die Reihenfolge orientiert sich an den Aufstellungen «Eigenschaften der Kontrollfelder» im Kapitel «Formular».

Schrift

In jedem Kontrollfeld, das Text anzeigt, können die Eigenschaften der Schrift angepasst werden.

Name

Datentyp

L/S

Eigenschaft

FontName

string

L+S

Schriftart.

FontHeight

single

L+S

Schriftgröße.

FontWeight

single

L+S

Schriftstärke.

FontSlant

integer

L+S

Art der Schrägstellung.

FontUnderline

integer

L+S

Art der Unterstreichung.

FontStrikeout

integer

L+S

Art des Durchstreichens.

Formular

Englische Bezeichnung: Form

Name

Datentyp

L/S

Eigenschaft

ApplyFilter

boolean

L+S

Filter aktiviert.

Filter

string

L+S

Aktueller Filter für die Datensätze.

FetchSize

long

L+S

Anzahl der Datensätze, die «am Stück» geladen werden.

Row

long

L

Nummer der aktuellen Zeile.

RowCount

long

L

Anzahl der Datensätze.

Einheitlich für alle Arten von Kontrollfeld

Englische Bezeichnung: Control siehe auch FormComponent

Name

Datentyp

L/S

Eigenschaft

Name

string

L+(S)

Bezeichnung für das Feld.

Enabled

boolean

L+S

Aktiviert: Feld kann ausgewählt werden.

EnableVisible

boolean

L+S

Sichtbar: Feld wird dargestellt.

ReadOnly

boolean

L+S

Nur lesen: Inhalt kann nicht geändert werden.

TabStop

boolean

L+S

Feld ist in der Tabulator-Reihenfolge erreichbar.

Align

integer

L+S

Horizontale Ausrichtung:
0 = links, 1 = zentriert, 2 = rechts

BackgroundColor

long

L+S

Hintergrundfarbe.

Tag

string

L+S

Zusatzinformation.

HelpText

string

L+S

Hilfetext als «Tooltip».

Einheitlich für viele Arten von Kontrollfeld

Name

Datentyp

L/S

Eigenschaft

Text

string

(L+S)

Inhalt des Feldes aus der Anzeige. Bei Textfeldern nach dem Lesen auch zur weiteren Verarbeitung geeignet, andernfalls nur in Ausnahmefällen.

Spin

boolean

L+S

Drehfeld eingeblendet (bei formatierten Feldern).

TextColor

long

L+S

Textfarbe.

DataField

string

L

Name des Feldes aus der Datenmenge

BoundField

object

L

Objekt, das die Verbindung zur Datenmenge herstellt und vor allem dem Zugriff auf den Feldinhalt dient.

Textfeld weitere Angaben

Englische Bezeichnung: TextField

Name

Datentyp

L/S

Eigenschaft

String

string

L+S

Inhalt des Feldes aus der Anzeige.

MaxTextLen

integer

L+S

Maximale Textlänge.

DefaultText

string

L+S

Standardtext.

MultiLine

boolean

L+S

Mehrzeilig oder einzeilig.

EchoChar

(integer)

L+S

Zeichen für Kennwörter (Passwort-Eingabe verstecken).

Numerisches Feld

Englische Bezeichnung: NumericField

Name

Datentyp

L/S

Eigenschaft

ValueMin

double

L+S

Minimalwert zur Eingabe.

ValueMax

double

L+S

Maximalwert zur Eingabe.

Value

double

L+(S)

Aktueller Wert
nicht für Werte aus der Datenmenge verwenden.

ValueStep

double

L+S

Intervall bei Verwendung mit Mausrad oder Drehfeld.

DefaultValue

double

L+S

Standardwert.

DecimalAccuracy

integer

L+S

Nachkommastellen.

ShowThousandsSeparator

boolean

L+S

Tausender-Trennzeichen anzeigen.

Datumsfeld

Englische Bezeichnung: DateField

Datumswerte werden als Datentyp long definiert und im ISO-Format YYYYMMDD angezeigt, also 20120304 für den 04.03.2012. Zur Verwendung dieses Typs zusammen mit getDate und updateDate sowie dem Typ com.sun.star.util.Date verweisen wir auf die Beispiele.

Name

Daten-typ

Datentyp ab LO 4.1.1

L/S

Eigenschaft

DateMin

long

com.sun.star.util.Date

L+S

Minimalwert zur Eingabe.

DateMax

long

com.sun.star.util.Date

L+S

Maximalwert zur Eingabe.

Date

long

com.sun.star.util.Date

L+(S)

Aktueller Wert
nicht für Werte aus der Datenmenge verwenden.

DateFormat

integer

 

L+S

Datumsformat nach Festlegung des Betriebssystems:
0 = kurze Datumsangabe (einfach)
1 = kurze Datumsangabe tt.mm.jj (Jahr zweistellig)
2 = kurze Datumsangabe tt.mm.jjjj (Jahr vierstellig)
3 = lange Datumsangabe (mit Wochentag und Monatsnamen)
Weitere Möglichkeiten sind der Formulardefinition oder der
API-Referenz zu entnehmen.

DefaultDate

long

com.sun.star.util.Date

L+S

Standardwert.

DropDown

boolean

 

L+S

Aufklappbaren Monatskalender anzeigen.

Zeitfeld

Englische Bezeichnung: TimeField

Auch Zeitwerte werden als Datentyp long definiert.

Name

Daten-typ

Datentyp ab LO 4.1.1

L/S

Eigenschaft

TimeMin

long

com.sun.star.util.Time

L+S

Minimalwert zur Eingabe.

TimeMax

long

com.sun.star.util.Time

L+S

Maximalwert zur Eingabe.

Time

long

com.sun.star.util.Time

L+(S)

Aktueller Wert
nicht für Werte aus der Datenmenge verwenden.

TimeFormat

integer

 

L+S

Zeitformat:
0 = kurz als hh:mm (Stunde, Minute, 24 Stunden)
1 = lang als hh:mm:ss (dazu Sekunden, 24 Stunden)
2 = kurz als hh:mm (12 Stunden AM/PM)
3 = lang als hh:mm:ss (12 Stunden AM/PM)
4 = als kurze Angabe einer Dauer
5 = als lange Angabe einer Dauer

DefaultTime

long

com.sun.star.util.Time

L+S

Standardwert.

Währungsfeld

Englische Bezeichnung: CurrencyField

Ein Währungsfeld ist ein numerisches Feld mit den folgenden zusätzlichen Möglichkeiten.

Name

Datentyp

L/S

Eigenschaft

CurrencySymbol

string

L+S

Währungssymbol (nur zur Anzeige).

PrependCurrencySymbol

boolean

L+S

Anzeige des Symbols vor der Zahl.

Formatiertes Feld

Englische Bezeichnung: FormattedControl

Ein formatiertes Feld wird wahlweise für Zahlen, Währungen oder Datum/Zeit verwendet. Sehr viele der bisher genannten Eigenschaften gibt es auch hier, aber mit anderer Bezeichnung.

Name

Datentyp

L/S

Eigenschaft

CurrentValue

variant

L

Aktueller Wert des Inhalts; der konkrete Datentyp hängt vom Inhalt des Feldes und dem Format ab.

EffectiveValue

L+(S)

EffectiveMin

double

L+S

Minimalwert zur Eingabe.

EffectiveMax

double

L+S

Maximalwert zur Eingabe.

EffectiveDefault

variant

L+S

Standardwert.

FormatKey

long

L+(S)

Format für Anzeige und Eingabe. Es gibt kein einfaches Verfahren, das Format durch ein Makro zu ändern.

EnforceFormat

boolean

L+S

Formatüberprüfung: Bereits während der Eingabe sind nur zulässige Zeichen und Kombinationen möglich.

Listenfeld

Englische Bezeichnung: ListBox

Der Lese- und Schreibzugriff auf den Wert, der hinter der ausgewählten Zeile steht, ist etwas umständlich, aber möglich.

Name

Datentyp

L/S

Eigenschaft

ListSource

array of string

L+S

Datenquelle: Herkunft der Listeneinträge oder Name der Datenmenge, die die Einträge liefert.

ListSourceType

integer

L+S

Art der Datenquelle:
0 = Werteliste
1 = Tabelle
2 = Abfrage
3 = Ergebnismenge eines SQL-Befehls
4 = Ergebnis eines Datenbank-Befehls
5 = Feldnamen einer Datenbank-Tabelle

StringItemList

array of string

L

Listeneinträge, die zur Auswahl zur Verfügung stehen.

ItemCount

integer

L

Anzahl der vorhandenen Listeneinträge.

ValueItemList

array of string

L

Liste der Werte, die über das Formular an die Tabelle weitergegeben werden.

DropDown

boolean

L+S

Aufklappbar.

LineCount

integer

L+S

Anzahl der angezeigten Zeilen im aufgeklappten Zustand.

MultiSelection

boolean

L+S

Mehrfachselektion vorgesehen.

SelectedItems

array of integer

L+S

Liste der ausgewählten Einträge, und zwar als Liste der Positionen in der Liste aller Einträge.

Das (erste) ausgewählte Element aus dem Listenfeld erhält man auf diesem Weg:

oControl = oForm.getByName("Name des Listenfelds")

sEintrag = oControl.ValueItemList( oControl.SelectedItems(0) )

Hinweis

Seit LO 4.1 wird direkt der Wert ermittelt, der bei einem Listenfeld an die Datenbank weitergegeben wird.

oControl = oForm.getByName("Name des Listenfelds")

iD = oControl.getCurrentValue()

Mit getCurrentValue() wird also immer der Wert ausgegeben, der auch tatsächlich in der Tabelle der Datenbank abgespeichert wird. Dies ist beim Listenfeld von dem hiermit verknüpften gebundenen Feld ( BoundField ) abhängig.

Bis einschließlich LO 4.0 wurde hier immer der angezeigte Inhalt, nicht aber der an die darunterliegende Tabelle weitergegebene Wert wiedergegeben.

Soll für die Einschränkung einer Auswahlmöglichkeit die Abfrage für ein Listenfeld ausgetauscht werden, so ist dabei zu beachten, dass es sich bei dem Eintrag um ein «array of string» handelt:

SUB Listenfeldfilter

        DIM stSql(0) AS STRING

        DIM oDoc AS OBJECT

        DIM oDrawpage AS OBJECT

        DIM oForm AS OBJECT

        DIM oFeld AS OBJECT

 oDoc = thisComponent

        oDrawpage = oDoc.drawpage

        oForm = oDrawpage.forms.getByName("MainForm")

        oFeld = oForm.getByname("Listenfeld")

        stSql(0) = "SELECT ""Name"", ""ID"" FROM ""Filter_Name"" ORDER BY ""Name"""

        oFeld.ListSource = stSql

        oFeld.refresh

END SUB

Kombinationsfeld

Englische Bezeichnung: ComboBox

Trotz ähnlicher Funktionalität wie beim Listenfeld weichen die Eigenschaften teilweise ab.

Hier verweisen wir ergänzend auf das Beispiel «Kombinationsfelder als Listenfelder mit Eingabemöglichkeit».

Name

Datentyp

L/S

Eigenschaft

Autocomplete

boolean

L+S

Automatisch füllen.

StringItemList

array of string

L+S

Listeneinträge, die zur Auswahl zur Verfügung stehen.

ItemCount

integer

L

Anzahl der vorhandenen Listeneinträge.

DropDown

boolean

L+S

Aufklappbar.

LineCount

integer

L+S

Anzahl der angezeigten Zeilen im aufgeklappten Zustand.

Text

string

L+S

Aktuell angezeigter Text.

DefaultText

string

L+S

Standardeintrag.

ListSource

string

L+S

Name der Datenquelle, die die Listeneinträge liefert.

ListSourceType

integer

L+S

Art der Datenquelle; gleiche Möglichkeiten wie beim Listenfeld (nur die Auswahl «Werteliste» wird ignoriert).

Markierfeld, Optionsfeld

Englische Bezeichnungen: CheckBox (Markierfeld) bzw. RadioButton (Optionsfeld; auch «Option Button» möglich)

Name

Datentyp

L/S

Eigenschaft

Label

string

L+S

Titel (Beschriftung)

State

short

L+S

Status
0 = nicht ausgewählt
1 = ausgewählt
2 = unbestimmt

MultiLine

boolean

L+S

Wortumbruch (bei zu langem Text).

RefValue

string

L+S

Referenzwert

Maskiertes Feld

Englische Bezeichnung: PatternField

Neben den Eigenschaften für «einfache» Textfelder sind folgende interessant.

Name

Datentyp

L/S

Eigenschaft

EditMask

string

L+S

Eingabemaske.

LiteralMask

string

L+S

Zeichenmaske.

StrictFormat

boolean

L+S

Formatüberprüfung bereits während der Eingabe.

Tabellenkontrollfeld

Englische Bezeichnung: GridControl

Name

Datentyp

L/S

Eigenschaft

Count

long

L

Anzahl der Spalten.

ElementNames

array of string

L

Liste der Spaltennamen.

HasNavigationBar

boolean

L+S

Navigationsleiste vorhanden.

RowHeight

long

L+S

Zeilenhöhe.

Beschriftungsfeld

Englische Bezeichnung: FixedText auch Label ist üblich

Name

Datentyp

L/S

Eigenschaft

Label

string

L+S

Der angezeigte Text.

MultiLine

boolean

L+S

Wortumbruch (bei zu langem Text).

Gruppierungsrahmen

Englische Bezeichnung: GroupBox

Keine Eigenschaft dieses Kontrollfelds wird üblicherweise durch Makros bearbeitet. Wichtig ist der Status der einzelnen Optionsfelder.

Schaltfläche

Englische Bezeichnungen: CommandButton für die grafische Schaltfläche ImageButton

Name

Datentyp

L/S

Eigenschaft

Label

string

L+S

Titel Text der Beschriftung.

State

short

L+S

Standardstatus «ausgewählt» bei «Umschalten».

MultiLine

boolean

L+S

Wortumbruch (bei zu langem Text).

DefaultButton

boolean

L+S

Standardschaltfläche

Navigationsleiste

Englische Bezeichnung: NavigationBar

Weitere Eigenschaften und Methoden, die mit der Navigation zusammenhängen z.B. Filter und das Ändern des Datensatzzeigers –, werden über das Formular geregelt.

Name

Datentyp

L/S

Eigenschaft

IconSize

short

L+S

Symbolgröße.

ShowPosition

boolean

L+S

Positionierung anzeigen und eingeben.

ShowNavigation

boolean

L+S

Navigation ermöglichen.

ShowRecordActions

boolean

L+S

Datensatzaktionen ermöglichen.

ShowFilterSort

boolean

L+S

Filter und Sortierung ermöglichen.

Methoden bei Formularen und Kontrollfeldern

Die Datentypen der Parameter werden durch Kürzel angedeutet:

In einer Datenmenge navigieren

Diese Methoden gelten sowohl für ein Formular als auch für die Ergebnismenge einer Abfrage.

Mit «Cursor» ist in den Beschreibungen der Datensatzzeiger gemeint.

Name

Datentyp

Beschreibung

Prüfungen für die Position des Cursors

isBeforeFirst

boolean

Der Cursor steht vor der ersten Zeile, wenn der Cursor nach dem Einlesen noch nicht gesetzt wurde.

isFirst

boolean

Gibt an, ob der Cursor auf der ersten Zeile steht.

isLast

boolean

Gibt an, ob der Cursor auf der letzten Zeile steht.

isAfterLast

boolean

Der Cursor steht hinter der letzten Zeile, wenn er von der letzten Zeile aus mit next weiter gesetzt wurde.

getRow

long

Nummer der aktuellen Zeile

Setzen des Cursors
Beim Datentyp boolean steht das Ergebnis «true» dafür, dass das Navigieren erfolgreich war.

beforeFirst

Wechselt vor die erste Zeile.

first

boolean

Wechselt zur ersten Zeile.

previous

boolean

Geht um eine Zeile zurück.

next

boolean

Geht um eine Zeile vorwärts.

last

boolean

Wechselt zur letzten Zeile.

afterLast

Wechselt hinter die letzte Zeile.

absolute(n)

boolean

Geht zu der Zeile mit der angegebenen Nummer.

relative(n)

boolean

Geht um eine bestimmte Anzahl von Zeilen weiter:
bei positivem Wert von n vorwärts, andernfalls zurück.

Maßnahmen zum Status der aktuellen Zeile

refreshRow

Liest die ursprünglichen Werte der aktuellen Zeile neu ein.

rowInserted

boolean

Gibt an, ob es sich um eine neue Zeile handelt.

rowUpdated

boolean

Gibt an, ob die aktuelle Zeile geändert wurde.

rowDeleted

boolean

Gibt an, ob die aktuelle Zeile gelöscht wurde.

Datenzeilen bearbeiten

Die Methoden zum Lesen stehen bei jedem Formular und bei einer Ergebnismenge zur Verfügung. Die Methoden zum Ändern und Speichern gibt es nur bei einer Datenmenge, die geändert werden kann (in der Regel also nur bei Tabellen, nicht bei Abfragen).

Name

Datentyp

Beschreibung

Maßnahmen für die ganze Zeile

insertRow

Speichert eine neue Zeile.

updateRow

Bestätigt Änderungen der aktuellen Zeile.

deleteRow

Löscht die aktuelle Zeile.

cancelRowUpdates

Macht Änderungen der aktuellen Zeile rückgängig.

moveToInsertRow

Wechselt den Cursor in die Zeile für einen neuen Datensatz.

moveToCurrentRow

Kehrt nach der Eingabe eines neuen Datensatzes zurück zur vorherigen Zeile.

Werte lesen

getString(c)

string

Liefert den Inhalt der Spalte als Zeichenkette.

getBoolean(c)

boolean

Liefert den Inhalt der Spalte als Wahrheitswert.

getByte(c)

byte

Liefert den Inhalt der Spalte als einzelnes Byte.

getShort(c)

short

Liefert den Inhalt der Spalte als ganze Zahl.

getInt(c)

integer

getLong(c)

long

getFloat(c)

float

Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit.

getDouble(c)

double

Liefert den Inhalt der Spalte als Dezimalzahl von doppelter Genauigkeit. Wegen der automatischen Konvertierung durch Basic ist dies auch für decimal- und currency-Werte geeignet.

getBytes(c)

array of bytes

Liefert den Inhalt der Spalte als Folge einzelner Bytes.

getDate(c)

Date

Liefert den Inhalt der Spalte als Datumswert.

getTime(c)

Time

Liefert den Inhalt der Spalte als Zeitwert.

getTimestamp(c)

DateTime

Liefert den Inhalt der Spalte als Zeitstempel (Datum und Zeit).

In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Für den Zugriff auf die Datenmenge gibt es verschiedene Datentypen: com.sun.star.util.Date für ein Datum, com.sun.star.util.Time für eine Zeit, com.sun.star.util.DateTime für einen Zeitstempel.

wasNull

boolean

Gibt an, ob der Wert der zuletzt gelesenen Spalte NULL war.

Werte speichern

updateNull(c)

Setzt den Inhalt der Spalte c auf NULL.

updateBoolean(c,b)

Setzt den Inhalt der Spalte c auf den Wahrheitswert b.

updateByte(c,x)

Speichert in Spalte c das angegebene Byte x.

updateShort(c,n)

Speichert in Spalte c die angegebene ganze Zahl n.

updateInt(c,n)

updateLong(c,n)

updateFloat(c,n)

Speichert in Spalte c die angegebene Dezimalzahl n.

updateDouble(c,n)

updateString(c,s)

Speichert in Spalte die angegebene Zeichenkette s.

updateBytes(c,x)

Speichert in Spalte das angegebene Byte-Array x.

updateDate(c,d)

Speichert in Spalte das angegebene Datum d.

updateTime(c,d)

Speichert in Spalte den angegebenen Zeitwert d.

updateTimestamp(c,d)

Speichert in Spalte den angegebenen Zeitstempel d.

Einzelne Werte bearbeiten

Mit diesen Methoden wird über BoundField aus einem Kontrollfeld der Inhalt der betreffenden Spalte gelesen oder geändert. Diese Methoden entsprechen fast vollständig denen im vorigen Abschnitt; die Angabe der Spalte entfällt.

Name

Datentyp

Beschreibung

Werte lesen

getString

string

Liefert den Inhalt der Spalte als Zeichenkette.

getBoolean

boolean

Liefert den Inhalt der Spalte als Wahrheitswert.

getByte

byte

Liefert den Inhalt der Spalte als einzelnes Byte.

getShort

short

Liefert den Inhalt der Spalte als ganze Zahl.

getInt

integer

getLong

long

getFloat

float

Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit.

getDouble

double

Liefert den Inhalt der Spalte als Dezimalzahl von doppelter Genauigkeit. Wegen der automatischen Konvertierung durch Basic ist dies auch für decimal- und currency-Werte geeignet.

getBytes

array of bytes

Liefert den Inhalt der Spalte als Folge einzelner Bytes.

getDate

Date

Liefert den Inhalt der Spalte als Datumswert.

getTime

Time

Liefert den Inhalt der Spalte als Zeitwert.

getTimestamp

DateTime

Liefert den Inhalt der Spalte als Zeitstempel (Datum und Zeit).

In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Für den Zugriff auf die Datenmenge gibt es verschiedene Datentypen: com.sun.star.util.Date für ein Datum, com.sun.star.util.Time für eine Zeit, com.sun.star.util.DateTime für einen Zeitstempel.

wasNull

boolean

Gibt an, ob der Wert der zuletzt gelesenen Spalte NULL war.

Werte speichern

updateNull

Setzt den Inhalt der Spalte auf NULL.

updateBoolean(b)

Setzt den Inhalt der Spalte auf den Wahrheitswert b.

updateByte(x)

Speichert in der Spalte das angegebene Byte x.

updateShort(n)

Speichert in der Spalte die angegebene ganze Zahl n.

updateInt(n)

updateLong(n)

updateFloat(n)

Speichert in der Spalte die angegebene Dezimalzahl n.

updateDouble(n)

updateString(s)

Speichert in der Spalte die angegebene Zeichenkette s.

updateBytes(x)

Speichert in der Spalte das angegebene Byte-Array x.

updateDate(d)

Speichert in der Spalte das angegebene Datum d.

updateTime(d)

Speichert in der Spalte den angegebenen Zeitwert d.

updateTimestamp(d)

Speichert in der Spalte den angegebenen Zeitstempel d.

Parameter für vorbereitete SQL-Befehle

Die Methoden, mit denen die Werte einem vorbereiteten SQL-Befehl siehe «Vorbereitete SQL-Befehle mit Parametern» übergeben werden, sind ähnlich denen der vorigen Abschnitte. Der erste Parameter mit i bezeichnet nennt seine Nummer (Position) innerhalb des SQL-Befehls.

Name

Datentyp

Beschreibung

setNull(i, n)

Setzt den Inhalt der Spalte auf NULL
n bezeichnet den SQL-Datentyp gemäß API-Referenz.

setBoolean(i, b)

Fügt den angegebenen Wahrheitswert b in den SQL-Befehl ein.

setByte(i, x)

Fügt das angegebene Byte x in den SQL-Befehl ein.

setShort(i, n)

Fügt die angegebene ganze Zahl n in den SQL-Befehl ein.

setInt(i, n)

setLong(i, n)

setFloat(i, n)

Fügt die angegebene Dezimalzahl n in den SQL-Befehl ein.

setDouble(i, n)

setString(i, s)

Fügt die angegebene Zeichenkette s in den SQL-Befehl ein.

setBytes(i, x)

Fügt das angegebene Byte-Array x in den SQL-Befehl ein.

setDate(i, d)

Fügt das angegebene Datum d in den SQL-Befehl ein.

setTime(i, d)

Fügt den angegebenen Zeitwert d in den SQL-Befehl ein.

setTimestamp(i, d)

Fügt den angegebenen Zeitstempel d in den SQL-Befehl ein.

clearParameters

Entfernt die bisherigen Werte aller Parameter eines SQL-Befehls.

Bedienbarkeit verbessern

Als erste Kategorie werden verschiedene Möglichkeiten vorgestellt, die zur Verbesserung der Bedienbarkeit von Base-Formularen dienen. Sofern nicht anders erwähnt, sind diese Makros Bestandteil der Beispieldatenbank «Medien_mit_Makros.odb».

Automatisches Aktualisieren von Formularen

Oft wird in einem Formular etwas geändert und in einem zweiten, auf der gleichen Seite liegenden Formular, soll die Änderung anschließend erscheinen. Hier hilft bereits ein kleiner Codeschnipsel, um das betreffende Anzeigeformular zu aktualisieren.

SUB Aktualisieren

Zuerst wird einmal das Makro benannt. Die Standardbezeichnung für ein Makro ist SUB. Dies kann groß oder klein geschrieben sein, Mit SUB wird eine Prozedur ablaufen gelassen, die nach außen keinen Wert weitergibt. Weiter unten wird im Gegensatz dazu einmal eine Funktion beschrieben, die im Unterschied dazu Rückgabewerte erzeugt.

Das Makro hat jetzt den Namen «Aktualisieren». Um sicher zu gehen, dass keine Variablen von außen eingeschleust werden gehen viele Programmierer so weit, dass sie Basic über Option Explicit gleich zu Beginn mitteilen: Erzeuge nicht automatisch irgendwelche Variablen, sondern nutze nur die, die ich auch vorher definiert habe.

Deshalb werden jetzt standardgemäß erst einmal die Variablen deklariert. Bei allen hier deklarierten Variablen handelt es sich um Objekte (nicht z.B. Zahlen oder Texte), so dass der Zusatz AS OBJECT hinter der Deklaration steht. Um später noch zu erkennen, welchen Typ eine Variable hat, ist vor die Variablenbezeichnung ein « gesetzt worden. Prinzipiell ist aber die Variablenbezeichnung nahezu völlig frei wählbar.

 DIM oDoc AS OBJECT

 DIM oDrawpage AS OBJECT

 DIM oForm AS OBJECT

Das Formular liegt in dem momentan aktiven Dokument. Der Behälter, in dem alle Formulare aufbewahrt werden, wird als Drawpage bezeichnet. Im Formularnavigator ist dies sozusagen der oberste Begriff, an den dann sämtliche Formulare angehängt werden.

Das Formular, auf das zugegriffen werden soll, ist hier mit den Namen "Anzeige" versehen. Dies ist der Name, der auch im Formularnavigator sichtbar ist. So hat z.B. das erste Formular standardmäßig erst einmal den Namen "MainForm".

 oDoc = thisComponent

 oDrawpage = oDoc.Drawpage

 oForm = oDrawpage.forms.getByName("Anzeige")

Nachdem das Formular jetzt ansprechbar gemacht wurde und der Punkt, an dem es angesprochen wurde, in der Variablen oForm gespeichert wurde, wird es jetzt mit dem Befehl reload() neu geladen.

 oForm.reload()

END SUB

Die Prozedur hat mit SUB begonnen. Sie wird mit END SUB beendet.

Dieses Makro kann jetzt z.B. ausgelöst werden, wenn die Abspeicherung in einem anderen Formular erfolgt. Wird z.B. in einem Kassenformular an einer Stelle die Anzahl der Gegenstände und (über Barcodescanner) die Nummer eingegeben, so kann in einem anderen Formular im gleichen geöffneten Fenster hierdurch der Kassenstand, die Bezeichnung der Ware usw. nach dem Abspeichern sichtbar gemacht werden.

Filtern von Datensätzen

Der Filter selbst funktioniert ja schon ganz ordentlich in einer weiter oben beschriebenen Variante im Kapitel «Datenfilterung». Die untenstehende Variante ersetzt den Abspeicherungsbutton und liest die Listenfelder neu ein, so dass ein gewählter Filter aus einem Listenfeld die Auswahl in dem anderen Listenfeld einschränken kann.3

SUB Filter

 DIM oDoc AS OBJECT

 DIM oDrawpage AS OBJECT

 DIM oForm1 AS OBJECT

 DIM oForm2 AS OBJECT

 DIM oFeldList1 AS OBJECT

 DIM oFeldList2 AS OBJECT

 oDoc = thisComponent

 oDrawpage = oDoc.drawpage

Zuerst werden die Variablen definiert und auf das Gesamtformular zugegriffen. Das Gesamtformular besteht aus den Formularen "Filter" und "Anzeige". Die Listenfelder befinden sich in dem Formular "Filter" und sind mit dem Namen "Liste_1" und "Liste_2" versehen.

 oForm1 = oDrawpage.forms.getByName("Filter")

 oForm2 = oDrawpage.forms.getByName("Anzeige")

 oFeldList1 = oForm1.getByName("Liste_1")

 oFeldList2 = oForm1.getByName("Liste_2")

Zuerst wird der Inhalt der Listenfelder an das darunterliegende Formular mit commit() weitergegeben. Die Weitergabe ist notwendig, da ansonsten die Änderung eines Listenfeldes bei der Speicherung nicht berücksichtigt wird. Genau genommen müsste der commit() nur auf dem Listenfeld ausgeführt werden, das gerade betätigt wurde. Danach wird der Datensatz mit updateRow() abgespeichert. Es existiert ja in unserer Filtertabelle prinzipiell nur ein Datensatz, und der wird zu Beginn einmal geschrieben. Dieser Datensatz wird also laufend durch ein Update-Kommado überschrieben.

 oFeldList1.commit()

 oFeldList2.commit()

 oForm1.updateRow()

Die Listenfelder sollen einander beeinflussen. Wird in einem Listenfeld z.B. eingegrenzt, dass an Medien nur CDs angezeigt werden sollen, so muss das andere Listenfeld bei den Autoren nicht noch sämtliche Buchautoren auflisten. Eine Auswahl im 2. Listenfeld hätte dann allzu häufig ein leeres Filterergebnis zur Folge. Daher müssen die Listenfelder jetzt neu eingelesen werden. Genau genommen müsste der refresh() nur auf dem Listenfeld ausgeführt werden, das gerade nicht betätigt wurde.

Anschließend wird das Formular2, das den gefilterten Inhalt anzeigen soll, neu geladen.

 oFeldList1.refresh()

 oFeldList2.refresh()

 oForm2.reload()

END SUB

Soll mit diesem Verfahren ein Listenfeld von der Anzeige her beeinflusst werden, so kann das Listenfeld mit Hilfe verschiedener Abfragen bestückt werden.

Die einfachste Variante ist, dass sich die Listenfelder mit ihrem Inhalt aus dem Filterergebnis versorgen. Dann bestimmt der eine Filter, aus welchen Datenbestand anschließend weiter gefiltert werden kann.

SELECT

 "Feld_1" || ' - ' || "Anzahl" AS "Anzeige",

 "Feld_1"

FROM

 ( SELECT COUNT( "ID" ) AS "Anzahl", "Feld_1" FROM "Suchtabelle"  GROUP BY "Feld_1" )

ORDER BY "Feld_1"

Es wird der Feldinhalt und die Trefferzahl angezeigt. Um die Trefferzahl zu errechnen, wird eine Unterabfrage gestellt. Dies ist notwendig, da sonst nur die Trefferzahl ohne weitere Information aus dem Feld in der Listbox angezeigt würde.

Das Makro erzeugt durch dieses Vorgehen ganz schnell Listboxen, die nur noch mit einem Wert gefüllt sind. Steht eine Listbox nicht auf NULL, so wird sie schließlich bei der Filterung bereits berücksichtigt. Nach Betätigung der 2. Listbox stehen also bei beiden Listboxen nur noch die leeren Felder und jeweils 1 angezeigter Wert zur Verfügung. Dies mag für eine eingrenzende Suche erst einmal praktisch erscheinen. Was aber, wenn z.B. in einer Bibliothek die Zuordnung zur Systematik klar war, aber nicht eindeutig, ob es sich um ein Buch, eine CD oder eine DVD handelt? Wurde einmal die Systematik angewählt und dann die 2. Listbox auf CD gestellt so muss, um auch die Bücher zu sehen, die 2. Listbox erst einmal wieder auf NULL gestellt werden, um dann auch die Bücher anwählen zu können. Praktischer wäre, wenn die 2. Listbox direkt die verschiedenen Medienarten anzeigen würde, die zu der Systematik zur Verfügung stehen – natürlich mit den entsprechenden Trefferquoten.

Um dies zu erreichen, wurde die folgende Abfrage konstruiert, die jetzt nicht mehr direkt aus dem Filterergebnis gespeist wird. Die Zahlen für die Treffer müssen anders ermittelt werden.

SELECT

 COALESCE( "Feld_1" || ' - ' || "Anzahl", 'leer - ' || "Anzahl" ) AS   "Anzeige",

 "Feld_1"

FROM

 ( SELECT COUNT( "ID" ) AS "Anzahl", "Feld_1" FROM "Tabelle"

  WHERE "ID" IN

  ( SELECT "Tabelle"."ID" FROM "Filter", "Tabelle"

   WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",

   "Tabelle"."Feld_2" ) )

  GROUP BY "Feld_1"

 )

ORDER BY "Feld_1"

Diese doch sehr verschachtelte Abfrage kann auch unterteilt werden. In der Praxis bietet es sich häufig an, die Unterabfrage in einer Tabellenansicht ('VIEW') zu erstellen. Das Listenfeld bekommt seinen Inhalt dann über eine Abfrage, die sich auf diesen 'VIEW' bezieht.

Die Abfrage im Einzelnen:

Die Abfrage stellt 2 Spalten dar. Die erste Spalte enthält die Ansicht, die die Person sieht, die das Formular vor sich hat. In der Ansicht werden die Inhalte des Feldes und, mit einem Bindestrich abgesetzt, die Treffer zu diesem Feldinhalt gezeigt. Die zweite Spalte gibt ihren Inhalt an die zugrundeliegende Tabelle des Formulars weiter. Hier steht nur der Inhalt des Feldes. Die Listenfelder beziehen ihre Inhalte dabei aus der Abfrage, die als Filterergebnis im Formular dargestellt wird. Nur diese Felder stehen schließlich zur weiteren Filterung zur Verfügung.

Als Tabelle, aus der diese Informationen gezogen werden, liegt eine Abfrage vor. In dieser Abfrage werden die Primärschlüsselfelder gezählt (SELECT COUNT( "ID" ) AS "Anzahl"). Dies geschieht gruppiert nach der Bezeichnung, die in dem Feld steht (GROUP BY "Feld_1"). Als zweite Spalte stellt diese Abfrage das Feld selbst als Begriff zur Verfügung. Diese Abfrage wiederum basiert auf einer weiteren Unterabfrage:

SELECT "Tabelle"."ID"

FROM "Filter", "Tabelle"

WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",  "Tabelle"."Feld_2" )

Diese Unterabfrage bezieht sich jetzt auf das andere zu filternde Feld. Prinzipiell muss das andere zu filternde Feld auch zu den Primärschlüsselnummern passen. Sollten noch mehrere weitere Filter existieren, so ist diese Unterabfrage zu erweitern:

SELECT "Tabelle"."ID"

FROM "Filter", "Tabelle"

WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",

  "Tabelle"."Feld_2" )

 AND

 "Tabelle"."Feld_3" = COALESCE( "Filter"."Filter_3",

  "Tabelle"."Feld_3" )

Alle weiteren zu filternden Felder beeinflussen, was letztlich in dem Listenfeld des ersten Feldes, "Feld_1", angezeigt wird.

Zum Schluss wird die gesamte Abfrage nur noch nach dem zugrundeliegenden Feld sortiert.

Wie letztlich die Abfrage aussieht, die dem anzuzeigenden Formular zugrunde liegt, ist im Kapitel «Datenfilterung» nachzulesen.

Mit dem folgenden Makro kann über das Listenfeld gesteuert werden, welches Listenfeld abgespeichert werden muss und welches neu eingelesen werden muss.

Die Variablen für das Array werden in den Eigenschaften des Listenfeldes unter Zusatzinformationen abgelegt. Die erste Variable enthält dort immer den Namen des Listenfeldes selbst, die weiteren Variablen die Namen aller anderen Listenfelder, getrennt durch Kommata.

SUB Filter_Zusatzinfo(oEvent AS OBJECT)

 DIM oDoc AS OBJECT

 DIM oDrawpage AS OBJECT

 DIM oForm1 AS OBJECT

 DIM oForm2 AS OBJECT

 DIM stTag AS String

 stTag = oEvent.Source.Model.Tag

Ein Array (Ansammlung von Daten, die hier über Zahlenverbindungen abgerufen werden können) wird gegründet und mit den Feldnamen der Listenfelder gefüllt. Der erste Name ist der Name des Listenfelds, das mit der Aktion (Event) verbunden ist.

 aList() = Split(stTag, ",")

 oDoc = thisComponent

        oDrawpage = oDoc.drawpage

        oForm1 = oDrawpage.forms.getByName("Filter")

        oForm2 = oDrawpage.forms.getByName("Anzeige")

Das Array wird von seiner Untergrenze (LBound()) bis zu seiner Obergrenze (UBound()) in einer Schleife durchlaufen. Alle Werte, die in den Zusatzinformationen durch Komma getrennt erschienen, werden jetzt nacheinander weitergegeben.

 FOR i = LBound(aList()) TO UBound(aList())

  IF i = 0 THEN

Das auslösende Listenfeld muss abgespeichert werden. Es hat die Variable aList(0). Zuerst wird die Information des Listenfeldes auf die zugrundeliegende Tabelle übertragen, dann wird der Datensatz gespeichert.

   oForm1.getByName(aList(i)).commit()

   oForm1.updateRow()

  ELSE

Die anderen Listenfelder müssen neu eingelesen werden, da sie ja in Abhängigkeit vom ersten Listenfeld jetzt andere Werte abbilden.

   oForm1.getByName(aList(i)).refresh()

  END IF

 NEXT

 oForm2.reload()

END SUB

Die Abfragen für dieses besser nutzbare Makro sind natürlich die gleichen wie in diesem Abschnitt zuvor bereits vorgestellt.

Daten über den Formularfilter filtern

Alternativ zu dieser Vorgehensweise ist es auch möglich, die Filterfunktion des Formulars direkt zu bearbeiten.

SUB FilterSetzen

        DIM oDoc AS OBJECT

        DIM oForm AS OBJECT

        DIM oFeld  AS OBJECT

        DIM stFilter As String

        oForm = thisComponent.Drawpage.Forms.getByName("MainForm")

        oFeld = oForm.getByName("Filter")

        stFilter = oFeld.Text

        oForm.filter = " UCASE(""Name"") LIKE '%'||'" + UCASE(stFilter) + "'||'%'"

        oForm.ApplyFilter = TRUE

        oForm.reload()

End Sub

Das Feld wird im Formular aufgesucht, der Inhalt ausgelesen. Der Filter wird entsprechend gesetzt. Die Filterung wird angeschaltet und das Formular neu geladen.

SUB FilterEntfernen

        DIM oForm AS OBJECT

        oForm = thisComponent.Drawpage.Forms.getByName("MainForm")

        oForm.ApplyFilter = False

        oForm.reload()

END SUB

Die Beendigung des Filters kann natürlich auch über die Navigationsleiste erfolgen. In diesem Fall wird einfach ein weiteres Makro genutzt.

Über diese Filterfunktion kann ein Formular auch direkt mit einem Filter z. B. für nur einen Datensatz gestartet werden. Aus dem startenden Formular wird ein Wert (z.B. ein Primärschlüssel für den aktuellen Datensatz) ausgelesen und an das Zielformular als Filterwert weiter gegeben.

Daten aus Textfeldern auf SQL-Tauglichkeit vorbereiten

Beim Speichern von Daten über einen SQL-Befehl können vor allem Hochkommata (') Probleme bereiten, wie sie z.B. in Namensbezeichnungen wie O'Connor vorkommen können. Dies liegt daran, dass Texteingaben in Daten in '' eingeschlossen sind. Hier muss eine Funktion eingreifen und die Daten entsprechend vorbereiten.

FUNCTION String_to_SQL(st AS STRING)

        IF InStr(st,"'") THEN

                st = Join(Split(st,"'"),"''")

        END IF

        String_to_SQL = st

END FUNCTION

Es handelt sich hier um eine Funktion. Eine Funktion nimmt einen Wert auf und liefert anschließend auch einen Gegenwert zurück.

Der übergebende Text wird zuerst einmal daraufhin untersucht, ob er ein Hochkomma enthält. Ist dies der Fall, so wird der Text an der Stelle aufgetrennt - der Trenner dafür ist das Hochkomma – und anschließend stattdessen mit zwei Hochkommata wieder zusammengefügt. Der SQL-Code wird so maskiert.

Die Funktion übergibt ihr Ergebnis durch den folgenden Aufruf:

stTextneu = String_to_SQL(stTextalt)

Es wird also einfach nur die Variable stTextalt überarbeitet und der entsprechende Wert wieder in der Variablen stTextneu gespeichert. Dabei müssen die Variablen gar nicht unterschiedlichen heißen. Der Aufruf geht praktischer direkt mit:

stText = String_to_SQL(stText)

Diese Funktion wird in den nachfolgenden Makros immer wieder benötigt, damit Hochkommata auch über SQL abgespeichert werden können.

Werte in einem Formular vorausberechnen

Werte, die über die Datenbankfunktionen berechnet werden können, werden in der Datenbank nicht extra gespeichert. Die Berechnung erfolgt allerdings nicht während der Eingabe im Formular, sondern erst nachdem der Datensatz abgespeichert ist. Solange das Formular aus einem Tabellenkontrollfeld besteht, mag das nicht so viel ausmachen. Schließlich kann direkt nach der Eingabe ein berechneter Wert ausgelesen werden. Bei Formularen mit einzelnen Feldern bleibt der vorherige Datensatz aber nicht unbedingt sichtbar. Hier bietet es sich an, die Werte, die sonst in der Datenbank berechnet werden, direkt in entsprechenden Feldern anzuzeigen.4

Die folgenden drei Makros zeigen, wie so etwas vom Prinzip her ablaufen kann. Beide Makros sind mit dem Verlassen bestimmter Felder gekoppelt. Dabei ist auch berücksichtigt, dass hinterher eventuell Werte in einem bereits bestehenden Feld geändert werden.

SUB Berechnung_ohne_MWSt(oEvent AS OBJECT)

        DIM oForm AS OBJECT

        DIM oFeld AS OBJECT

        DIM oFeld2 AS OBJECT

        oFeld = oEvent.Source.Model

        oForm = oFeld.Parent

        oFeld2 = oForm.getByName("Preis_ohne_MWSt")

        oFeld2.BoundField.UpdateDouble(oFeld.getCurrentValue / 1.19)

        IF NOT IsEmpty(oForm.getByName("Anzahl").getCurrentValue()) THEN

                Berechnung_gesamt2(oForm.getByName("Anzahl"))

        END IF

END SUB

Ist in einem Feld «Preis» ein Wert eingegeben, so wird beim Verlassen des Feldes das Makro ausgelöst. Im gleichen Formular wie das Feld «Preis» liegt das Feld «Preis_ohne_MWSt». Für dieses Feld wird mit BoundField.UpdateDouble der berechnete Preis ohne Mehrwertsteuer festgelegt. Das Datenfeld dazu entstammt einer Abfrage, bei der vom Prinzip her die gleiche Berechnung, allerdings bei bereits gespeicherten Daten, durchgeführt wird. Auf diese Art und Weise wird der berechnete Wert sowohl während der Eingabe als auch später während der Navigation durch die Datensätze sichtbar, ohne abgespeichert zu werden.

Ist bereits im Feld «Anzahl» ein Wert enthalten, so wird eine Folgerechnung auch für die damit verbundenen Felder durchgeführt.

SUB Berechnung_gesamt(oEvent AS OBJECT)

        oFeld = oEvent.Source.Model

        Berechnung_gesamt2(oFeld)

END SUB

Diese kurze Prozedur dient nur dazu, die Auslösung der Folgeprozedur vom Verlassen des Formularfeldes «Anzahl» weiter zu geben. Die Angabe könnte genauso gut mit Hilfe der Bestimmung des Feldes über die Drawpage in der Folgeprozedur integriert werden.

SUB Berechnung_gesamt2(oFeld AS OBJECT)

        DIM oForm AS OBJECT

        DIM oFeld2 AS OBJECT

        DIM oFeld3 AS OBJECT

        DIM oFeld4 AS OBJECT

        oForm = oFeld.Parent

        oFeld2 = oForm.getByName("Preis")

        oFeld3 = oForm.getByName("Preis_gesamt_mit_MWSt")

        oFeld4 = oForm.getByName("MWSt_gesamt")

        oFeld3.BoundField.UpdateDouble(oFeld.getCurrentValue * oFeld2.getCurrentValue)

        oFeld4.BoundField.UpdateDouble(oFeld.getCurrentValue * oFeld2.getCurrentValue -  

                oFeld.getCurrentValue * oFeld2.getCurrentValue / 1.19)

END SUB

Diese Prozedur ist lediglich eine Prozedur, bei der mehrere Felder berücksichtigt werden sollen. Die Prozedur wird aus einem Feld «Anzahl» gestartet, das die Anzahl bestimmter gekaufter Waren vorgeben soll. Mit Hilfe dieses Feldes und des Feldes «Preis» wird jetzt der «Preis_gesamt_mit_MWSt» und die «MWSt_gesamt» berechnet und in die entsprechenden Felder übertragen.

Nachteil in den Prozeduren und auch bei Abfragen: Der Steuersatz wird hier fest einprogrammiert. Besser wäre eine entsprechende Angabe dazu in Verbindung mit dem Preis, da ja Steuersätze unterschiedlich sein können und auch nicht immer konstant sind. In dem Fall müsste eben der Mehrwertsteuersatz aus einem Feld des Formulars ausgelesen werden.

Die aktuelle Office-Version ermitteln

Mit der Version 4.1 sind Änderungen bei Listenfeldern und Datumswerten vorgenommen worden, die es erforderlich machen, vor der Ausführung eines Makros für diesen Bereich zu erkunden, welche Office-Version denn nun verwendet wird. Dazu dient der folgende Code:

FUNCTION OfficeVersion()

        DIM aSettings, aConfigProvider

        DIM aParams2(0) AS NEW com.sun.star.beans.PropertyValue

        DIM sProvider$, sAccess$

        sProvider = "com.sun.star.configuration.ConfigurationProvider"

        sAccess = "com.sun.star.configuration.ConfigurationAccess"

        aConfigProvider = createUnoService(sProvider)

        aParams2(0).Name = "nodepath"

        aParams2(0).Value = "/org.openoffice.Setup/Product"

        aSettings = aConfigProvider.createInstanceWithArguments(sAccess, aParams2())

        OfficeVersion() = array(aSettings.ooName,aSettings.ooSetupVersionAboutBox)

END FUNCTION

Diese Funktion gibt eine Array wieder, das als ersten Wert z.B. "LibreOffice" und als zweiten Wert die detaillierte Version, z.B. "4.1.5.2" ausgibt.

Wert von Listenfeldern ermitteln

Mit LibreOffice 4.1 wird der Wert, den Listenfelder an die Datenbank weitergeben, über «CurrentValue» ermittelt. In Vorversionen, auch OpenOffice oder AOO, ist dies nicht der Fall. Die folgende Funktion soll dem Rechnung tragen. Die ermittelte LO-Version muss daraufhin untersucht werden, ob sie nach der Version 4.0 entstanden ist.

FUNCTION ID_Ermittlung(oFeld AS OBJECT) AS INTEGER

        a() = OfficeVersion()

        IF a(0) = "LibreOffice" AND (LEFT(a(1),1) = 4 AND RIGHT(LEFT(a(1),3),1) > 0) OR                 LEFT(a(1),1) > 4 THEN

                stInhalt = oFeld.currentValue

        ELSE

Vor LO 4.1 wird der Wert, der weiter gegeben wird, aus der Werteliste des Listenfeldes ausgelesen. Der sichtbar ausgewählte Datensatz ist SelectedItems(0). '0', weil auch mehrere Werte in einem Listenfeld ausgewählt werden könnten.

                stInhalt = oFeld.ValueItemList(oFeld.SelectedItems(0))

        END IF

        IF IsEmpty(stInhalt) THEN

Mit -1 wird ein Zahlenwert weiter gegeben, der nicht als AutoWert verwendet wird, also in vielen Tabellen nicht als Fremdschlüssel existiert.

                ID_Ermittlung = -1

        ELSE

                ID_Ermittlung = Cint(stInhalt)

Umwandlung von Text in Integer

        END IF

END FUNCTION

Die Funktion gibt den Wert als Integer wieder. Meist werden für Primärschlüssel ja automatisch hoch zählende Integer-Werte verwendet. Für eine Verwendung von Fremdschlüsseln, die diesem Kriterium nicht entsprechen, muss die Ausgabe der Variablen entsprechend angepasst werden.

Der angezeigte Wert eines Listenfeldes lässt sich weiterhin über die Ansicht des Feldes ermitteln:

SUB Listenfeldanzeige

        DIM oDoc AS OBJECT

        DIM oForm AS OBJECT

        DIM oListbox AS OBJECT

        DIM oController AS OBJECT

        DIM oView AS OBJECT

        oDoc = thisComponent

        oForm = oDoc.Drawpage.Forms(0)

        oListbox = oForm.getByName("Listenfeld")

        oController = oDoc.getCurrentController()

        oView = oController.getControl(oListbox)

        print "Angezeigter Inhalt: " & oView.SelectedItem

END SUB

Es wird über den Controller auf die Ansicht des Formulars zugegriffen. Damit wird ermittelt, was auf der sichtbaren Oberfläche tatsächlich erscheint. Der ausgewählte Wert ist der SelectedItem.

Listenfelder durch Eingabe von Anfangsbuchstaben einschränken

Manchmal kann es vorkommen, dass der Inhalt für Listenfelder unübersichtlich groß wird. Damit eine Suche schneller zum Erfolg führt, wäre es sinnvoll, hier den Inhalt des Listenfeldes nach Eingabe eines oder mehrerer Buchstaben einzugrenzen. Das Listenfeld selbst wird erst einmal mit einem SQL-Befehl versehen, der nur als Platzhalter dient. Hier könnte z.B. stehen:

SELECT "Name", "ID" FROM "Tabelle" ORDER BY "Name" LIMIT 5 (Hsqldb)
SELECT "Name", "ID" FROM "Tabelle" ORDER BY "Name" ROWS 5 (
Firebird)

So wird beim Öffnen des Formulars vermieden, dass Base erst einmal die umfangreiche Liste einlesen muss.

Das folgende Makro ist dafür an Eigenschaften: Listenfeld → Ereignisse → Taste losgelassen gekoppelt.

GLOBAL stListStart AS STRING

GLOBAL lZeit AS LONG

Zuerst werden globale Variablen erstellt. Diese Variablen sind notwendig, damit nicht nur nach einem Buchstaben, sondern nach dem Betätigen weiterer Tasten schließlich auch nach einer Buchstabenkombination gesucht werden kann.

In der globalen Variablen stListStart werden die Buchstaben in der eingegebenen Reihenfolge gespeichert.

Die globale Variable lZeit wird mit der aktuellen Zeit in Sekunden versorgt. Bei einer längeren Pause zwischen den Tastatureingaben soll die Variable stListStart wieder zurückgesetzt werden können. Deswegen wird jeweils der Zeitunterschied zur vorhergehenden Eingabe abgefragt.

SUB ListFilter(oEvent AS OBJECT)

 oFeld = oEvent.Source.Model

 IF oEvent.KeyCode < 538 OR oEvent.KeyCode = 1283 OR oEvent.KeyCode = 1284 THEN

Das Makro wird durch einen Tastendruck ausgelöst. Eine Taste hat innerhalb der API einen bestimmten Zahlencode, der unter com>sun>star>awt>Key nachgeschlagen werden kann. Sonderzeichen wie das «ä», «ö» und «ü» haben den KeyCode 0, alle anderen Schriftzeichen und Zahlen haben einen KeyCode kleiner als 538. Den KeyCode 1283 belegt die Backspace-Taste. Wird dieser Code mit ausgelesen, so können auch Korrekturen durchgeführt werden. Mit dem KeyCode 1284 wird auch die Leertaste in die möglichen Zeichen aufgenommen.

Die Abfrage des KeyCode ist hier wichtig, da auch der Schritt mit der Tabulatortaste auf das Auswahlfeld natürlich das Makro auslöst. Der KeyCode für die Tabulatortaste liegt allerdings bei 1282, so dass der weitere Code der Prozedur hier nicht ausgeführt wird.

                DIM stSql(0) AS STRING

Der SQL-Code für das Listenfeld wird in einem Array gespeichert. Das Array hat im Falle des SQL-Codes aber nur ein Datenfeld. Deshalb ist das Array direkt auf stSql(0) begrenzt.

Entsprechend muss auch beim Auslesen des SQL-Codes aus dem Listenfeld darauf geachtet werden, dass der SQL-Code nicht direkt als Text zugänglich ist. Stattdessen ist der Code in einem Array als einziger Eintrag vorhanden: oFeld.ListSource(0).

Der SQL-Code wird nach der Deklaration der Variablen für die weitere Verwendung aufgesplittet. Für das Feld, das gefiltert werden soll, wird nach dem ersten Komma der Code abgetrennt. Das Feld muss also an der ersten Position stehen. Anschließend wird der verbleibende Teil an dem ersten erscheinenden Anführungsstrich «"» aufgetrennt. Damit beginnt die Feldbezeichnung. Diese Aufteilungen erfolgen hier mit einfachen Arrays. Der Variablen stFeld wird schließlich wieder das doppelte Anführungszeichen am Beginn hinzugefügt. Außerdem wird über Rtrim vermieden, dass eine eventuell noch vorhandene Leertaste am Schluss des Ausdrucks bestehen bleibt.

                DIM stText AS STRING

                DIM stFeld AS STRING

                DIM stQuery AS STRING

                DIM ar0()

                DIM ar1()

                ar0() = Split(oFeld.ListSource(0),",", 2)

                ar1() = Split(ar0(0),"""", 2)

                stFeld = """" & Rtrim(ar1(1))

In dem SQL-Code wird eine Sortieranweisung erwartet. Allerdings kann die Anweisung in SQL in Großbuchstaben, Kleinbuchstaben oder beliebig gemischt erfolgen. Deshalb wird hier nicht mit Split, sondern mit Hilfe der Funktion inStr nach der Zeichenkette «ORDER» gesucht. Der abschließende Parameter in dieser Funktion sagt mit der «1» aus, dass nicht nach Groß- und Kleinschreibung unterschieden werden soll. Alles, was links von der Zeichenkette «ORDER» steht, soll für die Konstruktion des neuen SQL-Codes weiter genutzt werden. Damit ist gewährleistet, dass auch Listenfelder bestückt werden können, die aus unterschiedlichen Tabellen oder über weitere Bedingungen im SQL-Code definiert worden sind.

                stQuery = Left(oFeld.ListSource(0), inStr(1,oFeld.ListSource(0), "ORDER",1)-1)

                IF inStr(stQuery, "LOWER") > 0 THEN

                        stQuery = Left(stQuery, inStr(stQuery, "LOWER")-1)

                ELSEIF inStr(1,stQuery, "WHERE",1) > 0 THEN

                        stQuery = stQuery & " AND "

                ELSE                       

                        stQuery = stQuery & " WHERE "

                END IF

Enthält die ermittelte Abfrage den Begriff «LOWER», so wird davon ausgegangen, dass die Abfrage bereits über die Prozedur ListFilter erstellt wurde. Deswegen wird die neu zu konstruierende Abfrage nur bis zur Position dieses Begriffes übernommen.

Ist dies nicht der Fall und es existiert in der Abfrage bereits der Begriff «WHERE» in beliebiger Schreibweise, so müssen weitere Bedingungen an die Abfrage mit AND angehängt werden.

Sind beide Bedingungen nicht erfüllt, so wird ein WHERE an den bestehenden Code angehängt.

                IF lZeit > 0 AND Timer() - lZeit < 5 THEN

                        stListStart = stListStart & oEvent.KeyChar

                ELSE

                        stListStart = oEvent.KeyChar

                END IF

                lZeit = Timer()

Ist bereits einmal eine Zeit in der globalen Variablen abgespeichert worden und beträgt die Distanz zu dieser Zeit zum Zeitpunkt der Eingabe weniger als 5 Sekunden, so wird der eingegebene Buchstabe an die vorher eingegebenen Buchstaben angehängt. Anderenfalls wird der eingegebene Buchstabe als einzige (neue) Eingabe verstanden. Das Listenfeld wird dann einfach neu nach dem entsprechenden Buchstaben gefiltert. Anschließend wird die aktuelle Zeit wieder in der globalen Variablen lZeit gespeichert.

                stText = LCase( stListStart & "%")

                stSql(0) = stQuery + "LOWER("+stFeld+") LIKE '"+stText+"' ORDER BY "+stFeld+""

                oFeld.ListSource = stSql

                oFeld.refresh

 END IF

END SUB

Der SQL-Code wird schließlich zusammengefügt. Die Kleinschreibweise des Feldinhaltes wird mit der Kleinschreibweise des eingegebenen Buchstabens verglichen. Der Code wird dem Listenfeld hinzugefügt und das Listenfeld aufgefrischt, so dass nur noch der gefilterte Inhalt nachgeschlagen werden kann.

Datumswert aus einem Formularwert in eine Datumsvariable umwandeln

FUNCTION Datumswert(oFeld AS OBJECT) AS DATE

        a() = OfficeVersion()

        IF a(0) = "LibreOffice" AND (LEFT(a(1),1) = 4 AND RIGHT(LEFT(a(1),3),1) > 0)

                OR LEFT(a(1),1) > 4 THEN

Hier werden alle Versionen ab 4.1 durch die oben vorgestellte Funktion «OfficeVersion()» abgefangen. Dazu wird die Version in ihre Bestandteile aufgesplittet. Die Hauptversion und die erste Unterversion werden abgefragt. Das funktioniert vorerst bis zur LO-Version 9 einwandfrei.

                DIM stMonat AS STRING

                DIM stTag AS STRING

                stMonat = Right(Str(0) & Str(oFeld.CurrentValue.Month),2)

                stTag =  Right(Str(0) & Str(oFeld.CurrentValue.Day),2)

                Datumswert = CDateFromIso(oFeld.CurrentValue.Year & stMonat & stTag)

        ELSE

                Datumswert = CDateFromIso(oFeld.CurrentValue)

        END IF

END FUNCTION

Das Datum wird seit LO 4.1.2 als Array im Formularfeld gespeichert. Mit dem aktuellen Wert kann also nicht auf das Datum selbst zugegriffen werden. Entsprechend ist es neu aus den Werten für Tag, Monat und Jahr zusammen zu setzen, damit anschließend in Makros damit weiter gearbeitet werden kann.

Suchen von Datensätzen

Ohne Makro funktioniert das Suchen von Datensätzen auch. Hier ist aber die entsprechende Abfrage äußerst unübersichtlich zu erstellen. Da könnte eine Schleife mittels Makro Abhilfe schaffen.

Die folgende Variante liest die Felder einer Tabelle aus, gründet dann intern eine Abfrage und schreibt daraus schließlich eine Liste der Primärschlüsselnummern der durchsuchten Tabelle auf, auf die der Suchbegriff zutrifft. Für die folgende Beschreibung existiert eine Tabelle "Suchtmp", die aus einem per Autowert erstellten Primärschlüsselfeld "ID" und einem Feld "Nr." besteht, in das die aus der zu durchsuchenden Tabelle gefundenen Primärschlüssel eingetragen werden. Der Tabellenname wird dabei der Prozedur am Anfang als Variable mitgegeben.

Um ein entsprechendes Ergebnis zu bekommen, muss die Tabelle natürlich nicht die Fremdschlüssel, sondern entsprechende Feldinhalte in Textform enthalten. Dafür ist gegebenenfalls ein 'VIEW' zu erstellen, auf den das Makro auch zugreifen kann.5

SUB Suche(stTabelle AS STRING)

 DIM oDatenquelle AS OBJECT

 DIM oVerbindung AS OBJECT

 DIM oSQL_Anweisung AS OBJECT

 DIM stSql AS STRING

 DIM oAbfrageergebnis AS OBJECT

 DIM oDoc AS OBJECT

 DIM oDrawpage AS OBJECT

 DIM oForm AS OBJECT

 DIM oForm2 AS OBJECT

 DIM oFeld AS OBJECT

 DIM stInhalt AS STRING

 DIM arInhalt() AS STRING

 DIM inI AS INTEGER

 DIM inK AS INTEGER

 oDoc = thisComponent

        oDrawpage = oDoc.drawpage

        oForm = oDrawpage.forms.getByName("Suchform")

        oFeld = oForm.getByName("Suchtext")

        stInhalt = oFeld.getCurrentValue()

        stInhalt = LCase(stInhalt)

Der Inhalt des Suchtext-Feldes wird hier von vornherein in Kleinbuchstaben umgewandelt, damit die anschließende Suchfunktion nur die Kleinschreibweisen miteinander vergleicht.

 oDatenquelle = ThisComponent.Parent.DataSource

 oVerbindung = oDatenquelle.GetConnection("","")

 oSQL_Anweisung = oVerbindung.createStatement()

Zuerst wird einmal geklärt, ob überhaupt ein Suchbegriff eingegeben wurde. Ist das Feld leer, so wird davon ausgegangen, dass keine Suche vorgenommen wird. Alle Datensätze sollen angezeigt werden; eine weitere Abfrage erübrigt sich.

Ist ein Suchbegriff eingegeben worden, so werden die Spaltennamen der zu durchsuchenden Tabelle ausgelesen, um auf die Felder mit einer Abfrage zugreifen zu können.

 IF stInhalt <> "" THEN

                stInhalt = String_to_SQL(stInhalt)

  stSql = "SELECT ""COLUMN_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_COLUMNS""

                        WHERE ""TABLE_NAME"" = '" + stTabelle + "' ORDER BY ""ORDINAL_POSITION"""

  oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)

Hinweis

Der SQL-Code müsste für Firebird hier angepasst werden:

stSql = "SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS WHERE         RDB$RELATION_NAME = '" + stTabelle + "' ORDER BY RDB$Field_POSITION"

Auf die Doppelung der doppelten Anführungszeichen kann hier verzichtet werden, da die Bezeichnungen sowieso keine Sonderzeichen enthalten und nur aus Großbuchstaben zusammengesetzt sind.

Leider ist der folgende SQL-Code dieses Makros für Firebird so nicht geeignet, da Firebird nicht in der Lage ist, aus selektierten Daten eine neue Tabelle zu erstellen. (Firebird)

Hinweis

SQL-Formulierungen müssen in Makros wie normale Zeichenketten zuerst einmal in doppelten Anführungsstrichen gesetzt werden. Feldbezeichnungen und Tabellenbezeichnungen stehen innerhalb der SQL-Formulierungen in der Regel bereits in doppelten Anführungsstrichen. Damit letztlich ein Code entsteht, der auch diese Anführungsstriche weitergibt, müssen für Feldbezeichnungen und Tabellenbezeichnungen diese Anführungsstriche verdoppelt werden.

Aus stSql = "SELECT ""Name"" FROM ""Tabelle"";"
wird, wenn es mit dem Befehl
msgbox stSql auf dem Bildschirm angezeigt wird,
SELECT "Name" FROM "Tabelle";

Der Zähler des Arrays, in das die Feldnamen geschrieben werden, wird zuerst auf 0 gesetzt. Dann wird begonnen die Abfrage auszulesen. Da die Größe des Arrays unbekannt ist, muss immer wieder nachjustiert werden. Deshalb beginnt die Schleife damit, über ReDim Preserve arInhalt(inI) die Größe des Arrays festzulegen und den vorherigen Inhalt dabei zu sichern. Anschließend werden die Felder ausgelesen und der Zähler des Arrays um 1 heraufgesetzt. Damit kann dann das Array neu dimensioniert werden und wieder ein weiterer Wert abgespeichert werden.

  InI = 0

  WHILE oAbfrageergebnis.next

   ReDim Preserve arInhalt(inI)

   arInhalt(inI) = oAbfrageergebnis.getString(1)

   inI = inI + 1

  WEND

  stSql = "DROP TABLE ""Suchtmp"" IF EXISTS"

  oSQL_Anweisung.executeUpdate (stSql)

Jetzt wird die Abfrage in einer Schleife zusammengestellt, die anschließend an die zu Beginn angegebene Tabelle gestellt wird. Dabei werden alle Schreibweisen untersucht, da auch der Inhalt des Feldes in der Abfrage auf Kleinbuchstaben umgewandelt wird.

Die Abfrage wird direkt so gestellt, dass die Ergebniswerte in der Tabelle "Suchtmp" landen. Dabei wird davon ausgegangen, dass der Primärschlüssel an der ersten Position der Tabelle steht (arInhalt(0)).

  stSql = "SELECT """+arInhalt(0)+"""INTO ""Suchtmp"" FROM """+stTabelle+"""

                        WHERE "

  FOR inK = 0 TO (inI - 1)

   stSql = stSql+"LCase("""+arInhalt(inK)+""") LIKE '%"+stInhalt+"%'"

   IF inK < (inI - 1) THEN

    stSql = stSql+" OR "

   END IF

  NEXT

  oSQL_Anweisung.executeQuery(stSql)

 ELSE

  stSql = "DELETE FROM ""Suchtmp"""

  oSQL_Anweisung.executeUpdate (stSql)

 END IF

Das Anzeigeformular muss neu geladen werden. Es hat als Datenquelle eine Abfrage, in diesem Beispiel "Suchabfrage"

 oForm2 = oDrawpage.forms.getByName("Anzeige")

 oForm2.reload()

End Sub

Damit wurde eine Tabelle erstellt, die nun in einer Abfrage ausgewertet werden soll. Die Abfrage ist dabei möglichst so zu fassen, dass sie anschließend noch editiert werden kann. Im Folgenden also  ein Abfragecode:

SELECT * FROM "Suchtabelle"

WHERE "Nr." IN ( SELECT "Nr." FROM "Suchtmp" )

 OR "Nr." =

  CASE WHEN ( SELECT COUNT( "Nr." ) FROM "Suchtmp" ) > 0

  THEN '0' ELSE "Nr." END

Alle Elemente der "Suchtabelle" werden dargestellt. Auch der Primärschlüssel. Keine andere Tabelle taucht in der direkten Abfrage auf; somit ist auch kein Primärschlüssel einer anderen Tabelle nötig, damit das Abfrageergebnis weiterhin editiert werden kann.

Der Primärschlüssel ist in dieser Beispieltabelle unter dem Titel "Nr." abgespeichert. Durch das Makro wird genau dieses Feld ausgelesen. Es wird jetzt also zuerst nachgesehen, ob der Inhalt des Feldes "Nr." in der Tabelle "Suchtmp" vorkommt. Bei der Verknüpfung mit 'IN' werden ohne weiteres auch mehrere Werte erwartet. Die Unterabfrage darf also auch mehrere Datensätze liefern.

Bei größeren Datenmengen wird der Abgleich von Werten über die Verknüpfung IN aber zusehends langsamer. Es bietet sich also nicht an, für eine leere Eingabe in das Suchfeld einfach alle Primärschlüsselfelder der "Suchtabelle" in die Tabelle "Suchtmp" zu übertragen und dann auf die gleiche Art die Daten anzusehen. Stattdessen erfolgt bei einer leeren Eingabe eine Leerung der Tabelle "Suchtmp", so dass gar keine Datensätze mehr vorhanden sind. Hierauf zielt der zweite Bedingungsteil:

OR "Nr." = CASE WHEN ( SELECT COUNT( "Nr." ) FROM "Suchtmp" ) > 0
    THEN '-1' ELSE "Nr." END

Wenn in der Tabelle "Suchtmp" ein Datensatz gefunden wird, so ist das Ergebnis der ersten Abfrage größer als 0. Für diesen Fall gilt: "Nr." = '-1' (hier steht am Besten ein Zahlenwert, der als Primärschlüssel nicht vorkommt, also z.B. '-1' ). Ergibt die Abfrage genau 0 (Dies ist der Fall wenn keine Datensätze da sind), dann gilt "Nr." = "Nr.". Es wird also jeder Datensatz dargestellt, der eine "Nr." hat. Da "Nr." der Primärschlüssel ist, gilt dies also für alle Datensätze.

Suchen in Formularen und Ergebnisse farbig hervorheben

Bei größeren Inhalten eines Textfeldes ist oft unklar, an welcher Stelle denn nun die Suche den Treffer zu verzeichnen hat. Da wäre es doch gut, wenn das Formular den entsprechenden Treffer auch markieren könnte. So sollte das dann im Formular aussehen: