Base

Kapitel 9
MakrosDokumentationen zu LibreOffice unter de.libreoffice.org

Copyright
Dieses Dokument unterliegt dem Copyright © 2014. 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

Jürgen Thomas

Michael Niedermair

Jochen Schiffers

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 undanderer persönlicher Daten, die die E-Mail enthält, wird öffentlich archiviert und kannnicht gelöscht werden. Also, schreiben Sie mit Bedacht!

Datum der Veröffentlichung und Softwareversion
Veröffentlicht am 15.8.2014. Basierend auf der LibreOffice Version 4.3.

Anmerkung für Macintosh Nutzer
Einige Tastenbelegungen (Tastenkürzel) und Menüeinträge unterscheiden sich zwischen derMacintosh 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

EffektMenü-Auswahl Extras →Optionen

LibreOffice → EinstellungenZugriff auf die Programmoptionen

Rechts-Klick

Control+KlickÖffnen eines Kontextmenüs

Ctrl (Control)oder Strg (Steuerung)⌘ (Command)

Tastenkürzel in Verbindung mit anderen
TastenF5Shift+⌘
F5
Shift ⌘+F5

Öffnen des Dokumentnavigator-Dialogs

F11

⌘+T
T

Öffnen des Formatvorlagen-Dialogs

Makros2

Inhalt
Allgemeines zu Makros .................................................................................................................. 5
Makros in Base ............................................................................................................................... 6
Makros benutzen ....................................................................................................................... 6
Makros zuweisen ....................................................................................................................... 7
Ereignisse eines Formulars ................................................................................................... 7
Ereignisse innerhalb eines Formulars ................................................................................... 8
Bestandteile von Makros ............................................................................................................ 9
Der «Rahmen» eines Makros ................................................................................................ 9
Variablen definieren ............................................................................................................... 9
Zugriff auf das Formular ...................................................................................................... 10
Zugriff auf Elemente eines Formulars .................................................................................. 10
Zugriff auf die Datenbank .................................................................................................... 11
Datensätze lesen und benutzen .......................................................................................... 13
Datensätze bearbeiten – neu anlegen, ändern, löschen ..................................................... 14
Kontrollfelder prüfen und ändern ......................................................................................... 16
Englische Bezeichner in Makros .............................................................................................. 16
Eigenschaften bei Formularen und Kontrollfeldern .............................................................. 17
Methoden bei Formularen und Kontrollfeldern .................................................................... 23
Bedienbarkeit verbessern ............................................................................................................. 28
Automatisches Aktualisieren von Formularen .......................................................................... 28
Filtern von Datensätzen ........................................................................................................... 29
Daten über den Formularfilter filtern ........................................................................................ 32
Daten aus Textfeldern auf SQL-Tauglichkeit vorbereiten .......................................................... 33
Die aktuelle Office-Version ermitteln ........................................................................................ 33
Wert von Listenfeldern ermitteln .............................................................................................. 34
Datumswert aus einem Formularwert in eine Datumsvariable umwandeln .............................. 34
Suchen von Datensätzen ......................................................................................................... 35
Kombinationsfelder als Listenfelder mit Eingabemöglichkeit .................................................... 37
Textanzeige im Kombinationsfeld ........................................................................................ 38Übertragen eines Fremdschlüsselwertes vom Kombinationsfeld zum numerischen Feld .... 40
Kontrollfunktion für die Zeichenlänge der Kombinationsfelder ............................................. 46
Datensatzaktion erzeugen ................................................................................................... 46
Navigation von einem Formular zum anderen ......................................................................... 47
Hierarchische Listenfelder ....................................................................................................... 48
Zeiteingaben mit Millisekunden ................................................................................................ 51
Datenbankaufgaben mit Makros erweitert .................................................................................... 53
Verbindung mit Datenbanken erzeugen ................................................................................... 53
Datenbanksicherungen erstellen .............................................................................................. 53
Datenbanken komprimieren ..................................................................................................... 54
Tabellenindex heruntersetzen bei Autowert-Feldern ................................................................ 55
Drucken aus Base heraus ........................................................................................................ 55
Serienbriefdruck aus Base heraus ...................................................................................... 56
Drucken über Textfelder ...................................................................................................... 56
Aufruf von Anwendungen zum Öffnen von Dateien .................................................................. 57
Aufruf eines Mailprogramms mit Inhaltsvorgaben .................................................................... 58

3

Mauszeiger beim Überfahren eines Links ändern .................................................................... 59
Formulare ohne Symbolleisten präsentieren ............................................................................ 59
Formulare ohne Symbolleisten in einem Fenster ................................................................ 60
Formulare im Vollbildmodus ................................................................................................ 61
MySQL-Datenbank mit Makros ansprechen ................................................................................. 61
MySQL-Code in Makros ........................................................................................................... 61
Temporäre Tabelle als individueller Zwischenspeicher ............................................................. 62
Dialoge ......................................................................................................................................... 624

Allgemeines zu Makros
Prinzipiell kommt eine Datenbank unter Base ohne Makros aus. Irgendwann kann aber das
Bedürfnis kommen,• bestimmte Handlungsschritte zu vereinfachen (Wechsel von einem Formular zum anderen,
Aktualisierung von Daten nach Eingabe in einem Formular …),• Fehleingaben besser abzusichern,• häufigere Aufgaben zu automatisieren oder auch• bestimmte SQL-Anweisungen einfacher aufzurufen als mit dem separaten SQL-Editor.
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 bereitsder 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:

Allgemeines zu Makros

5• Unter Extras → Optionen → Sicherheit → Makrosicherheit ist die Sicherheitsstufe auf«Mittel» herunter zu stellen. Gegebenenfalls kann auch zusätzlich unter «Vertrauenswürdige Quellen» der Pfad angegeben werden, in dem eigene Dateien mit Makros liegen, um
spätere Nachfragen nach der Aktivierung von Makros zu vermeiden.
• Die Datenbankdatei muss nach der Erstellung des ersten Makro-Moduls einmal geschlossen und anschließend wieder geöffnet werden.
Einige Grundprinzipien zur Nutzung des Basic-Codes in LibreOffice:• Zeilen haben keine Zeilenendzeichen. Zeilen enden mit einem festen Zeilenumbruch.
• Zwischen Groß- und Kleinschreibung wird bei Funktionen, reservierten Ausdrücken usw.
nicht unterschieden. So ist z.B. die Bezeichnung «String» gleichbedeutend mit «STRING»
oder auch «string» oder eben allen anderen entsprechenden Schreibweisen. Groß- undKleinschreibung dienen nur der besseren Lesbarkeit.
• Eigentlich wird zwischen Prozeduren (beginnend mit SUB) und Funktionen (beginnend mit
FUNCTION) unterschieden. Prozeduren sind ursprünglich Programmabschnitte ohne Rückgabewert, Funktionen können Werte zurückgeben, die anschließend weiter ausgewertet
werden können. Inzwischen ist diese Unterscheidung weitgehend irrelevant; man spricht
allgemein von Methoden oder Routinen – mit oder ohne Rückgabewert. Auch eine Prozedur kann einen Rückgabewert (außer «Variant») erhalten; der wird einfach in der Definition
zusätzlich festgelegt:
SUB myProcedure AS INTEGEREND SUB

Zu weiteren Details siehe auch das Handbuch 'Erste Schritte Makros mit LibreOffice'.
Makros in diesem Kapitel sind entsprechend den Vorgaben aus dem Makro-Editorvon LibreOffice eingefärbt:HinweisMakro-Bezeichner
Makro-Kommentar
Makro-Operator
Makro-Reservierter-Ausdruck
Makro-Zahl
Makro-Zeichenkette

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.

•
•
•
•

Ereignisse eines Formular
Bearbeitung einer Datenquelle innerhalb des Formulars
Wechsel zwischen verschiedenen Kontrollfeldern
Reaktionen auf Maßnahmen innerhalb eines Kontrollfelds

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

Makros in Base

6

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
Maßnahmen, die beim Öffnen oder Schließen eines Formulars erledigt werden sollen, werden so
registriert:• Rufen Sie im Formularentwurf über Extras → Anpassen das Register Ereignisse auf.
• Wählen Sie das passende Ereignis aus der Gruppe «Ansicht» aus (die anderen kommen
nicht infrage).
• Suchen Sie über die Schaltfläche Makro das dafür definierte Makro und bestätigen Sie
diese Auswahl.
• Unter Speichern in ist das Formular anzugeben.
Dann kann diese Zuweisung mit OK bestätigt werden.

Makros in Base7

Ereignisse innerhalb eines Formulars
Alle anderen Makros werden bei den Eigenschaften von Teilformularen und Kontrollfeldern überdas Register Ereignisse registriert.

• Öffnen Sie (sofern noch nicht geschehen) das Fenster mit den Eigenschaften des Kontrollfelds.
• Wählen Sie im Register Ereignisse das passende Ereignis aus.

– Um die Datenquelle zu bearbeiten, gibt es vor allem die Ereignisse, die sich auf Datensatz––

oder Aktualisieren oder Zurücksetzen beziehen.
Zu Schaltflächen oder einer Auswahl bei Listen- oder Optionsfeldern gehört in erster Linie
das Ereignis Aktion ausführen.
Alle anderen Ereignisse hängen vom Kontrollfeld und der gewünschten Maßnahme ab.

• Durch einen Klick auf den rechts stehenden Button … wird das Fenster «Aktion zuweisen»geöffnet.
• Über die Schaltfläche Makro wird das dafür definierte Makro ausgewählt.
Über mehrfaches OK wird diese Zuweisung bestätigt.

Makros in Base

8

Bestandteile von Makros
In diesem Abschnitt sollen einige Teile der Makro-Sprache erläutert werden, die in Base – vorallem 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 oderohne Rückgabewert definiert werden und beliebig mit Argumenten versehen werden.
SUB Ausleihe_aktualisieren
END SUBSUB Zu_Formular_von_Formular(oEvent AS OBJECT)END SUBFUNCTION Loeschen_bestaetigen(oEvent AS OBJECT) AS BOOLEAN
Loeschen_bestaetigen = FALSEEND FUNCTION

Es ist hilfreich, diesen Rahmen sofort zu schreiben und den Inhalt anschließend einzufügen. Bittevergessen 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ß- undKleinschreibung. 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 OBJECTDIM oDrawpage AS OBJECTDIM 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 (A–Z oder a–z), 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.1 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 von StarBasic 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.
1

Die Kennzeichnung sollte eventuell noch verfeinert werden, da mit nur einem Buchstaben zwischen dem
Datentyp «Double» und dem Datentyp «Date» bzw. «Single» und «String» nicht unterschieden werdenkann.

Makros in Base

9

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 OBJECTDIM 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")

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:

HinweisoForm = 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.

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 dero.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.

Makros in Base

10

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. Wenndiese 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 OBJECTDIM 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 OBJECTDIM 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 OBJECTDIM 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 OBJECTDIM oDatasource AS OBJECTDIM 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 weiterverarbeitet. 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()

Makros in Base11

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 Benutzerin 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 vonZahlen, 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 seinmuss. 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 SQLBefehl 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)oSQL_Statement.setString(2, oTextfeld2.Text)oSQL_Statement.setLong(3, oZahlenfeld1.Value)
iResult = oSQL_Statement.executeUpdate' Text für den Nachnamen' Text für den Vornamen' Wert für die betreffende ID

Die vollständige Liste der Zuweisungen findet sich im Abschnitt «Parameter für vorbereitete SQLBefehle».
Wer sich weiter über die Vorteile dieses Verfahrens informieren möchte, findet hier Erläuterungen:• SQL-Injection (Wikipedia)• Why use PreparedStatement (Java JDBC)• SQL-Befehle (Einführung in SQL)

Makros in Base

12

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.

HinweisBei 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 MethodefindColumn 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 beachtensind.

• Es gibt keine Methode für Daten des Typs Decimal, Currency o.ä., also für kaufmännisch exakte Berechnungen. Da Basic automatisch die passende Konvertierung vornimmt,
kann ersatzweise getDouble verwendet werden.

• Bei getBoolean ist zu beachten, wie in der Datenbank «Wahr» und «Falsch» definiertsind. Die «üblichen» Definitionen (logische Werte, 1 als «Wahr») werden richtig verarbeitet.
• Datumsangaben können nicht nur mit dem Datentyp DATE definiert werden, sondern auch(wie oben) als util.Date. Das erleichtert u.a. Lesen und Ändern von Jahr, Monat, Tag.

• Bei ganzen Zahlen sind Unterschiede der Datentypen zu beachten. Im obigen Beispiel wird
getLong verwendet; auch die Basic-Variable ID muss den Datentyp Long erhalten, da dieser vom Umfang her mit Integer aus der Datenbank übereinstimmt.
Die vollständige Liste dieser Methoden findet sich im Abschnitt «Datenzeilen bearbeiten».

Ergebnis einer Abfrage
In gleicher Weise kann die Ergebnismenge einer Abfrage benutzt werden. Im Abschnitt «SQLBefehle» 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 einzelne Variable

Makros in Base13

sVar = oResult.getString(1)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.
Die Anzahl der Zeilen, die die Ergebnismenge enthält, kann so bestimmt werden:
DIM iResult AS LONG
IF oResult.lastiResult = oResult.getRow
ELSE
iResult = 0
END IF' gehe zum letzten Datensatz, sofern möglich' die laufende Nummer ist die Anzahl

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ürverschiedene Kontrollfelder gezeigt wird:sValue = oTextField.BoundField.Text
nValue = oNumericField.BoundField.ValuedValue = oDateField.BoundField.Date' Beispiel für ein Textfeld' Beispiel für ein numerisches Feld
' 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 iRow AS LONG
iRow = oForm.getRow()oForm.reload()
oForm.absolute(iRow)' notiere die aktuelle Zeilennummer' lade die Datenmenge neu' gehe wieder zu der notierten Zeilennummer

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

Datensätze bearbeiten – neu anlegen, ändern, löschen
Um Datensätze zu bearbeiten, müssen mehrere Teile zusammenpassen: Eine Information mussvom Anwender in das Kontrollfeld gebracht werden; das geschieht durch die Tastatureingabe.
Anschließend muss die Datenmenge «dahinter» diese Änderung zur Kenntnis nehmen; dasgeschieht 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.

Makros in Base

14

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 unddirekte 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. Wähle den aktuellen Datensatz.
2. Ändere die gewünschten Werte, wie im vorigen Abschnitt beschrieben.
3. Bestätige die Änderungen mit folgendem Befehl:
oForm.updateRow()

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 eineneue Zeile im Tabellenkontrollfeld). Es werden also folgende Maßnahmen benötigt:
1. Bereite einen neuen Datensatz vor:oForm.moveToInsertRow()

2. Trage alle vorgesehenen und benötigten Werte ein. Dies geht ebenfalls mit den
updateXxx-Methoden, wie im vorigen Abschnitt beschrieben.
3. Bestätige die Neuaufnahme mit folgendem Befehl:
oForm.insertRow()

Makros in Base15

4. Die Neuaufnahme kann nicht einfach rückgängig gemacht werden. Stattdessen ist diesoeben 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. Wähle – wie für eine Änderung – den gewünschten Datensatz und mache ihn zum aktuellen.
2. Bestätige die Löschung mit folgendem Befehl:
oForm.deleteRow()

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 Zusatzinformationeines 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 1oField.TextColor = RGB(0,0,255)' 1 = blau
CASE 2oField.TextColor = RGB(255,0,0)' 2 = rot
CASE ELSE
oField.TextColor = RGB(0,255,0)' 0 = grün (weder 1 noch 2)
END SELECTEND 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 inden Ü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:• NameBezeichnung der Eigenschaft oder Methode im Makro-Code• Datentyp Einer der Datentypen von Basic
• L/S
L
S
(L)

Makros in BaseBei Funktionen ist der Typ des Rückgabewerts angegeben; bei Prozeduren
entfällt diese Angabe.
Hinweis darauf, wie der Wert der Eigenschaft verwendet wird:
nur Lesen
nur Schreiben (Ändern)
Lesen möglich, aber für weitere Verarbeitung ungeeignet

16

(S)
Schreiben möglich, aber nicht sinnvoll
L+S geeignet für Lesen und Schreiben
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

stringL+S

Schriftart.

FontHeightsingleL+S

Schriftgröße.

FontWeightsingleL+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.

FilterstringL+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.

RowCountlongLAnzahl der Datensätze.

Einheitlich für alle Arten von Kontrollfeld
Englische Bezeichnung: Control – siehe auch FormComponent
Name

Datentyp

L/S

Eigenschaft

NamestringL+(S)Bezeichnung für das Feld.

Enabledboolean

L+S

Aktiviert: Feld kann ausgewählt werden.

EnableVisible

boolean

L+S

Sichtbar: Feld wird dargestellt.

ReadOnlyboolean

L+S

Nur lesen: Inhalt kann nicht geändert werden.

Makros in Base

17

Name

Datentyp

L/S

Eigenschaft

TabStopboolean

L+S

Feld ist in der Tabulator-Reihenfolge erreichbar.

Aligninteger

L+S

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

BackgroundColor

long

L+S

Hintergrundfarbe.

Tag

stringL+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.

Spinboolean

L+S

Drehfeld eingeblendet (bei formatierten Feldern).

TextColor

long

L+S

Textfarbe.

DataField

stringL

Name des Feldes aus der Datenmenge

BoundField

objectL

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

StringstringL+S

Inhalt des Feldes aus der Anzeige.

MaxTextLen

integer

L+S

Maximale Textlänge.

DefaultText

stringL+S

Standardtext.

MultiLineboolean

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

doubleL+S

Minimalwert zur Eingabe.

ValueMax

doubleL+S

Maximalwert zur Eingabe.

Value

doubleL+(S)Aktueller Wert
nicht für Werte aus der Datenmenge verwenden.

ValueStep

doubleL+S

Intervall bei Verwendung mit Mausrad oder
Drehfeld.

DefaultValue

doubleL+S

Standardwert.

DecimalAccuracy

integer

L+S

Nachkommastellen.

Makros in Base

18

Name

Datentyp

L/S

Eigenschaft

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 undupdateDate sowie dem Typ com.sun.star.util.Date verweisen wir auf die Beispiele.
Name

DatentypDatentypab LO 4.1.1

L/S

Eigenschaft

DateMinlong

com.sun.star.util.Date

L+S

Minimalwert zur Eingabe.

DateMax

longcom.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 (Jahrvierstellig)
3 = lange Datumsangabe (mit Wochentag
und Monatsnamen)
Weitere Möglichkeiten sind der Formulardefinition oder der API-Referenz zu entnehmen.

DefaultDate

long

L+S

Standardwert.

DropDownboolean

L+S

Aufklappbaren Monatskalender anzeigen.

com.sun.star.util.Date

Zeitfeld
Englische Bezeichnung: TimeField
Auch Zeitwerte werden als Datentyp long definiert.
Name

DatentypDatentypab LO 4.1.1

L/S

Eigenschaft

TimeMinlong

com.sun.star.util.Time

L+S

Minimalwert zur Eingabe.

TimeMax

longcom.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.

Makros in Base

19

Name

Datentyp

TimeFormat

integer

DefaultTime

long

Datentypab LO 4.1.1

com.sun.star.util.Time

L/S

Eigenschaft

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

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. Sehrviele der bisher genannten Eigenschaften gibt es auch hier, aber mit anderer Bezeichnung.
Name

Datentyp

L/S

Eigenschaft

CurrentValue

variant

L
L+(S)

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

EffectiveValue
EffectiveMin

doubleL+S

Minimalwert zur Eingabe.

EffectiveMax

doubleL+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.

Makros in Base

20

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 stringL

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

ItemCountintegerL

Anzahl der vorhandenen Listeneinträge.

ValueItemList

array of stringL

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

DropDownboolean

L+S

Aufklappbar.

LineCountinteger

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) )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()

HinweisMit getCurrentValue() wird also immer der Wert ausgegeben, der auch tatsächlichin 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 andie 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 OBJECTDIM oDrawpage AS OBJECTDIM oForm AS OBJECTDIM 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

Makros in Base

21

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

Autocompleteboolean

L+S

Automatisch füllen.

StringItemList

array of string

L+S

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

ItemCountintegerL

Anzahl der vorhandenen Listeneinträge.

DropDownboolean

L+S

Aufklappbar.

LineCountinteger

L+S

Anzahl der angezeigten Zeilen im aufgeklappten
Zustand.

Text

stringL+S

Aktuell angezeigter Text.

DefaultText

stringL+S

Standardeintrag.

ListSource

stringL+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

LabelstringL+S

Titel (Beschriftung)

State

short

L+S

Status0 = nicht ausgewählt
1 = ausgewählt
2 = unbestimmt

MultiLineboolean

L+S

Wortumbruch (bei zu langem Text).

Maskiertes Feld
Englische Bezeichnung: PatternField
Neben den Eigenschaften für «einfache» Textfelder sind folgende interessant.
Name

Datentyp

L/S

Eigenschaft

EditMask

stringL+S

Eingabemaske.

LiteralMask

stringL+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.

Makros in Base

22

Name

Datentyp

L/S

Eigenschaft

ElementNames

array of stringL

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

LabelstringL+S

Der angezeigte Text.

MultiLineboolean

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

LabelstringL+S

Titel – Text der Beschriftung.

State

short

L+S

Standardstatus «ausgewählt» bei «Umschalten».

MultiLineboolean

L+S

Wortumbruch (bei zu langem Text).

DefaultButtonboolean

L+S

Standardschaltfläche

Navigationsleiste
Englische Bezeichnung: NavigationBar
Weitere Eigenschaften und Methoden, die mit der Navigation zusammenhängen – z.B. Filter unddas Ändern des Datensatzzeigers –, werden über das Formular geregelt.
Name

Datentyp

L/S

Eigenschaft

IconSize

short

L+S

Symbolgröße.

ShowPositionboolean

L+S

Positionierung anzeigen und eingeben.

ShowNavigationboolean

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:

•
•
•
•

c

Nummer der Spalte des gewünschten Feldes in der Datenmenge – ab 1 gezähltnnumerischer Wert – je nach Situation als ganze Zahl oder als DezimalzahlsZeichenkette (String); die maximale Länge ergibt sich aus der Tabellendefinitionbboolean (Wahrheitswert) – true (wahr) oder false (falsch)

Makros in Base23• d Datumswert
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

booleanDer Cursor steht vor der ersten Zeile, wenn der Cursor nachdem Einlesen noch nicht gesetzt wurde.

isFirstboolean

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

isLastboolean

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

isAfterLast

booleanDer 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.

firstbooleanWechselt zur ersten Zeile.

previous

boolean

Geht um eine Zeile zurück.

nextboolean

Geht um eine Zeile vorwärts.

lastbooleanWechselt 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.

Makros in Base

24

Name

Datentyp

Beschreibung

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.

getString(c)stringLiefert 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)shortLiefert den Inhalt der Spalte als ganze Zahl.

getInt(c)integer

getLong(c)longgetFloat(c)float

Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit.

getDouble(c)doubleLiefert den Inhalt der Spalte als Dezimalzahl von doppelter Genauigkeit. – Wegen der automatischen Konvertierung durch Basic ist dies auch für decimal- undcurrency-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).

Werte lesen

In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Fürden 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.
wasNullboolean

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

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)–

updateDouble(c,n)–

Werte speichern

Makros in Base

Speichert in Spalte c die angegebene Dezimalzahl n.

25

Name

Datentyp

Beschreibung

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.
Hinweis: Damit eine Änderung in die Datenbank übernommen wird, ist sie durch «updateRow»bzw. «insertRow» ausdrücklich zu bestätigen.

Makros in Base

26

Name

Datentyp

Beschreibung

getString

stringLiefert den Inhalt der Spalte als Zeichenkette.

getBooleanboolean

Liefert den Inhalt der Spalte als Wahrheitswert.

getBytebyte

Liefert den Inhalt der Spalte als einzelnes Byte.

getShort

short

Liefert den Inhalt der Spalte als ganze Zahl.

getIntinteger

getLonglong

getFloat

float

Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit.

getDouble

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

getBytesarray of bytes

Liefert den Inhalt der Spalte als Folge einzelner Bytes.

getDate

Date

Liefert den Inhalt der Spalte als Datumswert.

getTimeTime

Liefert den Inhalt der Spalte als Zeitwert.

getTimestamp

DateTime

Liefert den Inhalt der Spalte als Zeitstempel (Datum undZeit).

Werte lesen

In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Fürden 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.
wasNullboolean

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

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)–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.

Werte speichern

Makros in Base

Speichert in der Spalte die angegebene Dezimalzahl n.

27

Parameter für vorbereitete SQL-Befehle
Die Methoden, mit denen die Werte einem vorbereiteten SQL-Befehl – siehe «Vorbereitete SQLBefehle 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.

–

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

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 SQLBefehls.

setInt(i, n)
setLong(i, n)
setFloat(i, n)
setDouble(i, n)Bedienbarkeit verbessern
Als erste Kategorie werden verschiedene Möglichkeiten vorgestellt, die zur Verbesserung derBedienbarkeit 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 vonauß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.

Bedienbarkeit verbessern

28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 «o» gesetzt worden. Prinzipiell ist aber die Variablenbezeichnung nahezu völlig frei wählbar.
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECTDIM 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 deroberste 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 SUBDie 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 undliest die Listenfelder neu ein, so dass ein gewählter Filter aus einem Listenfeld die Auswahl in dem
anderen Listenfeld einschränken kann.
Siehe zu diesem Abschnitt auch die Beispieldatenbank «Suchen_und_Filtern.odb»
SUB Filter
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECTDIM oForm1 AS OBJECTDIM oForm2 AS OBJECTDIM oFeldList1 AS OBJECTDIM 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")

Bedienbarkeit verbessern

29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, undder wird zu Beginn einmal geschrieben. Dieser Datensatz wird also laufend durch ein UpdateKommado ü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, dasgerade 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"Tabelle_Filterergebnis" 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 Sucheerst 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 auchdie Bücher zu sehen, die 2. Listbox erst einmal wieder auf NULL gestellt werden, um dann auchdie 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.
SELECTIFNULL( "Feld_1" || ' - ' || "Anzahl", 'leer - ' || "Anzahl" ) AS
"Anzeige","Feld_1"
FROM

Bedienbarkeit verbessern

30

( SELECT COUNT( "ID" ) AS "Anzahl", "Feld_1" FROM "Tabelle" WHERE "ID"IN
( SELECT "Tabelle"."ID" FROM "Filter", "Tabelle" WHERE"Tabelle"."Feld_2" = IFNULL( "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 sichhä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 Bindestrichabgesetzt, 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"). Diesgeschieht 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" = IFNULL( "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" = IFNULL( "Filter"."Filter_2", "Tabelle"."Feld_2" )AND"Tabelle"."Feld_3" = IFNULL( "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 OBJECTDIM oDrawpage AS OBJECTDIM oForm1 AS OBJECTDIM oForm2 AS OBJECTDIM oFeldList1 AS OBJECTDIM oFeldList2 AS OBJECT
DIM sTag AS StringsTag = oEvent.Source.Model.Tag

Bedienbarkeit verbessern

31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 vondem Listenfeld, das mit der Aktion (Event) verbunden ist.
aList() = Split(sTag, ",")
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 derDatensatz 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 IFNEXT
oForm2.reload()
END SUBDie 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 OBJECTDIM oForm AS OBJECTDIM oFeld AS OBJECT
DIM stFilter As StringoForm = 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 entsprechenden
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 SUBDie Beendigung des Filters kann natürlich auch über die Navigationsleiste erfolgen. In diesem Fall
wird einfach ein weiteres Makro genutzt.

Bedienbarkeit verbessern

32Ü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ürden 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 = stEND 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-Codewird 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 inder 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.

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.PropertyValueDIM 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.

Bedienbarkeit verbessern

33

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.currentValueELSE

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 ineinem Listenfeld ausgewählt werden könnten.
stInhalt = oFeld.ValueItemList(oFeld.SelectedItems(0))
END IFIF IsEmpty(stInhalt) THEN

Mit -1 wird ein Zahlenwert weiter gegeben, der nicht als AutoWert verwendet wird, also 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 automatischhoch 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.

Datumswert aus einem Formularwert in eine Datumsvariableumwandeln
FUNCTION Datumswert(oFeld AS OBJECT) AS DATEa() = 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 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 kannalso nicht auf das Datum selbst zugegriffen werden. Entsprechend ist es neu aus den Werten fürTag, Monat und Jahr zusammen zu setzen, damit anschließend in Makros damit weiter gearbeitet
werden kann.

Bedienbarkeit verbessern

34Suchen von Datensätzen
Ebenfalls ohne Makro funktioniert auch das Suchen von Datensätzen. 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 undschreibt daraus schließlich eine Liste der Primärschlüsselnummern der durchsuchten Tabelle auf,
auf der der Suchbegriff zutrifft. Für die folgende Beschreibung existiert eine Tabelle "Suchtmp", dieaus 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.
Siehe zu diesem Abschnitt auch die Beispieldatenbank «Suchen_und_Filtern.odb»
SUB Suche(stTabelle AS STRING)DIM oDatenquelle AS OBJECTDIM oVerbindung AS OBJECTDIM oSQL_Anweisung AS OBJECT
DIM stSql AS STRING
DIM oAbfrageergebnis AS OBJECTDIM oDoc AS OBJECTDIM oDrawpage AS OBJECTDIM oForm AS OBJECTDIM oForm2 AS OBJECTDIM 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)

Bedienbarkeit verbessern

35Hinweis

SQL-Formulierungen müssen in Makros wie normale Zeichenketten zuerst einmal indoppelten Anführungsstrichen gesetzt werden. Feldbezeichnungen und Tabellenbezeichnungen stehen innerhalb der SQL-Formulierungen 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 wird, wird zuerst auf 0 gesetzt. Dannwird 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 IFNEXToSQL_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

Bedienbarkeit verbessern

36Alle Elemente der "Suchtabelle" werden dargestellt. Auch der Primärschlüssel. Keine andereTabelle 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 allePrimä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 derzweite 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 derFall 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.

Kombinationsfelder als Listenfelder mit Eingabemöglichkeit
Aus Kombinationsfeldern und unsichtbaren numerischen Feldern kann direkt eine Tabelle mit
einem Datensatz versehen werden und der entsprechende Primärschlüssel in eine andere Tabelle
eingetragen werden.
In den Vorversionen von LO oder OOo war es notwendig, die numerischen Felder per Makro
unsichtbar zu machen. Dies ist unter LibreOffice nicht mehr notwendig, da die Eigenschaft 'Sichtbar' in der GUI enthalten ist.
Das Modul «Comboboxen» macht aus den Formularfeldern zur Eingabe und Auswahl von Werten(Kombinationsfelder) Listenfelder mit Eingabemöglichkeiten. Dazu werden neben den Kombinationsfeldern im Formular die jeweils an die zugrundeliegende Tabelle zu übergebenden Schlüsselfeldwerte in separaten numerischen Feldern abgelegt. Diese separaten Felder mussten vor OOo
3.3 als «unsichtbar» geschaltet werden, da die Funktion über das Formulardesign nicht erreichbar
war. Diese Funktion konnte in LibreOffice und OOo 3.3 entfernt werden. Felder können jetzt als
unsichtbar deklariert werden. Die Schlüssel aus diesen Feldern werden beim Start des Formulars
ausgelesen und das Kombinationsfeld auf den entsprechenden Inhalt eingestellt. Wird der Inhalt
des Kombinationsfeldes geändert, so wird er neu abgespeichert und der neue Primärschlüssel
zum Abspeichern in der Haupttabelle in das entsprechende numerische Feld übertragen.
Werden statt der Tabellen entsprechend konstruierte eingabefähige Abfragen erstellt, so kann derText, den die Kombinationsfelder darstellen sollen, direkt aus den Abfragen ermittelt werden. Ein
Makro ist dann für diesen Arbeitsschritt nicht notwendig.
Das Original dieses Moduls befindet sich in der Beispieldatenbank, die durch Makros erweitert ist.
Voraussetzung für die Funktionsweise des Makros ist, dass alle Primärschlüssel der Tabellen, diein den Kombinationsfeldern als Datenquellen auftauchen, mit einem automatisch hochzählenden
Autowert versehen sind. Außerdem ist als Bezeichnung hier vorausgesetzt, dass die Primärschlüssel den Namen "ID" tragen.

Bedienbarkeit verbessern

37Textanzeige im Kombinationsfeld
Diese Prozedur soll Text in den Kombinationsfeldern nach den Werten der (unsichtbaren) Fremdschlüssel-Felder aus dem Hauptformular einstellen. Dabei werden gegebenenfalls auch Listenfelder berücksichtigt, die sich auf 2 unterschiedliche Tabellen beziehen. Dies kann z.B. dann sein,
wenn bei einer Ortsangabe die Postleitzahl vom Ort abgetrennt wurde. Dann wird die Postleitzahl
aus einer Tabelle ausgelesen, in der auch ein Fremdschlüssel für den Ort liegt. Im Listenfeld werden Postleitzahl und Ort zusammen angezeigt.
SUB TextAnzeigen(oEvent AS OBJECT)

Dieses Makro sollte an das folgende Ereignis des Formulars gebunden werden: 'Nach dem Datensatzwechsel'
Das Makro wird direkt aus dem Formular angesprochen. Über das auslösende Ereignis werden die
gesamten notwendigen Variablen für das Makro ermittelt.
Die Variablen werden deklariert. Einige Variablen sind in einem separaten Modul bereits global
deklariert und werden hier nicht noch einmal erwähnt.
DIM oForm AS OBJECTDIM oFeld AS OBJECTDIM oFeldList AS OBJECT
DIM stAbfrage AS STRING
DIM stFeldWert AS STRING
DIM inCom AS INTEGER
oForm = oEvent.Source

Das Formular startet das Ereignis. Es ist die Quelle für das das Makro auslösende Ereignis.
In dem Formular befindet sich ein verstecktes Kontrollelement, aus dem hervor geht, wie die verschiedenen Kombinationsfelder in diesem Formular heißen. Nacheinander werden dann in dem
Makro die Kombinationsfelder abgearbeitet.
aComboboxen() = Split(oForm.getByName("Comboboxen").Tag,",")
FOR inCom = LBound(aComboboxen) TO Ubound(aComboboxen)...
NEXT inCom

Aus den Zusatzinformationen («Tag») des versteckten Kontrollelementes wird die Bezeichnung derKombinationsfelder ermittelt. Sie sind dort durch Kommas voneinander getrennt aufgeschrieben.
Die Namen der Felder werden in ein Array geschrieben und nacheinander in einer Schleife abgearbeitet. Die Schleife endet mit der Bezeichnung 'NEXT …'.
Das Kombinationsfeld, das jetzt statt eines Listenfeldes existiert, wird anschließend als'oFeldList' bezeichnet. Das Feld, das den Fremdschlüssel enthält, wird aus den Zusatzinformationen des Kombinationsfeldes ermittelt und als 'oFeld' bezeichnet.
oFeldList = oForm.getByName(Trim(aComboboxen(inCom)))
oFeld = oForm.getByName(oFeldList.Tag)
oFeldList.Refresh()

Das Kombinationsfeld wird mit 'Refresh()' neu eingelesen. Es kann ja sein, dass sich derInhalt des Feldes durch Neueingaben geändert hat. Diese müssen schließlich verfügbar gemacht
werden.
Anschließend wird der Wert des Fremdschlüssel ausgelesen. Nur wenn hier ein Wert eingetragenist, wird eine Verbindung mit der Datenquelle hergestellt.
Die Abfrage, die zur Ermittlung des an zu zeigenden Inhaltes des Kombinationsfeldes notwendig
ist, wird aus der Abfrage des Kombinationsfeldes und dem ermittelten Wert des Fremdschlüssels
erstellt. Damit der SQL-Code brauchbar wird, wird zuerst eine eventuelle Sortierungsanweisung
entfernt. Anschließend wird nachgesehen, ob bereits eine Beziehungsdefinition (beginnend mit'WHERE') existiert. Da die 'InStr()'-Funktion standardmäßig keinen Unterschied zwischen
Groß- und Kleinschreibung macht werden hier gleich alle Schreibweisen abgedeckt. Existiert eineBeziehungsdefinition, so enthält die Abfrage Felder aus zwei unterschiedlichen Tabellen. Es muss
jetzt die Tabelle herausgesucht werden, aus der der Fremdschlüssel für die Verbindung zur VerfüBedienbarkeit verbessern

38gung gestellt wird. Das Makro funktioniert hier nur mit Hilfe der Information, dass der Primärschlüssel einer jeden Tabelle "ID" heißt.
Existiert keine Beziehungsdefinition, so beruht die Abfrage nur auf einer Tabelle. Die Tabelleninformation kann entfallen, die Bedingung direkt mit dem Fremdschlüsselwert zusammen formuliert
werden.
IF NOT IsEmpty(oFeld.getCurrentValue()) THEN
stAbfrage = oFeldList.ListSource
stSql = Left(stAbfrage, InStr(stAbfrage,"order by")-1)
IF InStr(stSql,"where") THEN
st = Right(stSql, Len(stSql)-InStr(stSql,"where")-4)
IF InStr(Left(st, InStr(st,"=")),".""ID""") THENa() = Split(Right(st, Len(st)-InStr(st,"=")-1),".")ELSE
a() = Split(Left(st, InStr(st,"=")-1),".")END IF
stSql = stSql + "AND "+a(0)+".""ID"" = "+oFeld.getCurrentValue
ELSE
stSql = stSql + "WHERE ""ID"" = "+oFeld.getCurrentValueEND IF

Jedes Feld und jeder Tabellenname muss bereits in der SQL-Eingabe mit doppelten Anführungsstrichen oben versehen werden. Da bereits Anführungsstriche einfacher Art in Basic als die Einführung zu Text interpretiert werden, sind diese bei der Weitergabe des Codes nicht mehr sichtbar.
Erst bei einer Doppelung der Anführungsstriche wird ein Element mit einfachen Anführungsstrichen
weitergegeben. ""ID"" bedeutet also, dass in der Abfrage auf das Feld "ID" (mit einfachen
Anführungsstrichen für die SQL-Verbindung) zugegriffen wird.
Die in der Variablen 'stSql' abgespeicherte Abfrage wird jetzt ausgeführt und das Ergebnis dieser Abfrage in der Variablen 'oAbfrageergebnis' gespeichert.
oDatenquelle = ThisComponent.Parent.CurrentController
IF NOT (oDatenquelle.isConnected()) ThenoDatenquelle.connect()
End IFoVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)

Das Abfrageergebnis wird über eine Schleife ausgelesen. Hier könnten, wie in einer Abfrage ausder GUI, mehrere Felder und Datensätze dargestellt werden. Von der Konstruktion der Abfrage her
wird aber nur ein Ergebnis erwartet. Dieses Ergebnis wird in der ersten Spalte (1) der Abfrage zu
finden sein. Es ist der Datensatz, der den anzuzeigenden Inhalt des Kombinationsfeldes wiedergibt. Der Inhalt ist ein Textinhalt ('getString()'), deshalb hier'oAbfrageergebnis.getString(1)'.
WHILE oAbfrageergebnis.next
stFeldWert = oAbfrageergebnis.getString(1)WEND

Das Kombinationsfeld muss jetzt auf den aus der Abfrage sich ergebenden Textwert eingestellt
werden.
oFeldList.Text = stFeldWert
END IF
END IF

Falls überhaupt kein Wert in dem Feld für den Fremdschlüssel 'oFeld' vorhanden ist, ist auchdie ganze Abfrage nicht gelaufen. Das Kombinationsfeld wird jetzt auf eine leere Anzeige eingestellt.
IF IsEmpty(oFeld.getCurrentValue()) THEN
oFeldList.Text = ""END IF
NEXT inCom
END SUB

Bedienbarkeit verbessern

39Diese Prozedur erledigt jetzt also den Kontakt von dem in einem versteckten Feld abgelegten
Fremdschlüssel zu dem Kombinationsfeld. Für die Anzeige der richtigen Werte im Kombinationsfeld würde das ausreichen. Eine Abspeicherung von neuen Werten hingegen benötigt eine weitere
Prozedur.

Übertragen eines Fremdschlüsselwertes vom Kombinationsfeld zum numerischen
Feld
Wird nun ein neuer Wert ausgewählt oder neu in das Kombinationsfeld eingegeben (nur wegendieser Eigenschaft wurde ja das Makro konstruiert), so muss der entsprechende Primärschlüssel
als Fremdschlüssel in die dem Formular zugrundeliegende Tabelle eingetragen werden.
SUB TextAuswahlWertSpeichern(oEvent AS OBJECT)

Dieses Makro sollte an das folgende Ereignis des Formulars gebunden werden: 'Vor der Datensatzaktion'.
Nach Deklaration der Variablen (hier nicht weiter aufgeführt) wird zuerst differenziert, bei welchem
Ereignis genau das Makro überhaupt ablaufen soll. Vor der Datensatzaktion werden zwei Implementationen nacheinander aufgerufen. Für das Makro selbst ist es wichtig, das Formularobjekt zu
erhalten. Das geht prinzipiell über beide Implementationen, aber eben auf unterschiedliche Weise.
Es wird hier die Implementation mit dem Namen "ODatabaseForm" herausgefiltert.
IF InStr(oEvent.Source.ImplementationName,"ODatabaseForm") THEN...
END IF
END SUB

In diese Schleife wird der gleiche Start wie bei der Prozedur TextAnzeigen eingebaut:oForm = oEvent.Source
aComboboxen() = Split(oForm.getByName("Comboboxen").Tag,",")
FOR inCom = LBound(aComboboxen) TO Ubound(aComboboxen)...
NEXT inCom

Das Feld 'oFeldList' zeigt den Text an. Es kann in einem Tabellenkontrollfeld liegen. Dannkann nicht direkt vom Formular auf das Feld zugegriffen werden. In den Zusatzinformationen desversteckten Kontrollfeldes «Comboboxen» ist für diesen Fall der Pfad zum Kombinationsfeld über«Tabellenkontrollfeld>Kombinationsfeld» eingetragen. Durch Aufsplittung dieses Eintrages wird
ermittelt, wie das Kombinationsfeld anzusprechen ist.
a() = Split(Trim(aComboboxen(inCom)),">")IF Ubound(a) > 0 THEN
oFeldList = oForm.getByName(a(0)).getByName(a(1))
ELSEoFeldList = oForm.getByname(a(0))
END IFoFeld = oForm.getByName(Trim(oFeldList.Tag))

Das Feld 'oFeld' enthält den Fremdschlüssel, der über das Formular gespeichert wird. Das Feld
wird über die Zusatzinformationen ermittelt. Es liegt auch bei Tabellenkontrollfeldern außerhalb desTabellenkontrollfeldes direkt auf der Ebene des Formulars.
Anschließend wird die Abfrage aus dem Kombinationsfeld ausgelesen und in ihre Einzelteile zerlegt. Bei einfachen Kombinationsfeldern wären die notwendigen Informationen lediglich der Feldname und der Tabellenname:
SELECT "Feld" FROM "Tabelle"
Dies könnte gegebenenfalls noch durch eine Sortierung erweitert sein. Sobald zwei Felder in dem
Kombinationsfeld zusammen dargestellt werden muss aber bereits bei den Feldern zur Trennung
entsprechend mehr Aufwand getrieben werden:
SELECT "Feld1"||' '||"Feld2" FROM "Tabelle"

Bedienbarkeit verbessern

40Diese Abfrage fasst zwei Felder zusammen und setzt dazwischen eine Leertaste ein. Da der Trenner eine Leertaste ist, wird in dem Makro nach so einem Trenner gesucht und danach der Text in
zwei Teile gesplittet. Das funktioniert natürlich nur dann einwandfrei, wenn "Feld1" nicht bereits
Text enthalten soll, der eine Leertaste erlaubt. Sonst wird z.B. aus dem Vornamen «Anne Marie»
und dem Nachnamen «Müller» durch das Makro der Vorname «Anne» und der Nachname «Marie
Müller». Für solch einen Fall sollte ein passender Trenner eingesetzt werden, der dann auch vom
Makro gefunden werden kann. Bei Namen ist dies z.B. «Nachname, Vorname».
Noch komplizierter wird es, wenn die beiden enthaltenen Felder aus zwei verschiedenen Tabellen
stammen:
SELECT "Tabelle1"."Feld1"||' > '||"Tabelle2"."Feld2" FROM "Tabelle1", "Tabelle2" WHERE"Tabelle1"."ID" = "Tabelle2"."FremdID" ORDER BY "Tabelle1"."Feld1"||' > '||"Tabelle2"."Feld2"
ASC
Hier müssen die Felder voneinander getrennt werden, die Tabellenzuordnungen zu den Feldern
erfasst werden und die Fremdschlüsselzuweisung ermittelt werden.
stAbfrage = oFeldList.ListSource
aFelder() = Split(stAbfrage, """")
stInhalt = ""FOR i=LBound(aFelder)+1 TO UBound(aFelder)

Der Inhalt der Abfrage wird von Ballast befreit. Die Teile werden anschließend über eine nicht übliche Zeichenkombination zu einem Array wieder zusammengefügt.«FROM» trennt die sichtbare
Feldanzeige von der Tabellenbezeichnung. «WHERE» trennt die Beziehungsdefinition von derTabellenbezeichnung. Joins werden nicht unterstützt.
IF Trim(UCASE(aFelder(i))) = "ORDER BY" THEN
EXIT FORELSEIF Trim(UCASE(aFelder(i))) = "FROM" THEN
stInhalt = stInhalt+" §§ "ELSEIF Trim(UCASE(aFelder(i))) = "WHERE" THEN
stInhalt = stInhalt+" §§ "ELSE
stInhalt = stInhalt+Trim(aFelder(i))
END IF
NEXT iaInhalt() = Split(stInhalt, " §§ ")Die sichtbare Feldanzeige wird gegebenenfalls in Inhalte aus verschiedenen Feldern aufgeteilt:
aErster() = Split(aInhalt(0),"||")IF UBound(aErster) > 0 THENIF UBound(aInhalt) > 1 THEN

Der erste Teil enthält mindestens 2 Felder. Die Felder haben zu Beginn eine Tabellenbezeichnung.
Der zweite Teil enthält zwei Tabellenbezeichnungen, die aber schon aus dem ersten Teil ermittelt
werden können. Der dritte Teil enthält eine Beziehung über einen Fremdschlüssel mit «=»getrennt:
aTest() = Split(aErster(0),".")NameTabelle1 = aTest(0)NameTabellenFeld1 = aTest(1)Erase aTest
Feldtrenner = Join(Split(aErster(1),"'"),"")
aTest() = Split(aErster(2),".")NameTabelle2 = aTest(0)NameTabellenFeld2 = aTest(1)Erase aTest
aTest() = Split(aInhalt(2),"=")
aTest1() = Split(aTest(0),".")
IF aTest1(1) <> "ID" THEN
NameTab12ID = aTest1(1)IF aTest1(0) = NameTabelle1 THEN
Position = 2
ELSE
Position = 1

Bedienbarkeit verbessern

41

END IFELSE
Erase aTest1
aTest1() = Split(aTest(1),".")NameTab12ID = aTest1(1)IF aTest1(0) = NameTabelle1 THEN
Position = 2
ELSE
Position = 1
END IF
END IFELSE

Der erste Teil enthält zwei Feldbezeichnungen ohne Tabellenbezeichnung, eventuell mit Trenner,
der zweite Teil enthält die Tabellenbezeichnung. Ein dritter Teil ist nicht vorhanden:
IF UBound(aErster) > 1 THEN
NameTabellenFeld1 = aErster(0)Feldtrenner = Join(Split(aErster(1),"'"),"")NameTabellenFeld2 = aErster(2)ELSE
NameTabellenFeld1 = aErster(0)NameTabellenFeld2 = aErster(1)END IF
NameTabelle1 = aInhalt(1)END IF
ELSE

Es existiert nur ein Feld, das aus einer Tabelle stammt:
NameTabellenFeld1 = aErster(0)NameTabelle1 = aInhalt(1)END IFDie maximale Zeichenlänge, die eine Eingabe haben darf, wird im Folgenden mit der Funktion'Spaltengroesse' ermittelt. Das Kombinationsfeld kann hier mit seiner Beschränkung nicht sicher
weiterhelfen, da ja ermöglicht werden soll, gleichzeitig 2 Felder in einem Kombinationsfeld einzutragen.
LaengeFeld1 = Spaltengroesse(NameTabelle1,NameTabellenFeld1)
IF NameTabellenFeld2 <> "" THEN
IF NameTabelle2 <> "" THEN
LaengeFeld2 = Spaltengroesse(NameTabelle2,NameTabellenFeld2)
ELSE
LaengeFeld2 = Spaltengroesse(NameTabelle1,NameTabellenFeld2)
END IF
ELSE
LaengeFeld2 = 0
END IF

Der Inhalt des Kombinationsfeldes wird ausgelesen:stInhalt = oFeldList.getCurrentValue()

Der angezeigte Inhalt des Kombinationsfeldes wird ausgelesen. Leertasten und nicht druckbare
Zeichen am Anfang und Ende der Eingabe werden gegebenenfalls entfernt.
StInhalt = Trim(stInhalt)
IF stInhalt <> "" THEN
IF NameTabellenFeld2 <> "" THEN

Wenn ein zweites Tabellenfeld existiert muss der Inhalt des Kombinationsfeldes gesplittet werden.
Um zu wissen, an welcher Stelle die Aufteilung vorgenommen werden soll, ist der Feldtrenner vonBedeutung, der der Funktion als Variable mitgegeben wird.
a_stTeile = Split(stInhalt, Feldtrenner, 2)

Der letzte Parameter weist darauf hin, dass maximal 2 Teile erzeugt werden.
Abhängig davon, welcher Eintrag mit dem Feld 1 und welcher mit dem Feld 2 zusammenhängt,wird jetzt der Inhalt des Kombinationsfeldes den einzelnen Variablen zugewiesen. «Position = 2»
wird hier als Zeichen dafür genommen, dass an zweiter Position der Inhalt für das Feld 2 steht.
Bedienbarkeit verbessern

42

IF Position = 2 THEN
stInhalt = Trim(a_stTeile(0))
IF UBound(a_stTeile()) > 0 THEN
stInhaltFeld2 = Trim(a_stTeile(1))
ELSE
stInhaltFeld2 = ""END IF
stInhaltFeld2 = Trim(a_stTeile(1))
ELSE
stInhaltFeld2 = Trim(a_stTeile(0))
IF UBound(a_stTeile()) > 0 THEN
stInhalt = Trim(a_stTeile(1))
ELSE
stInhalt = ""END IF
stInhalt = Trim(a_stTeile(1))
END IF
END IFEs kann passieren, dass bei zwei voneinander zu trennenden Inhalten die Größeneinstellung desKombinationsfeldes (Textlänge) nicht zu einem der abzuspeichernden Tabellenfelder passt. Bei
Kombinationsfeldern für nur ein Feld wird dies in der Regel durch Einstellungen im Formularkontrollfeld erledigt. Hier muss hingegen ein eventueller Fehler abgefangen werden. Es wird darauf
hingewiesen, wie lang der Inhalt für das jeweilige Feld sein darf.
IF (LaengeFeld1 > 0 AND Len(stInhalt) > LaengeFeld1) OR (LaengeFeld2 > 0
AND Len(stInhaltFeld2) > LaengeFeld2) THEN

Wenn die Feldlänge des 1. oder 2. Teiles zu groß ist, wird erst einmal ein Standardtext in je einer
Variablen abgespeichert. 'CHR(13)' fügt hier einen Zeilenumbruch hinzu.
stmsgbox1 = "Das Feld " + NameTabellenFeld1 + " darf höchstens " +LaengeFeld1 + "Zeichen lang sein." + CHR(13)stmsgbox2 = "Das Feld " + NameTabellenFeld2 + " darf höchstens " +LaengeFeld2 + "Zeichen lang sein." + CHR(13)Sind beide Feldinhalte zu lang, so wird der Text mit beiden Feldinhalten ausgegeben.
IF (LaengeFeld1 > 0 AND Len(stInhalt) > LaengeFeld1) AND (LaengeFeld2> 0 AND Len(stInhaltFeld2) > LaengeFeld2) THEN
msgbox ("Der eingegebene Text ist zu lang." + CHR(13) + stmsgbox1+ stmsgbox2 + "Bitte den Text kürzen.",64,"Fehlerhafte Eingabe")

Die Anzeige erfolgt mit der Funktion 'msgbox()'. Sie erwartet zuerst einen Text, dann optional einen
Zahlenwert (der zu einer entsprechenden Darstellungsform gehört) und schließlich optional einen
Text als Überschrift über dem Fenster. Das Fenster hat hier also die Überschrift "Fehlerhafte Eingabe", die '64' fügt das Informationssymbol hinzu.
Im Folgenden werden alle auftretenden weiteren Fälle zu großer Textlänge abgearbeitet.
ELSEIF (LaengeFeld1 > 0 AND Len(stInhalt) > LaengeFeld1) THEN
msgbox ("Der eingegebene Text ist zu lang." + CHR(13) + stmsgbox1+ "Bitte den Text kürzen.",64,"Fehlerhafte Eingabe")
ELSE
msgbox ("Der eingegebene Text ist zu lang." + CHR(13) + stmsgbox2+ "Bitte den Text kürzen.",64,"Fehlerhafte Eingabe")
END IF
ELSE

Liegt kein zu langer Text vor, so kann die Funktion weiter durchlaufen. Ansonsten endet sie hier.
Jetzt werden die Inhaltseingaben so maskiert, dass eventuell vorhandene Hochkommata keine
Fehlermeldung erzeugen.
stInhalt = String_to_SQL(stInhalt)
IF stInhaltFeld2 <> "" THEN
stInhaltFeld2 = String_to_SQL(stInhaltFeld2)
END IF

Zuerst werden Variablen vorbelegt, die anschließend per Abfrage geändert werden können. Die
Variablen 'inID1' und 'inID2' sollen den Inhalt der Primärschlüsselfelder der beiden Tabellen
Bedienbarkeit verbessern

43speichern. Da bei einer Abfrage, die kein Ergebnis wiedergibt, durch Basic einer Integer-Variablen
0 zugewiesen wird, dies aber für das Abfrageergebnis auch bedeuten könnte, dass der ermittelte
Primärschlüssel eben den Wert 0 hat, wird die Variable auf jeweils -1 voreingestellt. Diesen Wert
nimmt ein Autowert-Feld bei der HSQLDB nicht automatisch an.
Anschließend wird die Datenbankverbindung erzeugt, soweit sie nicht schon besteht.
inID1 = -1
inID2 = -1
oDatenquelle = ThisComponent.Parent.CurrentController
If NOT (oDatenquelle.isConnected()) ThenoDatenquelle.connect()
End IfoVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()
IF NameTabellenFeld2 <> "" AND NOT IsEmpty(stInhaltFeld2) AND
NameTabelle2 <> "" THEN

Wenn ein zweites Tabellenfeld existiert, muss zuerst die zweite Abhängigkeit geklärt werden.
Zuerst wird überprüft, ob für den zweiten Wert in der Tabelle 2 bereits ein Eintrag existiert. Existiert
dieser Eintrag nicht, so wird er eingefügt.
Beispiel: Die Tabelle 2 ist die Tabelle "Ort". In ihr werden also Orte abgespeichert. Ist z.B. ein Eintrag für den Ort 'Rheine' vorhanden, so wird der entsprechende Primärschlüsseleintrag
ausgelesen. Ist der Eintrag 'Rheine' nicht vorhanden, wird er eingefügt und anschließend
der beim Einfügen erzeugte Primärschlüsselwert festgestellt.
stSql = "SELECT ""ID"" FROM """ + NameTabelle2 + """ WHERE """ +NameTabellenFeld2 + """='" + stInhaltFeld2 + "'"oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID2 = oAbfrageergebnis.getInt(1)WEND
IF inID2 = -1 THEN
stSql = "INSERT INTO """ + NameTabelle2 + """ (""" +NameTabellenFeld2 + """) VALUES ('" + stInhaltFeld2 + "') "oSQL_Anweisung.executeUpdate(stSql)
stSql = "CALL IDENTITY()"

Ist der Inhalt in der entsprechenden Tabelle nicht vorhanden, so wird er eingefügt. Der dabei entstehende Primärschlüsselwert wird anschließend ausgelesen. Ist der Inhalt bereits vorhanden, so
wird der Primärschlüsselwert durch die vorangehende Abfrage ermittelt. Die Funktion geht hier vonautomatisch erzeugten Primärschlüsselfeldern (IDENTITY) aus.
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID2 = oAbfrageergebnis.getInt(1)WEND
END IF

Der Primärschlüssel aus dem zweiten Wert wird in der Variablen 'inID2' zwischengespeichert.
Jetzt wird überprüft, ob eventuell dieser Schlüsselwert bereits in der Tabelle 1 zusammen mit dem
Eintrag aus dem ersten Feld vorhanden ist. Ist diese Kombination nicht vorhanden, so wird sie neu
eingefügt.
Beispiel: Für den Ort 'Rheine' aus der Tabelle 2 können in der Tabelle 1 mehrere Postleitzahlen
verfügbar sein. Ist die Kombination '48431' und 'Rheine' vorhanden, so wird nur der Primärschlüssel aus der Tabelle 1 ausgelesen, in der die Postleitzahlen und der Fremdschlüssel aus der Tabelle
2 gespeichert wurden.
stSql = "SELECT ""ID"" FROM """ + NameTabelle1 + """ WHERE """ +NameTab12ID + """='" + inID2 + "' AND """ + NameTabellenFeld1 + """ = '" + stInhalt +"'"oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND

Bedienbarkeit verbessern

44War der Inhalt der ersten Tabelle noch nicht vorhanden war, so wird der Inhalt neu abgespeichert(INSERT).
Beispiel: Existiert bereits die Postleitzahl '48429' in Kombination mit dem Fremdschlüssel aus derTabelle 2 "Ort", so wird auf jeden Fall ein neuer Datensatz erzeugt, wenn jetzt die Postleitzahl'48431' auftaucht. Der vorhergehenden Datensatz wird also nicht auf die neue Postleitzahl geändert. Schließlich sind durch die n:1-Verknüpfung der Tabellen mehrere Postleitzahlen für einen Ortermöglicht worden.
IF inID1 = -1 THEN
stSql = "INSERT INTO """ + NameTabelle1 + """ (""" +NameTabellenFeld1 + """,""" + NameTab12ID + """) VALUES ('" + stInhalt + "','" +inID2 + "') "oSQL_Anweisung.executeUpdate(stSql)

Der Primärschlüssel der ersten Tabelle muss schließlich wieder ausgelesen werden, damit er in die
dem Formular zugrundeliegende Tabelle übertragen werden kann.
stSql = "CALL IDENTITY()"
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND
END IF
END IF

Für den Fall, dass beide in dem Kombinationsfeld zugrundeliegenden Felder in einer Tabelle
gespeichert sind ( z.B. Nachname, Vorname in der Tabelle Name) muss eine andere Abfrage erfolgen:
IF NameTabellenFeld2 <> "" AND NameTabelle2 = "" THEN
stSql = "SELECT ""ID"" FROM """ + NameTabelle1 + """ WHERE """ +NameTabellenFeld1 + """='" + stInhalt + "' AND """ + NameTabellenFeld2 + """='" +stInhaltFeld2 + "'"oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND
IF inID1 = -1 THEN... und eine zweite Tabelle nicht existiert:
stSql = "INSERT INTO """ + NameTabelle1 + """ (""" +NameTabellenFeld1 + """,""" + NameTabellenFeld2 + """) VALUES ('" + stInhalt + "','"+ stInhaltFeld2 + "') "oSQL_Anweisung.executeUpdate(stSql)

Anschließend wird das Primärschlüsselfeld wieder ausgelesen.
stSql = "CALL IDENTITY()"
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND
END IF
END IFIF NameTabellenFeld2 = "" THEN

Jetzt wird der Fall geklärt, der der einfachste ist: Das 2. Tabellenfeld existiert nicht und der Eintrag
ist noch nicht in der Tabelle vorhanden. In das Kombinationsfeld ist also ein einzelner neuer Wert
eingetragen worden.
stSql = "SELECT ""ID"" FROM """ + NameTabelle1 + """ WHERE """ +NameTabellenFeld1 + """='" + stInhalt + "'"oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND
IF inID1 = -1 THEN

Wenn ein zweites Tabellenfeld nicht existiert wird der Inhalt neu eingefügt ...
Bedienbarkeit verbessern

45

stSql = "INSERT INTO """ + NameTabelle1 + """ (""" +NameTabellenFeld1 + """) VALUES ('" + stInhalt + "') "oSQL_Anweisung.executeUpdate(stSql)… und die entsprechende ID direkt wieder ausgelesen.
stSql = "CALL IDENTITY()"
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
inID1 = oAbfrageergebnis.getInt(1)WEND
END IF
END IF

Der Wert des Primärschlüsselfeldes muss ermittelt werden, damit er in die Haupttabelle des Formulars übertragen werden kann.
Anschließend wird der aus all diesen Schleifen ermittelte Primärschlüsselwert in das unsichtbare
Feld der Haupttabelle und die darunterliegende Datenbank übertragen. Mit 'BoundField' wird
das mit dem Formularfeld verbundene Tabellenfeld erreicht. Mit 'updateInt' wird eine IntegerZahl (siehe Zahlendefinition) diesem Feld zugewiesen.
oFeld.BoundField.updateInt(inID)
END IF
ELSE

Ist kein Primärschlüsselwert einzutragen, weil auch kein Eintrag in dem Kombinationsfeld erfolgte
oder dieser Eintrag gelöscht wurde, so ist auch der Inhalt des unsichtbaren Feldes zu löschen. Mit'updateNull()' wird das Feld mit dem datenbankspezifischen Ausdruck für ein leeres Feld,'NULL', versehen.
oFeld.BoundField.updateNull()END IF
NEXT inCom
END IF
END SUB

Kontrollfunktion für die Zeichenlänge der Kombinationsfelder
Die folgende Funktion soll die Zeichenlänge der jeweiligen Tabellenspalten ermitteln, damit zu
lange Eingaben nicht einfach gekürzt werden. Der Typ 'FUNCTION' wurde hier wegen der Rückgabewerte gewählt.
FUNCTION Spaltengroesse(Tabellenname AS STRING, Feldname AS STRING) AS INTEGER
oDatenquelle = ThisComponent.Parent.CurrentController
If NOT (oDatenquelle.isConnected()) ThenoDatenquelle.connect()
End IfoVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()
stSql = "SELECT ""COLUMN_SIZE"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_COLUMNS""WHERE ""TABLE_NAME"" = '" + Tabellenname + "' AND ""COLUMN_NAME"" = '" + Feldname +"'"oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
i = oAbfrageergebnis.getInt(1)WEND
Spaltengroesse = i
END FUNCTION

Datensatzaktion erzeugen
SUB Datensatzaktion_erzeugen(oEvent AS OBJECT)

Dieses Makro sollte an das folgende Ereignis des Listenfeldes gebunden werden:'Bei Fokuserhalt'. Es ist notwendig, damit auf jeden Fall bei einer Änderung des Listenfeldinhaltes
die Speicherung abläuft. Ohne dieses Makro wird keine Änderung in der Tabelle erzeugt, die fürBase wahrnehmbar ist, da die Combobox mit dem Formular nicht verbunden ist.
Bedienbarkeit verbessern

46Dieses Makro stellt direkt die Eigenschaft des Formulars um.
DIM oForm AS OBJECT
oForm = oEvent.Source.Model.Parent
oForm.IsModified = TRUEEND SUB

Bei Formularen, die bereits ihren Inhalt auch für die Kombinationsfelder aus Abfragen erhalten, istdieses Makro nicht notwendig. Änderungen in den Kombinationsfeldern werden direkt registriert.

Navigation von einem Formular zum anderen
Ein Formular soll über ein entsprechendes Ereignis geöffnet werden.
Im Formularkontrollfeld wird unter den Eigenschaften in der Zeile "Zusatzinformationen" (Tag) hierder Name des Formulars eintragen. Hier können auch weitere Informationen eingetragen werden,die über den Befehl Split() anschließend voneinander getrennt werden.
SUB Zu_Formular_von_Formular(oEvent AS OBJECT)
DIM stTag AS StringstTag = oEvent.Source.Model.Tag
aForm() = Split(stTag, ",")

Das Array wird gegründet und mit den Formularnamen gefüllt, in diesem Fall zuerst in dem zu öffnenden Formular und als zweites dem aktuellen Formular, dass nach dem Öffnen des anderengeschlossen werden soll.
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForm(0)) ).open
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForm(1)) ).close
END SUB

Soll stattdessen nur beim Schließen ein anderes Formular geöffnet werden, weil z.B. ein Hauptformular existiert und alle anderen Formulare von diesem aus über entsprechende Buttons angesteuert werden, so ist das folgende Makro einfach an das Formular unter Extras → Anpassen →Ereignisse → Dokument wird geschlossen anzubinden:
SUB Hauptformular_oeffnen
ThisDatabaseDocument.FormDocuments.getByName( "Hauptformular" ).open
END SUB

Wenn die Formulardokumente innerhalb der *.odb-Datei in Verzeichnissen sortiert sind, so mussdas Makro für den Formularwechsel etwas umfangreicher sein:
SUB Zu_Formular_von_Formular_mit_Ordner(oEvent AS OBJECT)
REM Das zu öffenende Formular wird als erstes angegeben.
REM Liegt ein Formular in einem Ordner, so ist die Beziehung über "/" zudefinieren,
REM so dass der Unterordner zu finden ist.
DIM stTag AS STRING
stTag = oEvent.Source.Model.Tag 'Tag wird unter den Zusatzinformationen eingegeben
aForms() = Split(stTag, ",") 'Hier steht zuerst der Formularname für das neue
Formular, dann der für das alte FormularaForms1() = Split(aForms(0),"/")
aForms2() = Split(aForms(1),"/")IF UBound(aForms1()) = 0 THEN
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms1(0)) ).open
ELSE
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms1(0)) ).getByName(
Trim(aForms1(1)) ).open
END IFIF UBound(aForms2()) = 0 THEN
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms2(0)) ).close
ELSE
ThisDatabaseDocument.FormDocuments.getByName( Trim(aForms2(0)) ).getByName(
Trim(aForms2(1)) ).close
END IF
END SUB

Bedienbarkeit verbessern

47Formularedokumente, die in einem Verzeichnis liegen, werden in den Zusatzinformationen als Verzeichnis/Formular angegeben. Dies muss umgewandelt werden zu...getByName("Verzeichnis").getByName("Formular").

Hierarchische Listenfelder
Einstellungen in einem Listenfeld sollen die Einstellungen in einem zweiten Listenfeld direkt beeinflussen. Auf einfachere Art und Weise wurde dies schon bei der Filterung von Datensätzen weiteroben beschrieben. Jetzt soll aber hinzu kommen, dass das erste Listenfeld den Inhalt des zweiten
Listenfeldes beeinflusst, der wiederum den Inhalt des dritten Listenfeldes beeinflusst usw.
Jahrgang

KlasseName

1aKarl Müller

2b

Evelyn Maier

3cMaria Gott4d

Eduard Abgefahren

5eKurt Drechsler

6f

Kunigunde Schimmel

7

g

8
9
10
11
12
13
Abbildung 1: Beispielhafte Listenfelder für eine hierarchische
Anordnung von Listenfeldern.
In diesem Beispiel enthält Listenfeld 1 alle Jahrgänge der Schule. Die Klassen der jeweiligen Jahrgänge sind durch Buchstaben kenntlich gemacht. Die Namen enthalten die Schülerinnen undSchüler der Klasse.
Unter normalen Umständen zeigt das Listenfeld für den Jahrgang alle 13 Jahrgänge an. Das Listenfeld für die Klasse alle Buchstaben und das Listenfeld für die Schüler und Schülerinnen alleSchüler und Schülerinnen der Schule.
Wird mit hierarchischen Listenfeldern gearbeitet, so wird nach Auswahl des Jahrganges das Listenfeld für die Klasse eingegrenzt. Es werden nur noch die Klassenbezeichnungen angezeigt, die
es in dem Jahrgang tatsächlich gibt. So könnte eben bei steigender Schüler- und Schülerinnenzahl
die Anzahl der Klassen im Jahrgang ebenfalls steigen. Das letzte Listenfeld, die Namen, ist jetzt
bereits stark eingegrenzt. Statt alle vermutlich deutlich über 1000 Schüler und Schülerinnen anzuzeigen listet das letzte Feld nur noch ca. 30 Schüler und Schülerinnen auf.
Zum Beginn steht nur die Auswahl des Jahrganges zur Verfügung. Ist ein Jahrgang ausgewählt, so
steht die (bereits eingeschränkte) Auswahl der Klasse zur Verfügung. Erst zum Schluss wird
schließlich das Listenfeld für die Namen freigegeben.

Bedienbarkeit verbessern

48

Wird das Listenfeld des Jahrganges geändert, so muss der Durchlauf wieder wie vorher starten.
Wird nur das Listenfeld der Klasse geändert, so muss der Wert des Jahrganges für das letzte Listenfeld der Namen weiter gelten.
Um solch eine Funktion bereit zu stellen muss innerhalb eines Formulars eine Variable zwischengespeichert werden. Dies erfolgt in einem versteckten Kontrollfeld.
Der Makrostart wird an die Veränderung des Inhaltes eines Listenfeldes gekoppelt: Eigenschaft
Listenfeld → Ereignisse → Modifiziert. In den Zusatzinformationen des Listenfeldes werden die
notwendige Variablen gespeichert.
Hier der Beispielhafte Inhalt der Zusatzinformationen:
MainForm,Jahrgang,verstecktes Kontrollfeld,Listenfeld 2
Das Formular hat die Bezeichnung «MainForm». Das aktuelle Listenfeld ist als «Listenfeld 1»
bezeichnet. Dieses Listenfeld stellt den Inhalt des Tabellenfeldes «Jahrgang» dar. Nach diesem
Eintrag muss also das darauffolgende Listenfeld gefiltert werden. Das versteckte Kontrollfeld ist indiesem Fall auch gleich mit dem entsprechenden Namen gekennzeichnet. Und schließlich wird
noch darauf hingewiesen, dass ein 2. Listenfeld, «Listenfeld 2», existiert, an das die Filterung weiter gegeben wird.
SUB Hierarchisches_Kontrollfeld(oEvent AS OBJECT)
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECTDIM oForm AS OBJECTDIM oFeldHidden AS OBJECTDIM oFeld AS OBJECTDIM oFeld1 AS OBJECT
DIM stSql AS STRING
DIM aInhalt()
DIM stTag AS STRING
stTag = oEvent.Source.Model.Tag
REM Tag wird unter den Zusatzinformationen eingegeben
REM Hier steht:
REM 0. Formularname für das zu filternde Formular,
REM 1. Feldname des zu filterndes Feldes in der Tabelle,
REM 2. Feldname des versteckten Konrollfeldes, das den Filterwert speichern soll,
REM 3. eventuell weiteres Listfeld
REM Der Tag wird von dem auslösenden Element ausgelesen. Die Variable wird an die
Prozedur weitergegeben, die gegebenenfalls alle weiteren Listenfelder
einstellt.
aFilter() = Split(stTag, ",")
stFilter = ""Nachdem die Variablen deklariert wurden wird der Inhalt des Tags in ein Array übertragen. So kannauf die einzelnen Elemente zugegriffen werden. Anschließend wird der Zugang zu den verschiedenen Feldern im Formular deklariert.
oDoc = thisComponent
oDrawpage = oDoc.drawpage
oForm = oDrawpage.forms.getByName(Trim(aFilter(0)))
oFeld = oForm.getByName(oEvent.Source.Model.Name)

Das Listenfeld wird aus dem Aufruf heraus ermittelt. Aus dem Listenfeld wird der Wert ausgelesen.
Nur wenn dieser Wert einen Inhalt hat wird er mit dem Feldnamen des zu filternden Feldes, in
unserem Beispiel «Jahrgang», zu einer SQL-Bedingung kombiniert. Ansonsten bleibt der Filter
leer. Sind die Listenfelder zur Filterung eines Formulars gedacht, dann ist kein verstecktes Kontrollfeld vorhanden. Unter dieser Bedingung wird der Filterwert direkt im Formular gespeichert.
IF Trim(aFilter(2)) = "" THEN
IF oFeld.getCurrentValue <> "" THEN
stFilter = """"+Trim(aFilter(1))+"""='"+oFeld.getCurrentValue()+"'"

Existiert bereits vorher ein Filter (weil es sich z.B. um das Listenfeld 2 handelt, das jetzt betätigt
wurde), so wird der neue Inhalt an den vorherigen angehängt, der in dem versteckten Feld zwischengespeichert wurde.

Bedienbarkeit verbessern

49

IF oForm.Filter <> ""Dies darf allerdings nur dann geschehen, wenn das gleiche Feld noch nicht gefiltert wurde.
Schließlich ist z.B. bei einer Filterung nach dem «Jahrgang» kein Datensatz unter «Name» mehrzu erwarten, wenn zusätzlich eine weitere Filterung nach «Jahrgang» erfolgt. Eine Person kannimmer nur in einem «Jahrgang» existieren. Es muss also ausgeschlossen werden, dass in der Filterung der Filtername bereits vorkommt.
AND InStr(oForm.Filter, """"+Trim(aFilter(1))+"""='") = 0 THEN
stFilter = oForm.Filter + " AND " + stFilter

Existiert bereits ein Filter und kommt das Feld, nach dem gefiltert werden soll, bereits im Filter vor,
so muss die vorherige Filterung ab diesem Feldnamen gelöscht und die neue Filterung eingefügt
werden.
ELSEIF oForm.Filter <> "" THEN
stFilter = Left(oForm.Filter,
InStr(oForm.Filter, """"+Trim(aFilter(1))+"""='")-1) + stFilter
END IF
END IF

Anschließend wird der Filter in das Formular eingetragen. Dieser Filter kann auch leer sein, wenndirekt das erste Listenfeld ohne Inhalt gewählt wurde.
oForm.Filter = stFilter
oForm.reload()

Die gleiche Prozedur wird durchlaufen, wenn nicht ein Formular direkt gefiltert werden soll. In dem
Fall wird der Filterwert in einem versteckten Kontrollfeld zwischengespeichert.
ELSE
oFeldHidden = oForm.getByName(Trim(aFilter(2)))
IF oFeld.getCurrentValue <>"" THEN
stFilter = """"+Trim(aFilter(1))+"""='"+oFeld.getCurrentValue()+"'"
IF oFeldHidden.HiddenValue <> ""AND InStr(oFeldHidden.HiddenValue, """"+Trim(aFilter(1))+"""='") = 0 THEN
stFilter = oFeldHidden.HiddenValue + " AND " + stFilter
ELSEIF oFeldHidden.HiddenValue <> "" THEN
stFilter = Left(oFeldHidden.HiddenValue,
InStr(oFeldHidden.HiddenValue, """"+Trim(aFilter(1))+"""='")-1) +stFilterEND IF
END IFoFeldHidden.HiddenValue = stFilter
END IF

Ist in den Zusatzinformationen ein 4. Eintrag (Arraynummerierung beginnt bei 0!) vorhanden, so
muss das folgende Listenfeld jetzt auf den entsprechenden Eintrag des aufrufenden Listenfeldes
eingestellt werden.
IF UBound(aFilter()) > 2 THEN
oFeld1 = oForm.getByName(Trim(aFilter(3)))
aFilter1() = Split(oFeld1.Tag,",")

Die notwendigen Daten für die Filterung werden aus den Zusatzinformationen («Tag») des entsprechenden Listenfeldes ausgelesen. Leider ist es nicht möglich, lediglich den SQL-Code in dem
Listenfeld neu zu schreiben und anschließend das Listenfeld ein zu lesen. Vielmehr müssen die
entsprechenden Werte direkt in das Listenfeld geschrieben werden.
Bei der Erstellung des Codes wird davon ausgegangen, dass die Tabelle, auf der das Formularberuht, die gleiche ist, auf der auch die Listenfelder beruhen. Für eine Weitergabe von Fremdschlüsseln an die Tabelle ist so ein Listenfeld also erst einmal nicht gedacht.
IF oFeld.getCurrentValue <> "" THEN
stSql = "SELECT DISTINCT """+Trim(aFilter1(1))+""" FROM """+oForm.Command+""" WHERE "+stFilter+" ORDER BY """+Trim(aFilter1(1))+""""
oDatenquelle = ThisComponent.Parent.CurrentController
If NOT (oDatenquelle.isConnected()) THEN
oDatenquelle.connect()
END IF

Bedienbarkeit verbessern

50oVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)

Die Werte werden in ein Array eingelesen. Das Array wird anschließend direkt in das Listenfeld
übertragen. Die entsprechenden Zähler für das Array werden durch die Schleife kontinuierlich
erhöht.
inZaehler = 0
WHILE oAbfrageergebnis.next
ReDim Preserve aInhalt(inZaehler)aInhalt(inZaehler) = oAbfrageergebnis.getString(1)inZaehler = inZaehler+1
WEND
ELSE
aInhalt(0) = ""END IFoFeld1.StringItemList = aInhalt()

Der Inhalte des Listenfeldes wurde neu erstellt. Das Listenfeld muss neu eingelesen werden.
Anschließend wird anhand der Zusatzinformationen des neu eingestellten Listenfeldes jedes eventuell weiter folgende Listenfeld entsprechend geleert, indem eine Schleife für alle folgenden Listenfelder gestartet wird, bis eben ein letztes Listenfeld kein 4. Eintrag in den Zusatzinformationen enthält.
oFeld1.refresh()
WHILE UBound(aFilter1()) > 2
DIM aLeer()oFeld2 = oForm.getByName(Trim(aFilter1(3)))
DIM aFilter1()
aFilter1() = Split(oFeld2.Tag,",")
oFeld2.StringItemList = aLeer()oFeld2.refresh()WEND
END IF
END SUB
Die sichtbaren Inhalte des Listenfeldes werden in oFeld1.StringItemList gespeichert. Soll zusätz-

lich auch ein Wert gespeichert werden, der als Fremdschlüssel an die darunterliegende Tabelle
weitergegeben wird, wie das bei Listenfeldern in Formularen üblich, so ist dieser Wert in derAbfrage zusätzlich zu ermitteln und anschließend mit oFeld1.ValueItemList abzuspeichern.
Für so eine Erweiterung sind allerdings zusätzliche Variablen notwendig wie z.B. neben derTabelle, in der die Werte des Formulars gespeichert werden, noch die Tabelle, aus der die Listenfeldinhalte gelesen werden.
Besondere Aufmerksamkeit ist dabei der Formulierung des Filters zu widmen.
stFilter = """"+Trim(aFilter(2))+"""='"+oFeld.getCurrentValue()+"'"

funktioniert dann nur noch, wenn es sich bei der zugrundeliegenden LO-Version um eine Version
ab LO 4.1 handelt, da hier als CurrentValue() der Wert wiedergegeben wird, der auch abgespeichert wird – nicht der Wert, der lediglich angezeigt wird.

Zeiteingaben mit Millisekunden
Um Zeiten im Millisekunden-Bereich zu speichern ist in der Tabelle ein Timestamp-Feld erforderlich, das zudem per SQL separat darauf eingestellt wird (siehe «Erstellung von Tabellen»). Ein solches Feld kann vom Formular aus mit einem formatierten Feld beschrieben werden, das auch das
Format MM:SS,00 anbietet. Allerdings scheitert der erste Schreibversuch daran, dass der Eingabe
der entsprechende Datumszusatz fehlt. Dies kann mit dem folgenden Makro erreicht werden, dasan die Eigenschaft «Vor der Datensatzaktion» des Formulars gebunden wird:
SUB Timestamp
DIM unoStmp AS NEW com.sun.star.util.DateTime
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECT

Bedienbarkeit verbessern

51

DIM oForm AS OBJECTDIM oFeld AS OBJECT
DIM stZeit AS STRING
DIM ar()
DIM arMandS()
DIM loNano AS LONG
DIM inSecond AS INTEGER
DIM inMinute AS INTEGER
oDoc = thisComponent
oDrawpage = oDoc.Drawpage
oForm = oDrawpage.Forms.getByName("MainForm")oFeld = oForm.getByName("Zeit")
stZeit = oFeld.Text

Die Variablen werden vorher deklariert. Nur wenn das Feld «Zeit» einen Inhalt hat wird der weitere
Code ausgeführt. Sonst tritt der Mechanismus des Formulars in Kraft, der das Feld auf NULL setzt.
IF stZeit <> "" THEN
ar() = Split(stZeit,",")
loNano = CLng(ar(1)&"0000000")
arMandS() = Split(ar(0),":")
inSecond = CInt(arMandS(1))
inMinute = Cint(arMandS(0))

Die Einträge aus dem Feld «Zeit» werden in ihre Bestandteile zerlegt.
Zuerst werden die Hundertstelsekunden abgetrennt und mit so vielen Nullen rechts aufgefüllt, dasssich insgesamt eine neunstellige Zahl ergibt. Eine so hohe Zahl kann nur in einer Lond-Variablen
gespeichert werden.
Anschließend werden aus dem verbleibenden Rest durch eine Trennung am Trennzeichen «:» die
Minuten von den Sekunden getrennt und in Integer-Zahlen umgewandelt.
WITH unoStmp.NanoSeconds = loNano.Seconds = inSecond.Minutes = inMinute.Hours = 0.Day = 30.Month = 12
.Year = 1899
END WITH

Dem Zeitstempel wird nun das Standarddatum 30.12.1899 zugewiesen, das dem Standard-Startdatum von LibreOffice entspricht. Hier kann natürlich auch das aktuelle Datum mitgespeichert werden.
Aktuelles Datum ermitteln und speichern:

HinweisDIM Jetzt AS DATE
Jetzt = Now()
WITH unoStmp.NanoSeconds = loNano.Seconds = inSecond.Minutes = inMinute.Hours = Hour(Jetzt).Day = Day(Jetzt).Month = Month(Jetzt).Year = Year(Jetzt)
END WITH

oFeld.BoundField.updateTimestamp(unoStmp)
END IF
END SUB

Anschließend wird der erzeugte Zeitstempel über updateTimestamp in das Feld übertragen und
mit dem Formular abgespeichert.

Bedienbarkeit verbessern52In älteren Anleitungen wird hier statt NanoSeconds der Begriff HundrethSeconds verwendet.
Dieser entspricht aber nicht der API von LibreOffice und erzeugt deshalb nur Fehlermeldungen.

Datenbankaufgaben mit Makros erweitert
Verbindung mit Datenbanken erzeugen
oDatenquelle = ThisComponent.Parent.DataSource
IF NOT oDatenquelle.IsPasswordRequired THEN
oVerbindung = oDatenquelle.GetConnection("","")

Hier wäre es möglich, fest einen Benutzernamen und ein Passwort einzugeben, wenn eine Passworteingabe erforderlich wäre. In den Klammer steht dann ("Benutzername","Passwort"). Statteinen Benutzernamen und ein Passwort in Reinschrift einzutragen, wird für diesen Fall der Dialog
für den Passwortschutz aufgerufen:
ELSE
oAuthentifizierung = createUnoService("com.sun.star.sdb.InteractionHandler")oVerbindung = oDatenquelle.ConnectWithCompletion(oAuthentifizierung)
END IF

Wird allerdings von einem Formular innerhalb der Base-Datei auf die Datenbank zugegriffen, so
reicht bereits
oDatenquelle = ThisComponent.Parent.CurrentController
IF NOT (oDatenquelle.isConnected()) ThenoDatenquelle.connect()
End IFoVerbindung = oDatenquelle.ActiveConnection()

Die Datenbank ist hier bekannt, ein Nutzername und ein Passwort sind nicht erforderlich, da diesebereits in den Grundeinstellungen der HSQLDB für die interne Version ausgeschaltet sind.
Für Formulare außerhalb von Base wird die Verbindung über das erste Formular hergestellt:
oDatenquelle = Thiscomponent.Drawpage.Forms(0)oVerbindung = oDatenquelle.activeConnection

Datenbanksicherungen erstellen
Vor allem beim Erstellen von Datenbanken kann es hin und wieder vorkommen, dass die *.odbDatei unvermittelt beendet wird. Vor allem beim Berichtsmodul ist ein häufigeres Abspeichern nachdem Editieren sinnvoll.
Ist die Datenbank erst einmal im Betrieb, so kann sie durch Betriebssystemabstürze beschädigt
werden, wenn der Absturz gerade während der Beendigung der Base-Datei erfolgt. Schließlich
wird in diesem Moment der Inhalt der Datenbank in die Datei zurückgeschrieben.
Außerdem gibt es die üblichen Verdächtigen für plötzlich nicht mehr zu öffnende Dateien wie Festplattenfehler usw. Da kann es dann nicht schaden, eine Sicherheitskopie möglichst mit dem aktuellsten Datenstand parat zu haben. Der Datenbestand ändert sich allerdings nicht, während die*.odb-Datei geöffnet ist. Deshalb kann die Sicherung direkt mit dem Öffnen der Datei verbunden
werden. Es werden einfach Kopien der Datei in das unter Extras → Optionen → LibreOffice →Pfade angegebene Backup-Verzeichnis erstellt. Dabei beginnt das Makro nach 5 Kopien damit, diejeweils älteste Variante zu überschreiben.
SUB Datenbankbackup
DIM oPath AS OBJECTDIM oDoc AS OBJECT
DIM sTitel AS STRING
DIM sUrl_Ziel AS STRING
DIM sUrl_Start AS STRING
DIM i AS INTEGER

Datenbankaufgaben mit Makros erweitert

53

DIM k AS INTEGER
oDoc = ThisComponentsTitel = oDoc.Title
sUrl_Start = oDoc.URL
oPath = createUnoService("com.sun.star.util.PathSettings")
FOR i = 1 TO 6
IF NOT FileExists(oPath.Backup & "/" & i & "_" & sTitel) THEN
IF i > 5 THEN
FOR k = 4 TO 1 STEP -1
IF FileDateTime(oPath.Backup & "/" & k & "_" & sTitel) <=FileDateTime(oPath.Backup & "/" & k+1 & "_" & sTitel) THEN
IF k = 1 THEN
i=k
EXIT FOR
END IF
ELSE
i=k+1
EXIT FOR
END IFNEXT
END IF
EXIT FOR
END IFNEXT
sUrl_Ziel = oPath.Backup & "/" & i &"_" & sTitel
FileCopy(sUrl_Start,sUrl_Ziel)
END SUB

Werden vor der Ausführung der Prozedur 'Datenbankbackup' während der Nutzung von Base die
Daten aus dem Cache in die Datei zurückgeschrieben, so kann ein entsprechendes Backup auchz.B. nach einer bestimmten Nutzerzeit oder durch Betätigung eines Buttons sinnvoll sein. Das
Zurückschreiben regelt die folgende Prozedur:
SUB Daten_aus_Cache_schreiben
DIM oDaten AS OBJECTDIM oDataSource AS OBJECT
oDaten = ThisDatabaseDocument.CurrentController
IF NOT ( oDaten.isConnected() ) THEN oDaten.connect()oDataSource = oDaten.DataSource
oDataSource.flush
END SUB

Datenbanken komprimieren
Eigentlich nur ein SQL-Befehl ('SHUTDOWN COMPACT'), der hin- und wieder, vor allem nach der
Löschung von vielen Daten, durchgeführt werden sollte. Die Datenbank speichert neue Daten ab,hält aber gleichzeitig den Platz für die eventuell gelöschten Daten vor. Bei starker Änderung desDatenbestandes muss deshalb der Datenbestand wieder komprimiert werden.
Wird die Komprimierung durchgeführt, so kann anschließend nicht mehr auf die Tabellen zugegriffen werden. Die Datei muss neu geöffnet werden. Deshalb schließt das Makro auch das Formular,
aus dem heraus es aufgerufen wird. Leider lässt sich das Dokument selbst nicht schließen, ohne
dass beim Neuaufruf die Wiederherstellung anspringt. Deshalb ist diese Funktion auskommentiert.
SUB Datenbank_komprimieren
DIM stMessage AS STRING
oDatenquelle = ThisComponent.Parent.CurrentController ' Zugriffsmöglichkeit aus dem
Formular heraus
IF NOT (oDatenquelle.isConnected()) THEN
oDatenquelle.connect()
END IFoVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()
stSql = "SHUTDOWN COMPACT" ' Die Datenbank wird komprimiert und geschlossen

Datenbankaufgaben mit Makros erweitert

54

oSQL_Anweisung.executeQuery(stSql)
stMessage = "Die Datenbank wurde komprimiert." + CHR(13) + "Das Formular wird
jetzt geschlossen."stMessage = stMessage + CHR(13) + "Anschließend sollte die Datenbankdatei
geschlossen werden."stMessage = stMessage + CHR(13) + "Auf die Datenbank kann erst nach dem erneutenÖffnen der Datenbankdatei zugegriffen werden."
msgbox stMessage
ThisDatabaseDocument.FormDocuments.getByName( "Wartung" ).close
REM Das Schließen der Datenbankdatei führt beim Neustart zu einem
Wiederherstellungsablauf.
' ThisDatabaseDocument.close(True)
END SUB

Tabellenindex heruntersetzen bei Autowert-Feldern
Werden viele Daten aus Tabellen gelöscht, so stören sich Nutzer häufig daran, dass die automatisch erstellten Primärschlüssel einfach weiter hochgezählt werden, statt direkt an den bisher
höchsten Schlüsselwert anzuschließen. Die folgende Prozedur liest für eine Tabelle den bisherigen
Höchstwert des Feldes "ID" aus und stellt den nächsten Schlüsselstartwert um 1 höher als das
Maximum ein.
Heißt das Primärschlüsselfeld nicht "ID", so müsste das Makro entsprechend angepasst werden.
SUB Tabellenindex_runter(stTabelle AS STRING)
REM Mit dieser Prozedur wird das automatisch hochgeschriebene Primärschlüsselfeld
mit der vorgegebenen Bezeichnung "ID" auf den niedrigst möglichen Wert eingestellt.
DIM inAnzahl AS INTEGER
DIM inSequence_Value AS INTEGER
oDatenquelle = ThisComponent.Parent.CurrentController ' Zugriffsmöglichkeit aus dem
Formular heraus
IF NOT (oDatenquelle.isConnected()) THEN
oDatenquelle.connect()
END IFoVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()
stSql = "SELECT MAX(""ID"") FROM """+stTabelle+"""" ' Der höchste in "ID"eingetragene Wert wird ermittelt
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql) ' Abfrage starten und den
Rückgabewert in einer Variablen oAbfrageergebnis speichern
WHILE oAbfrageergebnis.next
inAnzahl = oAbfrageergebnis.getInt(1)' Erstes Datenfeld wird ausgelesen
WEND' nächster Datensatz, in diesem Fall nicht mehr erforderlich, da nur ein
Datensatz existiert
IF inAnzahl = "" THEN ' Falls der höchste Wert gar kein Wert ist, also dieTabelle leer ist wird der höchste Wert als -1 angenommen
inAnzahl = -1
END IF
inSequence_Value = inAnzahl+1 ' Der höchste Wert wird um 1 erhöht
REM Ein neuer Befehl an die Datenbank wird vorbereitet. Die ID wird als neu
startend ab inAnzahl+1 deklariert.
REM Diese Anweisung hat keinen Rückgabewert, da ja kein Datensatz ausgelesen
werden mussoSQL_Anweisung1 = oVerbindung.createStatement()oSQL_Anweisung1.executeQuery("ALTER TABLE """ + stTabelle + """ ALTER COLUMN""ID"" RESTART WITH " + inSequence_Value + "")END SUB

Drucken aus Base heraus
Der Standard, um aus Base heraus ein druckbares Dokument zu erzeugen, ist die Nutzung eies
Berichtes. Daneben gibt es noch Möglichkeiten, Tabellen und Abfragen einfach nach Calc zu
kopieren und dort zum Druck aufzubereiten. Auch der direkte Druck eines Formularinhaltes vom
Bildschirm ist natürlich möglich.

Datenbankaufgaben mit Makros erweitert

55

Deutlich schneller und gut gestaltet geht der Druck aber über Makros mit Hilfe von Serienbrieffunktionen oder Textfeldern.

Serienbriefdruck aus Base heraus
Manchmal reicht einfach ein Bericht nicht aus, um sauber Briefe an die Adressaten zu erstellen.
Schon die Textfelder in einem Bericht sind hier in der Nutzung doch sehr eingeschränkt. Hierzu
wird dann ein Serienbrief im Writer erstellt. Es muss aber nicht sein, dass erst der Writer geöffnet
wird, dort dann über den Serienbriefdruck alle Auswahlmöglichkeiten und Eingaben gemacht werden und schließlich der Druck erfolgt. Dies alles geht auch per Makro direkt aus Base heraus.
SUB Serienbriefdruck
DIM oMailMerge AS OBJECT
DIM aProps()
oMailMerge = createunoservice("com.sun.star.text.MailMerge")

Als Name der Datenquelle wird der Name angegeben, unter dem die Datenbank in LO angemeldet
ist. Dieser Name muss nicht identisch mit dem Dateinamen sein. Der Anmeldename in diesem Beispiel lautet "Adressen"
oMailMerge.DataSourceName = "Adressen"

Die Pfadbeschreibung mit der Serienbriefdatei erfolgt in der Art der jeweiligen Betriebssystemumgebung, hier ab dem Wurzelpfad eines Linux-Systems.
oMailMerge.DocumentURL = ConvertToUrl("home/user/Dokumente/Serienbrief.odt")

Der Typ des Kommandos wird festgelegt. '0' steht für eine Tabelle, '1' für eine Abfrage und '2' fürein direktes SQL-Kommando.
oMailMerge.CommandType = 1

Hier wurde eine Abfrage gewählt, die den Namen "Serienbriefabfrage" trägt.
oMailMerge.Command = "Serienbriefabfrage"Über den Filter wird festgelegt, für welche Datensätze aus der Serienbriefabfrage ein Druck erfolgen soll. Dieser Filter könnte z.B. über ein Formularfeld aus Base heraus an das Makro weitergegeben werden. Mit dem Primärschlüssel eines Datensatzes könnte so der Ausdruck eines einzelnen Dokumentes erfolgen.
In diesem Beispiel wird aus der "Serienbriefabfrage" das Feld "Geschlecht" aufgesucht und dortnach Datensätzen gesucht, die in diesem Feld mit einem 'm' versehen sind.
oMailMerge.Filter = """Geschlecht""='m'"

Es gibt die Ausgabetypen Drucker (1), Datei (2) und Mail (3). Hier wurde zu Testzwecken die Ausgabe in eine Datei gewählt. Diese Datei wird in dem angegebenen Pfad abgespeichert. Für jeden
Serienbriefdatensatz wird ein Druck erzeugt. Damit dieser Druck unterscheidbar ist, wird das Feld
Nachname in den Dateinamen aufgenommen.
oMailMerge.OutputType = 2
oMailMerge.OutputUrl = ConvertToUrl("home/user/Dokumente")
oMailMerge.FileNameFromColumn = True
oMailMerge.Filenameprefix = "Nachname"oMailMerge.execute(aProps())
END SUB

Wird allein der Filter über ein Formular bestückt, so kann auf diese Art und Weise also ohne dieÖffnung des Writer-Dokumentes ein Serienbriefdruck erfolgen.

Drucken über TextfelderÜber Einfügen → Feldbefehl → Funktionen → Platzhalter wird im Writer eine Vorlage für das
zukünftig zu druckende Dokument erstellt. Die Platzhalter sollten dabei sinnvollerweise mit dem
Namen versehen werden, den die Felder auch in der Datenbank bzw. der Tabelle/Abfrage für das
Formular haben, aus dem heraus das Makro aufgerufen wird.

Datenbankaufgaben mit Makros erweitert

56

Für einfache Zwecke wird «Text» als Typ für den Platzhalter gewählt.
In dem Makro wird der Pfad zur Vorlage hinterlegt. Es wird ein neues Dokument «Unbenannt1.odt»
erstellt. Vom Makro werden die Platzhalter über die Abfrage des Inhaltes des aktuellen Datensatzes des Formulars befüllt. Das offene Dokument kann nun noch nach belieben verändert werden.
In der Beispieldatenbank «Beispiel_Datenbank_Serienbrief_direkt.odb» wird gezeigt, wie mit Hilfe
von Textfeldern und einem Zugriff auf eine in der Vorlage bereits vorgesehenen Tabelle eine komplette Rechnung erstellt werden kann. Im Gegensatz zum Report-Builder sind bei dieser Form derRechnungserstellung die entsprechenden Felder für den Tabelleninhalt nicht in der Höhe begrenzt.
Deshalb wird immer aller Text angezeigt.
Hier Teile des Codes, der im Wesentlichen diesem Beitrag von DPunch zu verdanken ist:
http://de.openoffice.info/viewtopic.php?f=8&t=45868#p194799
SUB Textfelder_FuellenoForm = thisComponent.Drawpage.Forms.MainForm

Das Hauptformular wird angesteuert. Hier könnte auch die Lage des auslösenden Buttons das Formular selbst ermitteln.
oColumns = oForm.Columns
oDB = ThisComponent.Parent

Der Zugriff auf die URL ist nicht vom Formular aus direkt möglich. Es muss auf den darüber liegenden Frame der Datenbank Bezug genommen werden.
stDir = Left(oDB.Location,Len(oDB.Location)-Len(oDB.Title))

Der Titel der Datenbank wird von der URL abgetrennt.
stDir = stDir & "Beispiel_Textfelder.ott"

Die Vorlage wird aufgesucht und geöffnet
DIM args(0) AS NEW com.sun.star.beans.PropertyValueargs(0).Name = "AsTemplate"args(0).Value = TrueoNewDoc = StarDesktop.loadComponentFromURL(stDir,"_blank",0,args)

Die Textfelder werden eingelesen
oTextfields = oNewDoc.Textfields.createEnumeration
DO WHILE oTextfields.hasMoreElements
oTextfield = oTextfields.nextElement
IF oTextfield.supportsService("com.sun.star.text.TextField.JumpEdit") THEN
stColumnname = oTextfield.PlaceHolder

Placeholder ist die Benennung für das Textfeld
IF oColumns.hasByName(stColumnname) THEN

Wenn der Name des Textfeldes gleich dem Spaltennamen der Daten ist, die dem Formularzugrunde liegen wird der Inhalt aus der Datenbank auf das Feld in dem Textdokument übertragen.
inIndex = oForm.findColumn(stColumnname)oTextfield.Anchor.String = oForm.getString(inIndex)
END IF
END IFLOOPEND SUB

Aufruf von Anwendungen zum Öffnen von Dateien
Mit dieser Prozedur kann durch einen Mausklick in ein Textfeld ein Programm aufgerufen werden,
das im eigenen Betriebssystem mit der Dateinamensendung verbunden ist. Damit werden auch
Links ins Internet oder sogar das Öffnen des Mailprogramms mit einer bestimmten Mailadressemöglich, die gerade in der Datenbank gespeichert wurde.
Siehe zu diesem Abschnitt auch die Beispieldatenbank «Mailstart_Dateiaufruf.odb»
Datenbankaufgaben mit Makros erweitert

57

SUB Link_oeffnen
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECTDIM oForm AS OBJECTDIM oFeld AS OBJECTDIM oShell AS OBJECT
DIM stFeld AS STRING
oDoc = thisComponent
oDrawpage = oDoc.Drawpage
oForm = oDrawpage.Forms.getByName("Formular")oFeld = oForm.getByName("Adresse")

Aus dem benannten Feld wird der Inhalt ausgelesen. Dies kann eine Webadresse, beginnend mit'http://', eine Mailadresse mit einem '@' oder ein einfaches Dokument sein, das durch eine
entsprechende Pfadangabe aufgesucht werden soll (z.B. externe Bilder, *.pdf-Dateien, Tondokumente …).
stFeld = oFeld.Text
IF stFeld = "" THEN
EXIT SUBEND IF

Ist das Feld leer, so soll das Makro sofort enden. Bei der Eingabe passiert es ja sehr oft, dass Felder mit der Maus aufgesucht werden. Ein Mausklick in das Feld, um dort zu ersten Mal schreibenzu können, soll aber noch nicht das Makro ausführen.
Jetzt wird gesucht, ob in dem Feld ein '@' enthalten ist. Dies deutet auf eine E-Mail-Adresse hin.
Es soll also das Mailprogramm gestartet werden und eine Mail an diese Mailadresse gesandt werden.
IF InStr(stFeld,"@") THEN
stFeld = "mailto:"+stFeld

Ist kein '@' vorhanden, so wird der Begriff so konvertiert, dass die Datei im Dateisystem gefunden
werden kann. Steht ein 'http://' am Anfang, so wird bei dieser Funktion nicht im Dateisystemsondern über den Webbrowser direkt im Internet nachgesehen. Ansonsten beginnt der erstellte
Pfad anschließend mit dem Begriff 'file:///'ELSE
stFeld = convertToUrl(stFeld)
END IF

Jetzt wird das Programm aufgesucht, das in dem eigenen Betriebssystem mit der entsprechenden
Dateiendung verbunden ist. Bei dem Stichwort 'mailto:' ist dies das Mailprogramm, bei'http://' der Webbrowser und bei allen anderen ist die Entscheidung des Systems mit den
Endungen der Datei verbunden.
oShell = createUnoService("com.sun.star.system.SystemShellExecute")
oShell.execute(stFeld,,0)
END SUB

Aufruf eines Mailprogramms mit Inhaltsvorgaben
Eine Erweiterung des vorhergehenden Beispiels zum Programmaufruf stellt dieser Aufruf eines
Mailprogramms mit Vorgaben in der Betreffzeile und inhaltlichen Vorgaben dar.
Siehe auch zu diesem Abschnitt die Beispieldatenbank «Mailstart_Dateiaufruf.odb»
Der Mailaufruf erfolgt mit 'mailto:Empfänger?subject= &body= &cc= &bcc= '. Die letzten beiden Eingaben sind im Formular allerdings nicht aufgeführt. Anhänge kommen in der Definition von 'mailto' nicht vor. Manchmal funktioniert allerdings 'attachment='.
SUB Mail_Aufruf
DIM oDoc AS OBJECTDIM oDrawpage AS OBJECT

Datenbankaufgaben mit Makros erweitert

58

DIM oForm AS OBJECTDIM oFeld1 AS OBJECTDIM oFeld2 AS OBJECTDIM oFeld3 AS OBJECTDIM oFeld4 AS OBJECTDIM oShell AS OBJECT
DIM stFeld1 AS STRING
DIM stFeld2 AS STRING
DIM stFeld3 AS STRING
DIM stFeld4 AS STRING
oDoc = thisComponent
oDrawpage = oDoc.Drawpage
oForm = oDrawpage.Forms.getByName("Formular")oFeld1 = oForm.getByName("E_Mail_Adresse")oFeld2 = oForm.getByName("E_Mail_Betreff")
oFeld3 = oForm.getByName("E_Mail_Inhalt")
stFeld1 = oFeld1.Text
IF stFeld1 = "" THEN
msgbox "Keine Mailadresse vorhanden." & CHR(13) & "Das Mailprogramm wird nicht
aufgerufen" , 48, "Mail senden"
EXIT SUBEND IFDie Konvertierung zu URL ist notwendig, damit Sonderzeichen und Zeilenumbrüche den Aufruf
nicht stören. Dabei wird allerdings auch der Begriff 'file:///' vorangestellt. Diese 8 Zeichen zu
Beginn werden nicht mit übernommen.
stFeld2 = Mid(ConvertToUrl(oFeld2.Text),9)
stFeld3 = Mid(ConvertToUrl(oFeld3.Text),9)Im Gegensatz zum einfachen Programmaufruf werden hier Details des Mailaufrufes über den Aufruf des Mailprogramms mitgegeben.
oShell = createUnoService("com.sun.star.system.SystemShellExecute")
oShell.execute("mailto:" + stFeld1 + "?subject=" + stFeld2 + "&body=" + stFeld3,,0)
END SUB

Mauszeiger beim Überfahren eines Links ändern
Im Internet üblich, bei Base nachgebaut: Der Mauszeiger fährt über ein Textfeld und verändert
seine Form zu einer zeigenden Hand. Der enthaltene Text kann jetzt noch in den Eigenschaften
des Feldes zu der Farbe Blau und unterstrichen geändert werden – schon ist der Eindruck eines
Links aus dem Internet perfekt. Jeder Nutzer erwartet nach einem Klick, dass sich ein externes
Programm öffnet.
Siehe auch zu diesem Abschnitt die Beispieldatenbank «Mailstart_Dateiaufruf.odb»
Diese kurze Prozedur sollte mit dem Ereignis 'Maus innerhalb' des Textfeldes verbunden werden.
SUB Mauszeiger(Event AS OBJECT)REM Siehe auch Standardbibliotheken: Tools → ModuleControls → SwitchMousePointerDIM oPointer AS OBJECT
oPointer = createUnoService("com.sun.star.awt.Pointer")oPointer.setType(27)'Typen in com.sun.star.awt.SystemPointerEvent.Source.Peer.SetPointer(oPointer)
END SUB

Formulare ohne Symbolleisten präsentieren
Neunutzer von Base sind häufig irritiert, dass z.B. eine Menüleiste existiert, diese aber im Formularso gar nicht verfügbar ist. Diese Menüleisten können auf verschiedene Arten ausgeblendet wer-

Datenbankaufgaben mit Makros erweitert59

den. Am erfolgreichsten unter allen LO-Versionen sind die beiden im Folgenden vorgestellten Vorgehensweisen.

Formulare ohne Symbolleisten in einem Fenster
Ein Fenster lässt sich in der Größe variieren. Über den entsprechenden Button lässt es sich auchschließen. Diese Aufgaben übernimmt der Window-Manager des jeweiligen Betriebssystems. Lageund Größe des Fensters auf dem Bildschirm kann beim Start über ein Makro mitgegeben werden.
SUB Symbolleisten_Ausblenden
DIM oFrame AS OBJECTDIM oWin AS OBJECTDIM oLayoutMng AS OBJECT
DIM aElemente()
oFrame = StarDesktop.getCurrentFrame()

Der Titel für das Formular wird in der Titelleiste des Fensters angezeigt.
oFrame.setTitle "Mein Formular"
oWin = oFrame.getContainerWindow()

Das Fenster wird auf die maximale Größe eingestellt. Dies entspricht nicht dem Vollbildmodus, daz.B. eine Kontrollleiste noch sichtbar ist und das Fenster eine Titelleiste hat, über die die Größe
des Fensters geändert und das Fenster geschlossen werden kann.
oWin.IsMaximized = true

Es besteht auch die Möglichkeit, das Fenster in einer ganz bestimmten Größe und mit einer festen
Position darzustellen. Dies würde mit 'oWin.setPosSize(0,0,600,400,15)' geschehen.
Hier wird das Fenster an der linken oberen Ecke des Bildschirms mit einer Breite von 600 Punkten
und einer Höhe von 400 Punkten dargestellt. Die letzte Ziffer weist darauf hin, dass alle Punkte
angegeben wurden. Sie wird als 'Flag' bezeichnet. Das 'Flag' wird auf mit den folgenden
Werten über eine Summierung berechnet: x=1, y=2, Breite=4, Höhe=8. Da x, y, Breite und Höheangegeben sind, hat das 'Flag' die Größe 1 + 2 + 4 + 8 = 15.
oLayoutMng = oFrame.LayoutManager
aElemente = oLayoutMng.getElements()
FOR i = LBound(aElemente) TO UBound(aElemente)
IF aElemente(i).ResourceURL = "private:resource/toolbar/formsnavigationbar"
THEN

Wenn es sich um die Navigationsleiste handelt soll nichts geschehen. Das Formular soll schließlich bedienbar bleiben, wenn nicht das Kontrollfeld für die Navigationsleiste eingebaut und die
Navigationsleiste sowieso ausgeblendet wurde. Nur wenn es sich nicht um die Navigationsleiste
handelt soll die entsprechende Leiste verborgen werden.
Hier wäre es auch möglich, sie als unsichtbar zu deklarieren, Nur erscheint dann, je nach OfficeVersion, z.B. die Menüleiste nach einem Klick in ein Formularelement wieder und lässt sich nicht
erneut als unsichtbar deklarieren.
ELSE' msgbox aElemente(i).ResourceURL
oLayoutMng.hideElement(aElemente(i).ResourceURL)
END IFNEXT
END SUB

Werden die Symbolleisten nicht wieder direkt beim Beenden des Formulars eingeblendet, so bleiben sie weiterhin verborgen. Sie können natürlich über Ansicht → Symbolleisten wieder aufgerufen werden. Etwas irritierend ist es jedoch, wenn gerade die Standardleiste (Ansicht → Symbolleisten → Standardleiste) oder die Statusleiste (Ansicht → Statusleiste) fehlt.
Mit dieser Prozedur werden die Symbolleisten aus dem Versteck ('hideElement') wieder hervorgeholt ('showElement'). Der Kommentar enthält die Leisten, die oben als sonst fehlendeLeisten am ehesten auffallen.
SUB Symbolleisten_Einblenden

Datenbankaufgaben mit Makros erweitert

60

DIM oFrame AS OBJECTDIM oLayoutMng AS OBJECT
DIM aElemente()
oFrame = StarDesktop.getCurrentFrame()oLayoutMng = oFrame.LayoutManager
aElemente = oLayoutMng.getElements()
FOR i = LBound(aElemente) TO UBound(aElemente)oLayoutMng.showElement(aElemente(i).ResourceURL)NEXT' eventuell fehlende wichtige Elemente:' "private:resource/toolbar/standardbar"' "private:resource/statusbar/statusbar"
END SUBDie Makros werden an die Eigenschaften des Formularfensters gebunden: Extras → Anpassen→ Ereignisse → Dokument öffnen → Symbolleisten_Ausblenden bzw. … Dokument
geschlossen → Symbolleisten_Einblenden

Formulare im Vollbildmodus
Beim Vollbildmodus wird der gesamte Bildschirm vom Formular bedeckt. Hier steht keine Kontrollleiste o.ä. mehr zur Verfügung, die gegebenenfalls anzeigt, ob noch irgendwelche anderen Programme laufen.
FUNCTION Fullscreen(boSwitch AS BOOLEAN)
DIM oDispatcher AS OBJECTDIM Props(0) AS NEW com.sun.star.beans.PropertyValueoDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
Props(0).Name = "FullScreen"
Props(0).Value = boSwitch
oDispatcher.executeDispatch(ThisComponent.CurrentController.Frame,".uno:FullScreen", "", 0, Props())
END FUNCTION

Diese Funktion wird durch die folgenden Prozeduren eingeschaltet. In den Prozeduren läuft gleichzeitig die vorhergehende Prozedur zum Ausblenden der Symbolleisten ab – sonst erscheint die
Symbolleiste, mit der der Vollbildmodus wieder ausgeschaltet werden kann. Auch dies ist eine
Symbolleiste, wenn auch nur mit einem Symbol.
SUB Vollbild_ein
Fullscreen(true)
Symbolleisten_Ausblenden
END SUB

Aus dem Vollbild-Modus geht es wieder heraus über die 'ESC'-Taste. Wenn stattdessen ein Button mit einem entsprechenden Befehl belegt werden soll, so reichen auch die folgenden Zeilen:
SUB Vollbild_aus
Fullscreen(false)
Symbolleisten_Einblenden
END SUBMySQL-Datenbank mit Makros ansprechen
Sämtliche bisher vorgestellten Makros wurden mit der internen HSQLDB verbunden. Bei der Arbeit
mit externen Datenbanken sind ein paar Änderungen und Erweiterungen notwendig.

MySQL-Code in Makros
Wird die interne Datenbank angesprochen, so werden die Tabellen und Felder mit doppelten
Anführungszeichen gegenüber dem SQL-Code abgesetzt:
SELECT "Feld" FROM "Tabelle"

MySQL-Datenbank mit Makros ansprechen61

Da in Makros der SQL-Befehl Text darstellt müssen die doppelten Anführungszeichen zusätzlich
maskiert werden:
stSQL = "SELECT ""Feld"" FROM ""Tabelle"""MySQL-Abfragen hingegen werden anders maskiert:
SELECT `Feld` FROM `Datenbank`.`Tabelle`
Durch diese andere Form der Maskierung wird daraus im Makro-Code:stSql = "SELECT `Feld` FROM `Datenbank`.`Tabelle`"

Temporäre Tabelle als individueller Zwischenspeicher
In den vorhergehenden Kapiteln wurde häufiger eine einzeilige Tabelle zum Suchen oder Filtern
von Tabellen genutzt. In einem Mehrbenutzersystem kann darauf nicht zurückgegriffen werden, dasonst andere Nutzer von dem Filterwert eines anderen Nutzers abhängig würden. Temporäre
Tabellen sind in MySQL nur für den Nutzer der gerade aktiven Verbindung zugänglich, so dass fürdie Such- und Filterfunktionen auf diese Tabellenform zugegriffen werden kann.
Diese Tabellen können natürlich nicht vorher erstellt worden sein. Sie müssen beim Öffnen derBase-Datei erstellt werden. Deshalb ist das folgende Makro mit dem Öffnen der *.odb-Datei zu verbinden:
SUB CreateTempTable
oDatenquelle = thisDatabaseDocument.CurrentController
IF NOT (oDatenquelle.isConnected()) THEN oDatenquelle.connect()oVerbindung = oDatenquelle.ActiveConnection()oSQL_Anweisung = oVerbindung.createStatement()
stSql = "CREATE TEMPORARY TABLE IF NOT EXISTS `Suchtmp` (`ID` INT PRIMARY KEY,
`Name` VARCHAR(50))"oSQL_Anweisung.executeUpdate(stSql)
END SUBZum Start der *.odb-Datei besteht noch keine Verbindung zur externen MySQL-Datenbank. Die
Verbindung muss erst einmal hergestellt werden. Dann wird eine temporäre Tabelle mit entsprechend notwendigen Feldern erstellt.

DialogeFehleingaben in Feldern fallen häufig erst später auf. Manchmal müssen auch gleich mehrere
Datensätze mit der gleichen Eingabe auf einmal geändert werden. Dies ist in der normalen Tabellenansicht umständlich, je mehr Änderungen vorgenommen werden müssen, da für jeden Datensatz einzeln eine Eingabe erforderlich ist.
Formulare könnten hier mit Makros greifen. Wird aber für viele Tabellen ein identisch aufgebautes
Formular benötigt, so bietet sich an, dies mit Dialogen zu erledigen. Ein Dialog wird zu Beginn mit
den notwendigen Daten zu der jeweiligen Tabelle versehen und kann so statt mehrerer Formulare
genutzt werden.

Dialoge62

Dialoge werden neben den Modulen für Makros abgespeichert. Ihre Erstellung erfolgt ähnlich dereines Formulars. Hier stehen auch weitgehend ähnliche Kontrollfelder zur Verfügung. Lediglich das
Tabellenkontrollfeld aus dem Formular fehlt als besondere Eingabemöglichkeit.

Wird ein Dialog ausgeführt, so erscheinen die Kontrollfelder entsprechend der Einstellung der
grafischen Benutzeroberfläche.
Der oben abgebildete Dialog der Beispieldatenbank soll dazu dienen, die Tabellen zu bearbeiten,
die nicht direkt in einem der Formulare als Grundlage vorhanden sind. So ist z.B. die Medienartüber ein Listenfeld zugänglich, in der Makro-Version bereits durch ein Kombinationsfeld. In der
Makro-Version können die Inhalte der Felder zwar durch neue Inhalte ergänzt werden, eine Änderung alter Inhalte ist aber nicht möglich. In der Version ohne Makros erfolgt die Änderung über ein
separates Tabellenkontrollfeld.
Während die Änderung noch ohne Makros recht einfach in den Griff zu bekommen ist, so ist es
doch recht umständlich, die Medienart vieler Medien auf eine andere Medienart zu ändern. Angenommen, es gäbe die Medienarten 'Buch, gebunden', 'Buch, kartoniert', 'Taschenbuch' und 'Ringbuch'. Jetzt stellt sich nach längerem Betrieb der Datenbank heraus, dass muntere Zeitgenossen
noch weitere ähnliche Medienarten für gedruckte Werke vorgesehen haben. Nur ist uns die Differenzierung viel zu weitgehend. Es soll also reduziert werden, am liebsten auf nur einen Begriff.
Dialoge63Ohne Makro müssten jetzt die Datensätze in der Tabelle Medien (mit Hilfe von Filtern) aufgesucht
werden und einzeln geändert werden. Mit Kenntnis von SQL geht dies über die SQL-Eingabe
schon wesentlich besser. Mit einer Eingabe werden alle Datensätze der Tabelle Medien geändert.
Mit einer zweiten SQL-Anweisung wird dann die jetzt überflüssige Medienart gelöscht, die keine
Verbindung mehr zur Tabelle "Medien" hat. Genau dieses Verfahren wird mit diesem Dialog über«Ersetzen durch:» angewandt – nur dass eben die SQL-Anweisung erst über das Makro an die
Tabelle "Medienart" angepasst wird, da das Makro auch andere Tabellen bearbeiten können soll.
Manchmal schleichen sich auch Eingaben in eine Tabelle ein, die im Nachhinein in den Formularen
geändert wurden, also eigentlich gar nicht mehr benötigt werden. Da kann es nicht schaden, solche verwaisten Datensätze einfach zu löschen. Nur sind die über die grafische Oberfläche recht
schwer ausfindig zu machen. Hier hilft wieder eine entsprechende SQL-Abfrage, die mit einer
Löschanweisung gekoppelt ist. Diese Anweisung ist im Dialog je nach betroffener Tabelle unter«Alle überflüssigen Eingaben löschen» hinterlegt.
Sollen mit dem Dialog mehrere Änderungen durchgeführt werden, so ist dies über das Markierfeld«Mehrere Datensätze bearbeiten» anzugeben. Dann endet der Dialog nicht mit der Betätigung desButtons «OK».
Der Makrocode für diesen Dialog ist aus der Beispieldatenbank ersichtlich. Im Folgenden werden
nur Ausschnitte daraus erläutert.
SUB Tabellenbereinigung(oEvent AS OBJECT)

Das Makro soll über Einträge im Bereich «Zusatzinformationen» des jeweiligen Buttons gestartet
werden.
0: Formular, 1: Unterformular, 2: UnterUnterformular, 3: Kombinationsfeld oder
Tabellenkontrollfeld, 4: Fremdschlüsselfeld im Formular, bei Tabellenkontrollfeld
leer, 5: Tabellenname Nebentabelle, 6: Tabellenfeld1 Nebentabelle, 7: Tabellenfeld2
Nebentabelle, ggf. 8: Tabellenname Nebentabelle für Tabellenfeld 2

Die Einträge in diesem Bereich werden zu Beginn des Makros als Kommentar aufgelistet. Die
damit verbundenen Ziffern geben die Ziffern wieder, unter denen der jeweilige Eintrag aus dem
Array ausgelesen wird. Das Makro kann Listenfelder verarbeiten, die zwei Einträge, getrennt durch«>», enthalten. Diese beiden Einträge können auch aus unterschiedlichen Tabellen stammen undüber eine Abfrage zusammengeführt sein, wie z.B. bei der Tabelle "Postleitzahl", die für die Ortelediglich das Fremdschlüsselfeld "Ort_ID" enthält, zur Darstellung des Ortes also die Tabelle "Ort"benötigt.
DIM aFremdTabellen(0, 0 to 1)
DIM aFremdTabellen2(0, 0 to 1)

Unter den zu Beginn definierten Variablen fallen zwei Arrays auf. Während normale Arrays auchdurch den Befehl 'Split()' während der Laufzeit der Prozedur erstellt werden können, müssen
zweidimensionale Arrays vorher definiert werden. Zweidimensionale Arrays werden z.B. benötigt,
um aus einer Abfrage mehrere Datensätze zu speichern, bei denen die Abfrage selbst über mehrals ein Feld geht. Die beiden obigen Arrays müssen Abfragen auswerten, die sich jeweils auf zwei
Tabellenfelder beziehen. Deshalb werden sie in der zweiten Dimension mit '0 to 1' auf zwei unterschiedliche Inhalte festgelegt.
stTag = oEvent.Source.Model.Tag
aTabelle() = Split(stTag, ", ")FOR i = LBound(aTabelle()) TO UBound(aTabelle())
aTabelle(i) = trim(aTabelle(i))
NEXT

Die mitgegebenen Variablen werden ausgelesen. Die Reihenfolge steht im obigen Kommentar. Es
gibt maximal 9 Einträge, wobei geklärt werden muss, ob ein 8. Eintrag für das Tabellenfeld2 undein 9. Eintrag für eine zweite Tabelle existieren.
Wenn Werte aus einer Tabelle entfernt werden, so muss zuerst einmal berücksichtigt werden, ob
sie nicht noch als Fremdschlüssel in anderen Tabellen existieren. In einfachen Tabellenkonstruktionen gibt es von einer Tabelle aus lediglich eine Fremdschlüsselverbindung zu einer anderen
Dialoge64

Tabelle. In der vorliegenden Beispieldatenbank aber wird z.B. die Tabelle "Ort" genutzt, um dieErscheinungsorte der Medien und die Orte für die Adressen zu speichern. Es wird also zweimal
der Primärschlüssel der Tabelle "Ort" in unterschiedlichen Tabellen eingetragen. Diese Tabellen
und Fremdschlüsselbezeichnungen könnten natürlich auch über die «Zusatzinformationen» eingegeben werden. Schöner wäre es aber, wenn sie universell für alle Fälle ermittelt werden. Diesgeschieht durch die folgende Abfrage.
stSql = "SELECT ""FKTABLE_NAME"", ""FKCOLUMN_NAME"" FROM""INFORMATION_SCHEMA"".""SYSTEM_CROSSREFERENCE"" WHERE ""PKTABLE_NAME"" = '" +aTabelle(5) + "'"In der Datenbank sind im Bereich "INFORMATION_SCHEMA" alle Informationen zu den Tabellen
der Datenbank abgespeichert, so auch die Informationen zu den Fremdschlüsseln. Die entsprechende Tabelle, die diese Informationen enthält, ist über"INFORMATION_SCHEMA"."SYSTEM_CROSSREFERENCE" erreichbar. Mit "PKTABLE_NAME"
wird die Tabelle erreicht, die ihren Primärschlüssel ("Primary Key") in die Beziehung mit einbringt.
Mit "FKTABLE_NAME" wird die Tabelle erreicht, die diesen Primärschlüssel als Fremdschlüssel
("Foreign Key") nutzt. Über "FKCOLUMN_NAME" wird schließlich die Bezeichnung des Fremdschlüsselfeldes ermittelt.
Die Tabelle, die einen Primärschlüssel als Fremdschlüssel zur Verfügung stellt, befindet sich indem vorher erstellten Array an der 6. Position. Da die Zählung mit 0 beginnt, wird der Wert aus
dem Array mit 'aTabelle(5)' ermittelt.
inZaehler = 0
stFremdIDTab1Tab2 = "ID"stFremdIDTab2Tab1 = "ID"stNebentabelle = aTabelle(5)Bevor die Auslesung des Arrays gestartet wird, müssen einige Standardwerte gesetzt werden. Diessind der Zähler für das Array, in das die Werte der Nebentabelle geschrieben werden, der Standardprimärschlüssel, wenn nicht der Fremdschlüssel für eine zweite Tabelle benötigt wird und die
Standardnebentabelle, die sich auf die Haupttabelle bezieht, bei Postleitzahl und Ort z.B. die
Tabelle für die Postleitzahl.
Bei der Verknüpfung von zwei Feldern zur Anzeige in den Listenfeldern kann es ja, wie oben
erwähnt, zu einer Verknüpfung über zwei Tabellen kommen. Für die Darstellung von Postleitzahl
und Ort lautet hier die Abfrage
SELECT "Postleitzahl"."Postleitzahl" || ' > ' || "Ort"."Ort" FROM "Postleitzahl", "Ort" WHERE"Postleitzahl"."Ort_ID" = "Ort"."ID"
Die Tabelle, die sich auf das erste Feld bezieht (Postleitzahl), ist mit der zweiten Tabelle über einen
Fremdschlüssel verbunden. Lediglich die Information der beiden Tabellen und der Felder "Postleitzahl" und "Ort" wurde dem Makro mitgegeben. Die Primärschlüssel sind standardmäßig in dem
Beispiel mit der Bezeichnung "ID" versehen. Der Fremdschlüssel von "Ort" in "Postleitzahl" mussalso über das Makro ermittelt werden.
Genauso muss über das Makro jede andere Tabelle ermittelt werden, mit der die Inhalte des Listenfeldes über Fremdschlüssel in Verbindung stehen.
oAbfrageergebnis = oSQL_Anweisung.executeQuery(stSql)
WHILE oAbfrageergebnis.next
ReDim Preserve aFremdTabellen(inZaehler,0 to 1)

Das Array muss jedes Mal neu Dimensioniert werden. Damit die alten Inhalte erhalten bleiben
erfolgt über (Preserve) eine Sicherung des vorherigen Inhaltes.
aFremdTabellen(inZaehler,0) = oAbfrageergebnis.getString(1)Auslesen des ersten Feldes mit dem Namen der Tabelle, die den Fremdschlüssel enthält. Ergebnis
für die Tabelle "Postleitzahl" ist hier die Tabelle "Adresse".
aFremdTabellen(inZaehler,1) = oAbfrageergebnis.getString(2)Dialoge65

Auslesen des zweiten Feldes mit der Bezeichnung des Fremdschlüsselfeldes. Ergebnis für die
Tabelle "Postleitzahl" ist hier das Feld "Postleitzahl_ID" in der Tabelle "Adresse".
Für den Fall, dass dem Aufruf der Prozedur auch der Name einer zweiten Tabelle mitgegeben
wurde erfolgt die folgende Schleife. Nur wenn der Name der zweiten Tabelle als Fremdschlüsseltabelle für die erste Tabelle auftaucht erfolgt hier eine Änderung der Standardeinträge. In unserem
Fall kommt dies nicht vor, da die Tabelle "Ort" keinen Fremdschlüssel der Tabelle "Postleitzahl"
enthält. Der Standardeintrag für die Nebentabelle bleibt also bei "Postleitzahl"; schließlich ist die
Kombination von Postleitzahl und Ort eine Grundlage für die Adressentabelle, die einen Fremdschlüssel zu der Tabelle "Postleitzahl" enthält.
IF UBound(aTabelle()) = 8 THEN
IF aTabelle(8) = aFremdTabellen(inZaehler,0) THEN
stFremdIDTab2Tab1 = aFremdTabellen(inZaehler,1)stNebentabelle = aTabelle(8)END IF
END IF
inZaehler = inZaehler + 1

Da eventuell noch weitere Werte auszulesen sind, erfolgt eine Erweiterung des Zählers zur Neudimensionierung des Arrays. Anschließend wird die Schleife beendet.
WEND

Existiert im Aufruf der Prozedur ein zweiter Tabellenname, so wird die gleiche Abfrage jetzt mit
dem zweiten Tabellennamen gestartet:IF UBound(aTabelle()) = 8 THEN

Der Ablauf ist identisch. Nur wird in der Schleife jetzt gesucht, ob vielleicht der erste Tabellenname
als Fremdschlüssel-Tabellenname auftaucht. Das ist hier der Fall: Die Tabelle "Postleitzahl" enthält
den Fremdschlüssel "Ort_ID" aus der Tabelle "Ort". Dieser Fremdschlüssel wird also jetzt der
Variablen «stFremdIDTab1Tab2» zugewiesen, so dass die Beziehung der Tabellen untereinander
definiert werden kann.
IF aTabelle(5) = aFremdTabellen2(inZaehler,0) THEN
stFremdIDTab1Tab2 = aFremdTabellen2(inZaehler,1)
END IF

Nach einigen weiteren Einstellungen zur korrekten Rückkehr nach Aufruf des Dialogs in die entsprechenden Formulare (Ermittlung der Zeilennummer des Formulars, damit nach einem Neueinlesen auf die Zeilennummer wieder gesprungen werden kann) startet die Schleife, die den Dialog
gegebenenfalls wieder neu erstellt, wenn die erste Aktion erfolgt ist, der Dialog aber für weitere
Aktionen offen gehalten werden soll. Die Einstellung zur Wiederholung erfolgt über das entsprechende Markierfeld.
DO

Bevor der Dialog gestartet wird, wird erst einmal der Inhalt der Listenfelder ermittelt. Dabei muss
berücksichtigt werden, ob die Listenfelder zwei Tabellenfelder darstellen und eventuell sogar einen
Bezug zu zwei Tabellen haben.
IF UBound(aTabelle()) = 6 THEN

Das Listenfeld bezieht sich nur auf eine Tabelle und ein Feld, da das Array bei dem Tabellenfeld1
der Nebentabelle endet.
stSql = "SELECT """ + aTabelle(6) + """ FROM """ + aTabelle(5) + """ ORDER
BY """ + aTabelle(6) + """"ELSEIF UBound(aTabelle()) = 7 THEN

Das Listenfeld bezieht sich auf zwei Tabellenfelder, aber nur auf eine Tabelle, da das Array bei
dem Tabellenfeld2 der Nebentabelle endet.
stSql = "SELECT """ + aTabelle(6) + """||' > '||""" + aTabelle(7) + """FROM """ + aTabelle(5) + """ ORDER BY """ + aTabelle(6) + """"ELSE

Das Listenfeld hat zwei Tabellenfelder und zwei Tabellen als Grundlage. Diese Abfrage trifft also
auf das Beispiel mit der Postleitzahl und den Ort zu.
Dialoge66

stSql = "SELECT """ + aTabelle(5) + """.""" + aTabelle(6) + """||' > '||"""+ aTabelle(8) + """.""" + aTabelle(7) + """ FROM """ + aTabelle(5) + """, """ +aTabelle(8) + """ WHERE """ + aTabelle(8) + """.""" + stFremdIDTab2Tab1 + """ = """ +aTabelle(5) + """.""" + stFremdIDTab1Tab2 + """ ORDER BY """ + aTabelle(6) + """"END IF

Hier erfolgt die erste Auswertung zur Ermittlung von Fremdschlüsseln. Die Variablen «stFremdIDTab2Tab1» und «stFremdIDTab1Tab2» starten mit dem Wert "ID". Für «stFremdIDTab1Tab2»
wurde in der Auswertung der vorhergehenden Abfrage ein anderer Wert ermittelt, nämlich der Wert
"Ort_ID". Damit ergibt die vorherige Abfragekonstruktion genau den Inhalt, der weiter oben bereitsfür Postleitzahl und Ort formuliert wurde – lediglich erweitert um die Sortierung.
Jetzt muss der Kontakt zu den Listenfeldern erstellt werden, damit diese mit dem Inhalt der Abfragen bestückt werden. Diese Listenfelder existieren noch nicht, da noch gar kein Dialog existiert.
Dieser Dialog wird mit den folgenden Zeilen erst einmal im Speicher erstellt, bevor er tatsächlich
auf dem Bildschirm ausgeführt wird.
DialogLibraries.LoadLibrary("Standard")oDlg = CreateUnoDialog(DialogLibraries.Standard.Dialog_Tabellenbereinigung)

Anschließend werden Einstellungen für die Felder, die der Dialog enthält, ausgeführt. Hier als Beispiel das Auswahllistenfeld, das mit dem Ergebnis der obigen Abfrage bestückt wird:
oCtlList1 = oDlg.GetControl("ListBox1")
oCtlList1.addItems(aInhalt(),0)

Der Zugriff auf die Felder des Dialogs erfolgt über 'GetControl' sowie die entsprechende
Bezeichnung. Bei Dialogen ist es nicht möglich, für zwei Felder die gleichen Bezeichnungen zu
verwenden, da sonst eine Auswertung des Dialoges problematisch wäre.
Das Listenfeld wird mit den Inhalten aus der Abfrage, die in dem Array «aInhalt()» gespeichert wurden, ausgestattet. Das Listenfeld enthält nur die darzustellenden Inhalte als ein Feld, wird also nur
in der Position '0' bestückt.
Nachdem alle Felder mit den gewünschten Inhalten versorgt wurden wird der Dialog gestartet.
Select Case oDlg.Execute()
Case 1 'Case 1 bedeutet die Betätigung des Buttons "OK"
Case 0 'Wenn Button "Abbrechen"
inWiederholung = 0
End SelectLOOP WHILE inWiederholung = 1

Der Dialog wird so lange durchgeführt, wie der Wert für «inWiederholung» auf 1 steht. Diese Setzung erfolgt mit dem entsprechenden Markierfeld.
Hier der Inhalt nach Betätigung des Buttons «OK» im Kurzüberblick:
Case 1stInhalt1 = oCtlList1.getSelectedItem() 'Wert aus Listbox1 auslesen ...
REM ... und den dazugehoerigen ID-Wert bestimmen.

Der ID-Wert des ersten Listenfeldes wird in der Variablen «inLB1» gespeichert.
stText = oCtlText.Text ' Den Wert des Feldes auslesen.

Ist das Textfeld nicht leer, so wird nur der Eintrag im Textfeld erledigt. Weder das Listenfeld für eine
andere Zuweisung noch das Markierfeld für eine Löschung aller Daten ohne Bezug werden
berücksichtigt. Dies wird auch dadurch verdeutlicht, dass bei Texteingabe die anderen Felder inaktiv geschaltet werden.
IF stText <> "" THEN

Ist das Textfeld nicht leer, dann wird der neue Wertes anstelle des alten Wertes mit Hilfe des vorher ausgelesenen ID-Feldes in die Tabelle geschrieben. Dabei werden wieder zwei Einträge
ermöglicht, wie dies auch in dem Listenfeld geschieht. Das Trennzeichen ist «>». Bei Zwei Einträgen in verschiedenen Tabellen müssen auch entsprechend zwei UPDATE-Kommandos gestartet
werden, die hier gleichzeitig erstellt werden und, durch ein Semikolon getrennt, weitergeleitet werden.
Dialoge

67

ELSEIF oCtlList2.getSelectedItem() <> "" THEN

Wenn das Textfeld leer ist und das Listenfeld 2 einen Wert aufweist muss der Wert des Listenfeldes 1 durch den Wert des Listenfeldes 2 ersetzt werden. Das bedeutet, dass alle Datensätze derTabellen, in denen die Datensätze der Listenfelder Fremdschlüssel sind, überprüft und gegebenenfalls mit einem geänderten Fremdschlüssel beschrieben werden müssen.
stInhalt2 = oCtlList2.getSelectedItem()
REM Den Wert der Listbox auslesen.
REM ID für den Wert das Listenfeld ermitteln.

Der ID-Wert des zweiten Listenfeldes wird in der Variablen «inLB2» gespeichert. Auch dieses
erfolgt wieder unterschiedlich, je nachdem, ob ein oder zwei Felder in dem Listenfeld enthaltensind, sowie eine oder zwei Tabellen Ursprungstabellen des Listenfeldinhaltes sind.
Der Ersetzungsprozess erfolgt danach, welche Tabelle als die Tabelle definiert wurde, die für die
Haupttabelle den Fremdschlüssel darstellt. Für das oben erwähnte Beispiel ist dies die Tabelle"Postleitzahl", da die "Postleitzahl_ID" der Fremdschlüssel ist, der durch Listenfeld 1 und Listenfeld
2 wiedergegeben wird.
IF stNebentabelle = aTabelle(5) THEN
FOR i = LBound(aFremdTabellen()) TO UBound(aFremdTabellen())

Ersetzen des alten ID-Wertes durch den neuen ID-Wert. Problematisch ist dies bei n:m-Beziehungen, da dann der gleiche Wert doppelt zugeordnet werden kann. Dies kann erwünscht sein, mussaber vermieden werden, wenn der Fremdschlüssel hier Teil des Primärschlüssels ist. So darf in derTabelle "rel_Medien_Verfasser" ein Medium nicht zweimal den gleichen Verfasser haben, da derPrimärschlüssel aus der "Medien_ID" und der "Verfasser_ID" gebildet wird. In der Abfrage werdenalle Schlüsselfelder untersucht, die zusammen die Eigenschaft UNIQUE haben oder als Fremdschlüssel mit der Eigenschaft 'UNIQUE' über einen Index definiert wurden.
Sollte also der Fremdschlüssel die Eigenschaft 'UNIQUE' haben und bereits mit der gewünschten
zukünftigen «inLB2» dort vertreten sein, so kann der Schlüssel nicht ersetzt werden.
stSql = "SELECT ""COLUMN_NAME"" FROM ""INFORMATION_SCHEMA"".""SYSTEM_INDEXINFO""WHERE ""TABLE_NAME"" = '" + aFremdTabellen(i,0) + "' AND ""NON_UNIQUE"" = False AND""INDEX_NAME"" = (SELECT ""INDEX_NAME"" FROM""INFORMATION_SCHEMA"".""SYSTEM_INDEXINFO"" WHERE ""TABLE_NAME"" = '" +aFremdTabellen(i,0) + "' AND ""COLUMN_NAME"" = '" + aFremdTabellen(i,1) + "')"

Mit ' "NON_UNIQUE" = False ' werden die Spaltennamen angegeben, die 'UNIQUE' sind.
Allerdings werden nicht alle Spaltennamen benötigt sondern nur die, die gemeinsam mit dem
Fremdschlüsselfeld einen Index bilden. Dies ermittelt der 'Subselect' mit dem gleichen Tabellennamen (der den Fremdschlüssel enthält) und dem Namen des Fremdschlüsselfeldes.
Wenn jetzt der Fremdschlüssel in der Ergebnismenge vorhanden ist, dann darf der Schlüsselwert
nur dann ersetzt werden, wenn gleichzeitig andere Felder dazu benutzt werden, den entsprechenden Index als 'UNIQUE' zu definieren. Hierzu muss beim Ersetzen darauf geachtet werden, dassdie Einzigartigkeit der Indexkombination nicht verletzt wird.
IF aFremdTabellen(i,1) = stFeldbezeichnung THEN
inUnique = 1
ELSE
ReDim Preserve aSpalten(inZaehler)
aSpalten(inZaehler) = oAbfrageergebnis.getString(1)inZaehler = inZaehler + 1
END IF

Alle Spaltennamen, die neben dem bereits bekannten Spaltennamen des Fremdschlüsselfeldes
als Index mit der Eigenschaft UNIQUE auftauchen, werden in einem Array abgespeichert. Da der
Spaltenname des Fremdschlüsselfeldes auch zu der Gruppe gehört wird durch ihn gekennzeichnet, dass die Einzigartigkeit bei der Datenänderung zu berücksichtigen ist.
IF inUnique = 1 THEN
stSql = "UPDATE """ + aFremdTabellen(i,0) + """ AS ""a"" SET """ +aFremdTabellen(i,1) + """='" + inLB2 + "' WHERE """ + aFremdTabellen(i,1) + """='" +Dialoge68

inLB1 + "' AND ( SELECT COUNT(*) FROM """ + aFremdTabellen(i,0) + """ WHERE """ +aFremdTabellen(i,1) + """='" + inLB2 + "' )"IF inZaehler > 0 THEN
stFeldgruppe = Join(aSpalten(), """|| ||""")

Gibt es mehrere Felder, die neben dem Fremdschlüsselfeld gemeinsam einen 'UNIQUE'-Index bilden, so werden die hier für eine SQL-Gruppierung zusammengeführt. Ansonsten erscheint als«stFeldgruppe» nur «aSpalten(0)».
stFeldbezeichnung = ""FOR ink = LBound(aSpalten()) TO UBound(aSpalten())
stFeldbezeichnung = stFeldbezeichnung + " AND """ + aSpalten(ink) + """ =""a"".""" + aSpalten(ink) + """ "Die SQL-Teilstücke werden für eine korrelierte Unterabfrage zusammengefügt.
NEXT
stSql = Left(stSql, Len(stSql) – 1)

Die vorher erstellte Abfrage endet mit einer Klammer. Jetzt sollen noch Inhalte zu der Unterabfrage
hinzugefügt werden. Also muss die Schließung wieder aufgehoben werden. Anschließend wird die
Abfrage durch die zusätzlich ermittelten Bedingungen ergänzt.
stSql = stSql + stFeldbezeichnung + "GROUP BY (""" + stFeldgruppe + """) ) < 1"
END IF

Wenn die Feldbezeichnung des Fremdschlüssels nichts mit dem Primärschlüssel oder einem 'UNIQUE'-Index zu tun hat, dann kann ohne weiteres auch ein Inhalt doppelt erscheinen
ELSE
stSql = "UPDATE """ + aFremdTabellen(i,0) + """ SET """ + aFremdTabellen(i,1) +"""='" + inLB2 + "' WHERE """ + aFremdTabellen(i,1) + """='" + inLB1 + "'"END IFoSQL_Anweisung.executeQuery(stSql)
NEXT

Das Update wird so lange durchgeführt, wie unterschiedliche Verbindungen zu anderen Tabellen
mit vorkommen, d.h. Die aktuelle Tabelle einen Fremdschlüssel in anderen Tabellen liegen hat.
Dies ist z.B. Bei der Tabelle "Ort" zweimal der Fall: in der Tabelle "Medien" und in der Tabelle"Postleitzahl".
Anschließend kann der alte Wert aus dem Listenfeld 1 gelöscht werden, weil er keine Verbindung
mehr zu anderen Tabellen hat.
stSql = "DELETE FROM """ + aTabelle(5) + """ WHERE ""ID""='" + inLB1 + "'"oSQL_Anweisung.executeQuery(stSql)

Das gleiche Verfahren muss jetzt auch für eine eventuelle zweite Tabelle durchgeführt werden, ausder die Listenfelder gespeist werden. In unserem Beispiel ist die erste Tabelle die Tabelle "Postleitzahl", die zweite Tabelle die Tabelle "Ort".
Wenn das Textfeld leer ist und das Listenfeld 2 ebenfalls nichts enthält wird nachgesehen, ob
eventuell das Markierfeld darauf hindeutet, dass alle überflüssigen Einträge zu löschen sind. Dies
ist für die Einträge der Fall, die nicht mit anderen Tabellen über einen Fremdschlüssel verbundensind.
ELSEIF oCtlCheck1.State = 1 THEN
stBedingung = ""IF stNebentabelle = aTabelle(5) THEN
FOR i = LBound(aFremdTabellen()) TO UBound(aFremdTabellen())
stBedingung = stBedingung + """ID"" NOT IN (SELECT """ +aFremdTabellen(i,1) + """ FROM """ + aFremdTabellen(i,0) + """) AND "
NEXT
ELSE
FOR i = LBound(aFremdTabellen2()) TO UBound(aFremdTabellen2())
stBedingung = stBedingung + """ID"" NOT IN (SELECT """ +aFremdTabellen2(i,1) + """ FROM """ + aFremdTabellen2(i,0) + """) AND "NEXT
END IF

Dialoge69

Das letzte «AND» muss abgeschnitten werden, da sonst die Löschanweisung mit einem «AND»
enden würde.
stBedingung = Left(stBedingung, Len(stBedingung) - 4) 'stSql = "DELETE FROM """ + stNebentabelle + """ WHERE " + stBedingung + ""oSQL_Anweisung.executeQuery(stSql)

Da nun schon einmal die Tabelle bereinigt wurde kann auch gleich der Tabellenindex überprüft undgegebenenfalls nach unten korrigiert werden. Siehe hierzu die in dem vorhergehenden Kapitel
Tabellenindex heruntersetzen bei Autowert-Feldern erwähnte Prozedur.
Tabellenindex_runter(stNebentabelle)

Anschließend wird noch gegebenenfalls das Listenfeld des Formulars, aus dem der Tabellenbereinigungsdialog aufgerufen wurde, auf den neuesten Stand gebracht. Unter Umständen ist das
gesamte Formular neu einzulesen. Hierzu wurde zu Beginn der Prozedur der aktuelle Datensatz
ermittelt, so dass nach einem Auffrischen des Formulars der aktuelle Datensatz auch wieder eingestellt werden kann.
oDlg.endExecute()'Dialog beenden ...
oDlg.Dispose()'... und aus dem Speicher entfernen
END SUB

Dialoge werden mit «endExecute()» beendet und mit «Dispose()» komplett aus dem Speicher entfernt.

Dialoge70