Base Handbuch
LibreOffice 24.2
Dieses Dokument unterliegt dem Copyright © 2024. Die Beitragenden sind unten aufgeführt. Sie dürfen dieses Dokument unter den Bedingungen der GNU General Public License (http://www.gnu.org/licenses/gpl.html), Version 3 oder höher, oder der Creative Commons Attribution License (http://creativecommons.org/licenses/by/3.0/), Version 3.0 oder höher, verändern und/oder weitergeben.
Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt.
Fast alle Hardware- und Softwarebezeichnungen und weitere Stichworte und sonstige Angaben, die in diesem Buch verwendet werden, sind als eingetragene Marken geschützt.
Da es nicht möglich ist, in allen Fällen zeitnah zu ermitteln, ob ein Markenschutz besteht, wird das Symbol (R) in diesem Buch nicht verwendet.
Mitwirkende/Autoren
Robert Großkopf | Jost Lange | Jochen Schiffers |
Jürgen Thomas | Michael Niedermair |
|
Rückmeldung (Feedback)
Kommentare oder Vorschläge zu diesem Dokument können Sie in deutscher Sprache an die Adresse discuss@de.libreoffice.org senden.
Vorsicht
Alles, was an eine Mailingliste geschickt wird, inklusive der E-Mail-Adresse und anderer persönlicher Daten, die die E-Mail enthält, wird öffentlich archiviert und kann nicht gelöscht werden. Also, schreiben Sie mit Bedacht!
Datum der Veröffentlichung und Softwareversion
Veröffentlicht am 01.02.2024. Basierend auf der Version LibreOffice 24.2.
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 bereits der Start und das Ende für eine Prozedur angegeben:
1 REM ***** BASIC *****
2
3 Sub Main
4
5 End Sub
Um Makros, die dort eingegeben wurden, nutzen zu können, sind folgende Schritte notwendig:
•Unter Extras → Optionen → Sicherheit → Makrosicherheit ist der Sicherheitslevel → Mittel zu wählen. Gegebenenfalls kann auch zusätzlich unter Vertrauenswürdige Quellen → Vertrauenswürdige Speicherorte 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ß- und Kleinschreibung 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 mit festem Variablentyp (außer «Variant») erhalten; der wird einfach in der Definition zusätzlich festgelegt:
SUB myProcedure AS INTEGER
END SUB
Zu weiteren Details siehe auch das Handbuch «Erste Schritte Makros mit LibreOffice».
Hinweis
Makro-Bezeichner
Makro-Kommentar
Makro-Operator
Makro-Reservierter-Ausdruck
Makro-Zahl
Makro-Zeichenkette
Hinweis
Bezeichner können frei gewählt werden, sofern sie nicht einem reservierten Ausdruck entsprechen. Viele Makros sind in dieser Anleitung mit an die deutsche Sprache angelehnten Bezeichnern versehen. Dies führte bei der englischsprachigen Übersetzung allerdings zu zusätzlichen Problemen. Deshalb sind die Bezeichner in neueren Makros an die englische Sprache angelehnt.
Hinweis
Die hier aufgezeigten Makros sind nahezu ausschließlich innerhalb der Base-Datei gespeichert und dort auch getestet. So kann z.B. der Kontakt zu einer Datenbank mit ThisDatabaseDocument nur innerhalb einer Base-Datei hergestellt werden. Sollen die Makros außerhalb der Datei unter Meine Makros und Dialoge gespeichert werden, so kann eventuell statt ThisDatabaseDocument einfach ThisComponent zum Ziel führen. Es kann aber auch sein, dass dann bestimmte Methoden einfach nicht zur Verfügung stehen.
Der Objektkatalog auf der linken Seite zeigt alle zur Zeit verfügbaren Bibliotheken und darin Module an, die über ein Ereignis aufgerufen werden können. Meine Makros & Dialoge ist für alle Dokumente eines Benutzers verfügbar. LibreOffice Makros & Dialoge sind für alle Benutzer des Rechners und auch anderer Rechner nutzbar, da sie standardmäßig mit LibreOffice installiert werden. Hinzu kommen noch die Bibliotheken, die in dem jeweiligen Dokument, hier Medien_mit_Makros.odb, abgespeichert sind.
Prinzipiell ist es zwar möglich, aus allen verfügbaren Bibliotheken die Module und die darin liegenden Makros zu nutzen. Für eine sinnvolle Nutzung empfiehlt es sich aber nicht, Makros aus anderen Dokumenten zu nutzen, da diese eben nur bei Öffnung des entsprechenden Dokumentes verfügbar sind. Ebenso ist es nicht empfehlenswert, Bibliotheken aus «Meine Makros & Dialoge» einzubinden, wenn die Datenbankdatei auch an andere Nutzer weitergegeben werden soll. Ausnahmen können hier Erweiterungen («Extensions») sein, die dann mit der Datenbankdatei weiter gegeben werden.
In dem Eingabebereich wird aus dem Modul Aktualisierung die Prozedur Ausleihe_aktualisieren angezeigt. Eingegebene Zeilen enden mit einem Return. Groß- und Kleinschreibung sowie Einrückung des Codes sind in Basic beliebig. Lediglich der Verweis auf Zeichenketten, z.B. "Filter", muss genau der Schreibweise in dem dort gemeinten Formular entsprechen.
Makros können schrittweise für Testzwecke durchlaufen werden. Entsprechende Veränderungen der Variablen werden im Beobachter angezeigt.
Die Benennung von Modulen, Dialogen und Bibliotheken sollte erfolgen, bevor irgendein Makro in die Datenbank eingebunden wird. Sie definieren schließlich den Pfad, in dem das auslösende Ereignis nach dem Makro sucht.
Innerhalb einer Bibliothek kann auf alle Makros der verschiedenen Module zugegriffen werden. Sollen Makros anderer Bibliotheken genutzt werden, so müssen diese extra geladen werden:
1 GlobalScope.BasicLibraries.LoadLibrary("Tools")
lädt die Bibliothek «Tools», die eine Bibliothek von LibreOffice Makros ist.
Über Extras → Makros verwalten → LibreOffice Basic → Verwalten kann der obige Dialog aufgerufen werden. Hier können neue Module und Dialoge erstellt und mit einem Namen versehen werden. Die Namen können allerdings nicht hier, sondern nur in dem Makroeditor selbst verändert werden.
Im Makroeditor wird mit einem rechten Mausklick auf die Reiter mit der Modulbezeichnung direkt oberhalb der Suchleiste ein Kontextmenü geöffnet, das u.a. die Änderung des Modulnamens ermöglicht.
Neue Bibliotheken können innerhalb der Base-Datei angelegt werden. Die Bezeichnung «Standard» der ersten erstellten Bibliothek lässt sich nicht ändern. Die Namen der weiteren Bibliotheken sind frei wählbar, anschließend aber auch nicht änderbar. In eine Bibliothek können Makros aus anderen Bibliotheken importiert werden. Sollte also der dringende Wunsch bestehen, eine andere Bibliotheksbezeichnung zu erreichen, so müsste eine neue Bibliothek mit diesem Namen erstellt werden und sämtlicher Inhalt der alten Bibliothek in die neue Bibliothek exportiert werden. Dann kann anschließend die alte Bibliothek gelöscht werden.
Tipp
Bei der Bibliothek «Standard» ist es nicht möglich, ein Kennwort zu setzen. Sollen Makros vor den Blicken des normalen Nutzers verborgen bleiben, so muss dafür eine neue Bibliothek erstellt werden. Diese lässt sich dann mit einem Kennwort schützen.
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 der Objekte thisComponent (siehe den Abschnitt Zugriff auf das Formular) oder oEvent (siehe den Abschnitt Zugriff auf Elemente eines Formulars) benutzt wird.
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.
Maßnahmen, die beim Öffnen oder Schließen eines Formulardokuments erledigt werden sollen, werden so registriert:
•Rufen Sie im Formularentwurf über Extras → Anpassen das Register Ereignisse auf.
•Wählen Sie das passende Ereignis aus. Bestimmte Makros lassen sich nur starten, wenn Ansicht wurde erzeugt gewählt ist. Andere Makros wie z.B. das Erzeugen eines Vollbild-Formulars kann über Dokument öffnen gestartet werden.
•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 (hier: «Medien_mit_Makros : Ausleihe)».
Dann kann diese Zuweisung mit OK bestätigt werden.
Die Einbindung von Makros in das Datenbankdokument erfolgt auf dem gleichen Weg. Nur stehen hier teilweise andere Ereignisse zur Wahl.
Vorsicht
Beim Starten eines Datenbankdokumentes kann, wenn die Makrounterstützung nicht aktiviert wurde, die Nachfrage kommen, ob Makros ausgeführt werden. Wird in einem Datenbankdokument das Ereignis Ansicht wurde erzeugt gewählt, so wird dieses Ereignis nicht ausgeführt, auch wenn die Frage nach dem Ausführen der Makros bejaht wurde.
Das Ereignis Dokument öffnen wird hingegen ausgeführt. Soll also auf jeden Fall beim Start des Datenbankdokumentes ein Makro ausgeführt werden, so sollte dieses Ereignis gewählt werden.
Nachdem das Fenster für die gesamten Inhalte des Formulars (Formulardokument) geöffnet wurde, kann auf die einzelnen Elemente des Formulardokuments zugegriffen werden. Hierzu gehören auch die dem Formular zugeordneten Formularelemente.
Die Formularelemente können, wie in obigem Bild, über den Formularnavigator angesteuert werden. Sie sind genauso gut über jedes einzelne Kontrollfeld der Formularoberfläche über das Kontextmenü des Kontrollfeldes zu erreichen.
Die unter Formular-Eigenschaften → Ereignisse aufgezeigten Ereignisse finden statt während das Formularfenster geöffnet ist. Sie können für jedes Formular oder Unterformular des Formularfensters separat ausgewählt werden.
Hinweis
Der Gebrauch des Begriffes «Formular» ist bei Base leider nicht eindeutig. Der Begriff wird zum einen für das Fenster benutzt, das zur Eingabe von Daten geöffnet wird. Zum anderen wird der Begriff für das Element genutzt, das mit diesem Fenster eine bestimmte Datenquelle (Tabelle oder Abfrage) verbindet.
Es können auf einem Formularfenster sehr wohl mehrere Formulare mit unterschiedlichen Datenquelle untergebracht sein. Im Formularnavigator steht zuerst immer der Begriff «Formulare», dem dann in einem einfachen Formular lediglich ein Formular untergeordnet wird.
Das Formularfenster wird in dieser Dokumentation zur besseren Unterscheidung deshalb oft auch als «Formulardokument» bezeichnet.
Alle anderen Makros werden bei den Eigenschaften von Teilformularen und Kontrollfeldern über das 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.
In diesem Abschnitt sollen einige Teile der Makro-Sprache erläutert werden, die in Base – vor allem bei Formularen – immer wieder benutzt werden. (Soweit möglich und sinnvoll, werden dabei die Beispiele der folgenden Abschnitte benutzt.)
Die Definition eines Makros beginnt mit dem Typ des Makros – SUB oder FUNCTION – und endet mit END SUB bzw. END FUNCTION. Einem Makro, das einem Ereignis zugewiesen wird, können Argumente (Werte) übergeben werden; sinnvoll ist aber nur das Argument oEvent. Alle anderen Routinen, die von einem solchen Makro aufgerufen werden, können abhängig vom Zweck mit oder ohne Rückgabewert definiert werden und beliebig mit Argumenten versehen werden.
1 SUB Ausleihe_aktualisieren
2 END SUB
1 SUB Zu_Formular_von_Formular(oEvent AS OBJECT)
2 END SUB
1 FUNCTION Loeschen_bestaetigen(oEvent AS OBJECT) AS BOOLEAN
2 Loeschen_bestaetigen = FALSE
3 END FUNCTION
Es ist hilfreich, diesen Rahmen sofort zu schreiben und den Inhalt anschließend einzufügen. Bitte vergessen Sie nicht, Kommentare zur Bedeutung des Makros nach dem Grundsatz «so viel wie nötig, so wenig wie möglich» einzufügen. Außerdem unterscheidet Basic nicht zwischen Groß- und Kleinschreibung. In der Praxis werden feststehende Begriffe wie SUB vorzugsweise groß geschrieben, während andere Bezeichner Groß- und Kleinbuchstaben mischen.
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.
1 DIM oDoc AS OBJECT
2 DIM oDrawpage AS OBJECT
3 DIM oForm AS OBJECT
4 DIM sName AS STRING
5 DIM bOKEnabled AS BOOLEAN, iCounter AS INTEGER, dBirthday AS DATE
Die Deklaration von Variablen ist in Einzelzeilen oder zusammengefasst in einer Zeile, getrennt durch ein Komma, möglich.
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.
Die Liste der möglichen Datentypen in Star-Basic steht im Anhang des Handbuches. An verschiedenen Stellen sind Unterschiede zwischen der Datenbank, von Basic und der LibreOffice-API zu beachten. Darauf wird bei den Beispielen hingewiesen.
Gerade für Datenbanken ist die Sammlung von mehreren Variablen in einem Datensatz von Bedeutung. Werden mehrere Variablen zusammen in einer gemeinsamen Variablen gespeichert, so wird dies als ein Array bezeichnet. Ein Array muss definiert werden, bevor Daten in das Array geschrieben werden können.
1 DIM arDaten()
erzeugt eine leeres Array.
2 arDaten = array("Lisa","Schmidt")
So wird ein Array auf eine bestimmte Größe von 2 Elementen festgelegt und gleichzeitig mit Daten versehen.
Über
3 print arDaten(0), arDaten(1)
werden die beiden definierten Inhalte auf dem Bildschirm ausgegeben. Die Zählung für die Felder in Arrays beginnt hier mit 0.
1 DIM arDaten(2)
2 arDaten(0) = "Lisa"
3 arDaten(1) = "Schmidt"
4 arDaten(2) = "Köln"
Dies erstellt ein Array, in dem 3 Elemente beliebigen Typs gespeichert werden können, also z.B. ein Datensatz mit den Variablen «Lisa» «Schmidt» «Köln». Mehr passt leider in dieses Array nicht hinein. Sollen mehr Elemente gespeichert werden, so muss das Array vergrößert werden. Wird während der Laufzeit eines Makros allerdings die Größe eines Arrays einfach nur neu definiert, so ist das Array anschließend leer wie eben ein neues Array.
5 ReDIM Preserve arDaten(3)
6 arDaten(3) = "18.07.2003"
Durch den Zusatz Preserve werden die vorherigen Daten beibehalten, das Array also tatsächlich zusätzlich um die Datumseingabe, hier in Form eines Textes, erweitert.
Das oben aufgeführte Array kann leider nur einen einzelnen Satz an Daten speichern. Sollen stattdessen, wie in einer Tabelle einer Datenbank, mehrere Datensätze gespeichert werden, so muss dem Array zu Beginn eine zusätzliche Dimension hinzugefügt werden.
1 DIM arDaten(2,1)
2 arDaten(0,0) = "Lisa"
3 arDaten(1,0) = "Schmidt"
4 arDaten(2,0) = "Köln"
5 arDaten(0,1) = "Egon"
6 arDaten(1,1) = "Müller"
7 arDaten(2,1) = "Hamburg"
Auch hier gilt bei einer Erweiterung über das vorher definierte Maß hinaus, dass der Zusatz Preserve die vorher eingegebenen Daten mit übernimmt.
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:
1 oDoc = thisComponent
2 oDrawpage = oDoc.drawpage
3 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:
4 DIM oSubForm AS OBJECT
5 DIM oSubSubForm AS OBJECT
6 oSubForm = oForm.getByName("Leserauswahl")
7 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.)
6 oForm = thisComponent.drawpage.forms.getByName("Filter")
7 oSubSubForm = oForm.getByName("Leserauswahl").getByName("Leseranzeige")
Hinweis
6 oForm = thisComponent.drawpage.forms.Filter
7 oSubSubForm = oForm.Leserauswahl.Leseranzeige
Einen anderen Zugang zum Formular ermöglicht das auslösende Ereignis für das Makro.
Startet ein Makro über ein Ereignis des Formulars, wie z. B. Formular-Eigenschaften → Vor der Datensatzaktion, so wird das Formular selbst folgendermaßen erreicht:
1 SUB MakrobeispielBerechne(oEvent AS OBJECT)
2 oForm = oEvent.Source
3 ...
4 END SUB
Startet ein Makro über ein Ereignis eines Formularfeldes, wie z. B. Eigenschaften: Textfeld → Bei Fokusverlust, so kann sowohl das Formularfeld als auch das Formular ermittelt werden:
1 SUB MakrobeispielBerechne(oEvent AS OBJECT)
2 oFeld = oEvent.Source.Model
3 oForm = oFeld.Parent
4 ...
5 END SUB
Die Zugriffe über das Ereignis haben den Vorteil, dass kein Gedanke darüber verschwendet werden muss, ob es sich bei dem Formular um ein Hauptformular oder Unterformular handelt. Auch interessiert der Name des Formulars für die Funktionsweise des Makros nicht.
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:
1 DIM btnOK AS OBJECT ' Button »OK»
2 btnOK = oSubSubForm.getByName("Schaltfläche 1") ' aus dem Formular Leseranzeige
Dieser Weg funktioniert immer dann, wenn bekannt ist, mit welchem Element das Makro arbeiten soll. Wenn aber im ersten Schritt zu prüfen ist, welches Ereignis das Makro gestartet hat, ist der o.g. Weg über oEvent sinnvoll. Dann wird die Variable innerhalb des Makro-"Rahmens" deklariert und beim Start des Makros zugewiesen. Die Eigenschaft Source liefert immer dasjenige Element, das das Makro gestartet hat; die Eigenschaft Model beschreibt das Kontrollfeld im Einzelnen:
1 SUB Auswahl_bestaetigen(oEvent AS OBJECT)
2 DIM btnOK AS OBJECT
3 btnOK = oEvent.Source.Model
4 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.
Normalerweise wird der Zugriff auf die Datenbank über Formulare, Abfragen, Berichte oder die Serienbrief-Funktion geregelt, wie es in allen vorhergehenden Kapiteln beschrieben wurde. Wenn diese Möglichkeiten nicht genügen, kann ein Makro auch gezielt die Datenbank ansprechen, wofür es mehrere Wege gibt.
Das einfachste Verfahren benutzt dieselbe Verbindung wie das Formular, wobei oForm wie oben bestimmt wird:
1 DIM oConnection AS OBJECT
2 oConnection = oForm.activeConnection()
Oder man holt die Datenquelle, also die Datenbank, durch das Dokument und benutzt die vorhandene Verbindung auch für das Makro:
1 DIM oDatasource AS OBJECT
2 DIM oConnection AS OBJECT
3 oDatasource = thisComponent.Parent.dataSource
4 oConnection = oDatasource.getConnection("","")
Ein weiterer Weg stellt sicher, dass bei Bedarf die Verbindung zur Datenbank hergestellt wird:
1 DIM oDatasource AS OBJECT
2 DIM oConnection AS OBJECT
3 oDatasource = thisComponent.Parent.CurrentController
4 IF NOT (oDatasource.isConnected()) THEN oDatasource.connect()
5 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. Dazu muss das Makro innerhalb der Base-Datei gespeichert werden.
1 DIM oDatasource AS OBJECT
2 DIM oConnection AS OBJECT
3 oDatasource = thisDatabaseDocument.CurrentController
4 IF NOT (oDatasource.isConnected()) THEN oDatasource.connect()
5 oConnection = oDatasource.ActiveConnection()
Der Zugriff auf Datenbanken außerhalb der aktuellen Datenbank ist folgendermaßen möglich:
1 DIM oDatabaseContext AS OBJECT
2 DIM oDatasource AS OBJECT
3 DIM oConnection AS OBJECT
4 oDatabaseContext = createUnoService("com.sun.star.sdb.DatabaseContext")
5 oDatasource = oDatabaseContext.getByName("angemeldeter Name der Datenbank in LO")
6 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.
Die Arbeit mit der Datenbank erfolgt über SQL-Befehle. Ein solcher muss also erstellt und an die Datenbank geschickt werden; je nach Art des Befehls wird das Ergebnis ausgewertet und weiter verarbeitet. Mit der Anweisung createStatement wird das Objekt dafür erzeugt:
1 DIM oSQL_Statement AS OBJECT ' das Objekt, das den SQL-Befehl ausführt
2 DIM stSql AS STRING ' Text des eigentlichen SQL-Befehls
3 DIM oResult AS OBJECT ' Ergebnis für executeQuery
4 DIM iResult AS INTEGER ' Ergebnis für executeUpdate
5 oSQL_Statement = oConnection.createStatement()
Um Daten abzufragen, wird mit dem Befehl die Methode executeQuery aufgerufen und ausgeführt; das Ergebnis wird anschließend ausgewertet. Tabellennamen und Feldnamen werden üblicherweise in doppelte Anführungszeichen gesetzt. Diese müssen im Makro durch weitere doppelte Anführungszeichen maskiert werden, damit sie im Befehl erscheinen.
6 stSql = "SELECT * FROM ""Tabelle1"""
7 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.
8 stSql = "DROP TABLE ""Suchtmp"" IF EXISTS"
9 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.
Hinweis
1 oSQL_Statement = oConnection.createStatement()
2 oSQL_Statement.EscapeProcessing = False
In allen Fällen, in denen manuelle Eingaben der Benutzer in einen SQL-Befehl übernommen werden, ist es einfacher und sicherer, den Befehl nicht als lange Zeichenkette zu erstellen, sondern ihn vorzubereiten und mit Parametern zu benutzen. Das vereinfacht die Formatierung von Zahlen, Datumsangaben und auch Zeichenketten (die ständigen doppelten Anführungszeichen entfallen) und verhindert Datenverlust durch böswillige Eingaben.
Bei diesem Verfahren wird zunächst das Objekt für einen bestimmten SQL-Befehl erstellt und vorbereitet:
1 DIM oSQL_Statement AS OBJECT ' das Objekt, das den SQL-Befehl ausführt
2 DIM stSql AS STRING ' Text des eigentlichen SQL-Befehls
3 stSql = "UPDATE ""Verfasser"" " _
4 & "SET ""Nachname"" = ?, ""Vorname"" = ?" _
5 & "WHERE ""ID"" = ?"
6 oSQL_Statement = oConnection.prepareStatement(stSql)
Das Objekt wird mit prepareStatement erzeugt, wobei der SQL-Befehl bereits bekannt sein muss. Jedes Fragezeichen markiert eine Stelle, an der später – vor der Ausführung des Befehls – ein konkreter Wert eingetragen wird. Durch das «Vorbereiten» des Befehls stellt sich die Datenbank darauf ein, welche Art von Angaben – in diesem Fall zwei Zeichenketten und eine Zahl – vorgesehen ist. Die verschiedenen Stellen werden durch die Position (ab 1 gezählt) unterschieden.
Anschließend werden mit passenden Anweisungen die Werte übergeben und danach der SQL-Befehl ausgeführt. Die Werte werden hier aus Kontrollfeldern des Formulars übernommen, können aber auch aus anderen Makro-Elementen stammen oder im Klartext angegeben werden:
7 oSQL_Statement.setString(1, oTextfeld1.Text) ' Text für den Nachnamen
8 oSQL_Statement.setString(2, oTextfeld2.Text) ' Text für den Vornamen
9 oSQL_Statement.setLong(3, oZahlenfeld1.Value) ' Wert für die betreffende ID
10 iResult = oSQL_Statement.executeUpdate
Die vollständige Liste der Zuweisungen findet sich im Abschnitt Parameter für vorbereitete SQL-Befehle.
Es ist auch möglich, direkt mehrere Datensätze über einen vorbereiteten SQL-Befehl einzufügen:
11 stSql = "UPDATE ""Person"" " _
12 & "SET ""Name"" = ?" _
13 & "WHERE ""ID"" = ?"
14 oSQL_Statement = oConnection.prepareStatement(stSql)
15 oSQL_Statement.setString(1, "Bill")
16 oSQL_Statement.setLong(2, 1)
17 oSQL_Statement.addBatch()
18 oSQL_Statement.setString(1, "Michaela")
19 oSQL_Statement.setLong(2, 2)
20 oSQL_Statement.addBatch()
21 oSQL_Statement.executeBatch()
Mit clearBatch könnte der gesamte Inhalt vor der Ausführung auch wieder zurückgenommen werden.
Wer sich weiter über die Vorteile dieses Verfahrens informieren möchte, findet hier Erläuterungen:
•SQL-Injection (Wikipedia) (http://de.wikipedia.org/wiki/SQL-Injection)
•Why use PreparedStatement (Java JDBC) (http://javarevisited.blogspot.de/2012/03/why-use-preparedstatement-in-java-jdbc.html)
•SQL-Befehle (Einführung in SQL) (http://de.wikibooks.org/wiki/Einführung_in_SQL:_SQL-Befehle#Hinweis_f.C3.BCr_Programmierer:_Parameter_benutzen.21)
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 um dasjenige (Teil-) Formular, das mit einer bestimmten Datenmenge verbunden ist.
Der aktuelle Datensatz und seine Daten stehen immer über das Formular zur Verfügung, das die betreffende Datenmenge (Tabelle, Abfrage, Ansicht (View)) anzeigt. Dafür gibt es mehrere Methoden, die mit get und dem Datentyp bezeichnet sind, beispielsweise diese:
1 DIM ID AS LONG
2 DIM sName AS STRING
3 DIM dValue AS CURRENCY
4 DIM dEintritt AS NEW com.sun.star.util.Date
5 ID = oForm.getLong(1)
6 sName = oForm.getString(2)
7 dValue = oForm.getDouble(4)
8 dEintritt = oForm.getDate(7)
Bei allen diesen Methoden ist jeweils die Nummer der Spalte in der Datenmenge anzugeben – gezählt ab 1.
Hinweis
Bei allen Methoden, die mit Datenbanken arbeiten, wird ab 1 gezählt. Das gilt sowohl für Spalten als auch für Zeilen.
Soll anstelle der Spaltennummern mit den Spaltennamen der zugrundeliegenden Datenmenge (Tabelle, Abfrage, Ansicht (View)) gearbeitet werden, so kann die Spaltennummer über die Methode findColumn ermittelt werden. Hier ein Beispiel zum Auffinden der Spalte "Name":
1 DIM sName AS STRING
2 DIM nName AS STRING
3 nName = oForm.findColumn("Name")
4 sName = oForm.getString(nName)
Das Ergebnis ist immer ein Wert des Typs der Methode, wobei die folgenden Sonderfälle zu beachten sind.
•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» definiert sind. 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.
Tipp
1 SUB WerteAuslesen(oEvent AS OBJECT)
2 DIM oForm AS OBJECT
3 DIM stFeld1 AS STRING
4 oForm = oEvent.Source.Model.Parent
5 stFeld1 = oForm.getString(1)
6 END SUB
Hinweis
Grundsätzlich können alle Felder mit getString ausgelesen werden. Um sie in Makros weiter zu verarbeiten, müssen die erhaltenen Strings dann gegebenenfalls anschließend in die jeweiligen Variablen umgewandelt werden.
Das Auslesen mit getString hat den Vorteil, dass auch leere Felder einwandfrei ermittelt werden können. Werden die (passenden) Variablen stattdessen z.B. über getInt ausgelesen, so ergibt selbst ein leeres Feld '0'. Dies täuscht also vor, dass in dem Feld eben diese Zahl enthalten war. So etwas ist besonders lästig, wenn eben auch der Wert '0' selbst vorkommt – bei der internen Hsqldb z. B. beim automatisch hoch zählenden Schlüsselwert als Startwert.
Alternativ muss immer mit wasNull nachgesehen werden, ob denn der Inhalt vielleicht leer gewesen ist.
In gleicher Weise kann die Ergebnismenge einer Abfrage benutzt werden. Im Abschnitt SQL-Befehle steht die Variable oResult für diese Ergebnismenge, die üblicherweise so oder ähnlich ausgelesen wird:
1 WHILE oResult.next ' einen Datensatz nach dem anderen verarbeiten
2 rem übernimm die benötigten Werte in Variablen
3 stVar = oResult.getString(1)
4 inVar = oResult.getLong(2)
5 boVar = oResult.getBoolean(3)
6 rem mach etwas mit diesen Werten
7 WEND
Je nach Art des SQL-Befehls, dem erwarteten Ergebnis und dem Zweck kann vor allem die WHILE-Schleife verkürzt werden oder sogar entfallen. Aber grundsätzlich wird eine Ergebnismenge immer nach diesem Schema ausgewertet.
Soll nur der erste Datensatz ausgewertet werden, so wird mit
1 oResult.next
zuerst die Zeile auf diesen Datensatz bewegt und dann mit
2 stVar = oResult.getString(1)
z.B. der Inhalt des ersten Datenfeldes gelesen. Die Schleife entfällt hier.
3 IF wasNull THEN
4 ...
5 END IF
Mit dieser Nachfrage würde die gerade getätigte Abfrage zu stVar darauf überprüft, ob sie nach SQL-Standard NULL gewesen ist.
Die Abfrage zu dem obigen Beispiel hat in der ersten Spalte einen Text, in der zweiten Spalte einen Integer-Zahlenwert (Integer aus der Datenbank entspricht Long in Basic) und in der dritten Spalte ein Ja/Nein-Feld. Die Felder werden durch den entsprechenden Indexwert angesprochen. Der Index für die Felder beginnt hier, im Gegensatz zu der sonstigen Zählung bei Arrays, mit dem Wert '1'.
In dem so erstellten Ergebnis ist allerdings keine Navigation möglich. Nur einzelne Schritte zum nächsten Datensatz sind erlaubt. Um innerhalb der Datensätze navigieren zu können, muss der ResultSetType bei der Erstellung der Abfrage bekannt sein. Hierauf wird über
1 oSQL_Anweisung.ResultSetType = 1004
oder
1 oSQL_Anweisung.ResultSetType = 1005
zugegriffen. Der Typ 1004 - SCROLL_INTENSIVE erlaubt eine beliebige Navigation. Allerdings bleibt eine Änderung an den Originaldaten während des Auslesens unbemerkt. Der Typ 1005 – SCROLL_SENSITIVE berücksichtigt zusätzlich gegebenenfalls Änderungen an den Originaldaten, die das Abfrageergebnis beeinflussen könnten.
Soll zusätzlich in dem Ergebnissatz eine Änderung der Daten ermöglicht werden, so muss die ResultSetConcurrency vorher definiert werden. Die Update-Möglichkeit wird über
1 oSQL_Anweisung.ResultSetConcurrency = 1008
hergestellt. Der Typ 1007 - READ_ONLY ist hier die Standardeinstellung.
Die Anzahl der Zeilen, die die Ergebnismenge enthält, kann nur nach Wahl der entsprechenden Typen so bestimmt werden:
1 DIM iResult AS LONG
2 IF oResult.last THEN ' gehe zum letzten Datensatz, sofern möglich
3 iResult = oResult.getRow ' die laufende Nummer ist die Anzahl
4 ELSE
5 iResult = 0
6 END IF
Hinweis
Sollen viele Daten über die Schleife WHILE oResult.next ausgelesen werden, so macht sich hier ein Geschwindigkeitsunterschied bei verschiedenen Datenbanken deutlich bemerkbar. Die interne Firebird Datenbank arbeitet hier deutlich schneller als die interne Hsqldb. Selbst bei gleichen Datenbanken kann es abhängig vom Treiber zu deutlichen Unterschieden kommen. Bei der MariaDB mit direkter Verbindung ist die Geschwindigkeit auf dem Level der Firebird Datenbank. Mit der JDBC-Verbindung hingegen ist das Auslesen so langsam wie mit der Hsqldb.
Dieses Verhalten kann schon bei einer Abfrage getestet werden, wenn zum Ausführen die direkte SQL-Verbindung genutzt wird. Das Scrollen zum letzten Datensatz braucht bei vielen Zeilen deutlich länger mit der internen Hsqldb und MariaDB über die JDBC-Verbindung als mit der internen Firebird Datenbank und MariaDB mit der direkten Verbindung.
Wenn ein Kontrollfeld mit einer Datenmenge verbunden ist, kann der Wert auch direkt ausgelesen werden, wie es im nächsten Abschnitt beschrieben wird. Das ist aber teilweise mit Problemen verbunden. Sicherer ist – neben dem Verfahren Mithilfe des Formulars – der folgende Weg, der für verschiedene Kontrollfelder gezeigt wird:
1 sValue = oTextField.BoundField.Text ' Beispiel für ein Textfeld
2 nValue = oNumericField.BoundField.Value ' Beispiel für ein numerisches Feld
3 dValue = oDateField.BoundField.Date ' Beispiel für ein Datumsfeld
BoundField stellt dabei die Verbindung her zwischen dem (sichtbaren) Kontrollfeld und dem eigentlichen Inhalt der Datenmenge.
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:
1 DIM loRow AS LONG
2 loRow = oForm.getRow() ' notiere die aktuelle Zeilennummer
3 oForm.reload() ' lade die Datenmenge neu
4 oForm.absolute(loRow) ' gehe wieder zu der notierten Zeilennummer
Im Abschnitt In einer Datenmenge navigieren stehen alle dazu passenden Methoden.
Die Methode mit getRow() funktioniert aber nur dann einwandfrei, wenn es sich bei dem aktuellen Datensatz nicht um einen neuen Datensatz handelt. Deswegen hier eine weitere Möglichkeit der Navigation über den Bookmark-Befehl. Damit lässt sich der Cursor auch dann sicher positionieren, wenn bei einer neuen Zeile getRow() 0 ermittelt.
1 DIM var AS VARIANT
2 var = oForm.getBookmark()
3 loRow = oForm.getRow()
4 oForm.reload()
5 IF loRow = 0 THEN
6 oForm.MoveToInsertRow()
7 ELSE
8 oForm.MoveToBookmark(var)
9 END IF
Bei einem neuen Datensatz wird die Zeile mit '0' angegeben. In diesem Fall wird dann der Cursor nach dem Neuladen mit MoveToInsertRow auf diese Position gesetzt. Mit MoveToBookmark würde hier der Cursor auf die letzte Zeile mit Inhalt gesetzt.
Um Datensätze zu bearbeiten, müssen mehrere Teile zusammenpassen: Eine Information muss vom Anwender in das Kontrollfeld gebracht werden; das geschieht durch die Tastatureingabe. Anschließend muss die Datenmenge «dahinter» diese Änderung zur Kenntnis nehmen; das geschieht durch das Verlassen eines Feldes und den Wechsel zum nächsten Feld. Und schließlich muss die Datenbank selbst die Änderung erfahren; das erfolgt durch den Wechsel von einem Datensatz zu einem anderen.
Bei der Arbeit mit einem Makro müssen ebenfalls diese Teilschritte beachtet werden. Wenn einer fehlt oder falsch ausgeführt wird, gehen Änderungen verloren und «landen» nicht in der Datenbank. In erster Linie muss die Änderung nicht in der Anzeige des Kontrollfelds erscheinen, sondern in der Datenmenge. Es ist deshalb sinnlos, die Eigenschaft Text des Kontrollfelds zu ändern. Diese Eigenschaft dient nur zur Anzeige des Wertes.
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.
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:
1 DIM unoDate AS NEW com.sun.star.util.Date
2 unoDate.Year = Year(Date)
3 unoDate.Month = Month(Date)
4 unoDate.Day = Day(Date)
5 oDateField = oForm.getByName("Datum")
6 oDateField.BoundField.updateDate( unoDate )
7 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. Die entsprechende Erstellung des Datums kann auch durch die Formel CDateToUnoDate erreicht werden:
1 oDateField = oForm.getByName("Datum")
2 oDateField.BoundField.updateDate( CDateToUnoDate(NOW()) )
3 oForm.updateRow() ' Weitergabe der Änderung an die Datenbank
Wenn mehrere Werte in einer Zeile geändert werden sollen, ist der vorstehende Weg ungeeignet. Zum einen müsste für jeden Wert ein Kontrollfeld existieren, was oft nicht gewünscht oder sinnvoll ist. Zum anderen muss man sich für jedes dieser Felder ein Objekt «holen». Der einfache und direkte Weg geht über das Formular, beispielsweise so:
1 DIM unoDate AS NEW com.sun.star.util.Date
2 unoDate.Year = Year(Date)
3 unoDate.Month = Month(Date)
4 unoDate.Day = Day(Date)
5 oForm.updateDate(3, unoDate )
6 oForm.updateString(4, "ein Text")
7 oForm.updateDouble(6, 3.14)
8 oForm.updateInt(7, 16)
9 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.
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 eine neue 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()
4.Die Neuaufnahme kann nicht einfach rückgängig gemacht werden. Stattdessen ist die soeben neu angelegte Zeile wieder zu löschen.
Für das Löschen eines Datensatzes gibt es einen einfachen Befehl; es sind also folgende Maßnahmen nötig:
1.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()
Tipp
1 IF oForm.isNew THEN
2 oForm.insertRow()
3 ELSE
4 oForm.updateRow()
5 END IF
Neben dem Inhalt, der aus der Datenmenge kommt, können viele weitere Informationen zu einem Kontrollfeld gelesen, verarbeitet und geändert werden. Das betrifft vor allem die Eigenschaften, die im Kapitel «Formulare» aufgeführt werden. Eine Übersicht steht im Abschnitt Eigenschaften bei Formularen und Kontrollfeldern.
In mehreren Beispielen des Abschnitts Bedienbarkeit verbessern wird die Zusatzinformation eines Feldes benutzt:
1 SUB Main(oEvent AS OBJECT)
2 DIM stTag AS STRING
3 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:
1 SUB showWarning(oField AS OBJECT, iType AS INTEGER)
2 SELECT CASE iType
3 CASE 1
4 oField.TextColor = RGB(0,0,255) ' 1 = blau
5 CASE 2
6 oField.TextColor = RGB(255,0,0) ' 2 = rot
7 CASE ELSE
8 oField.TextColor = RGB(0,255,0) ' 0 = grün (weder 1 noch 2)
9 END SELECT
10 END SUB
Während der Formular-Designer in der deutschen Version auch deutsche Bezeichnungen für die Eigenschaften und den Datenzugriff verwendet, müssen in Basic englische Begriffe verwendet werden. Diese sind in den folgenden Übersichten aufgeführt.
Eigenschaften, die üblicherweise nur in der Formular-Definition festgelegt werden, stehen nicht in den Übersichten. Gleiches gilt für Methoden (Funktionen und Prozeduren), die nur selten verwendet werden oder für die kompliziertere Erklärungen nötig wären.
Die Übersichten nennen folgende Angaben:
•Name Bezeichnung der Eigenschaft oder Methode im Makro-Code
•Datentyp Einer der Datentypen von Basic
Bei Funktionen ist der Typ des Rückgabewerts angegeben; bei Prozeduren
entfällt diese Angabe.
•L/S Hinweis darauf, wie der Wert der Eigenschaft verwendet wird:
L nur Lesen
S nur Schreiben (Ändern)
(L) Lesen möglich, aber für weitere Verarbeitung ungeeignet
(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 .
1 SUB Main(oEvent AS OBJECT)
2 Xray(oEvent)
3 END SUB
Hiermit wird die Erweiterung Xray aus dem Aufruf heraus gestartet.
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».
Wenn mit
1 oFeld = oForm.getByName("Name des Kontrollfeldes")
auf ein Kontrollfeld zugegriffen wird, so werden die Eigenschaften einfach durch ein Anhängen an dieses Objekt mit einem Punkt als Verbinder angesprochen:
1 oFeld.FontHeight = 16
definiert also z. B. die Schriftgröße in 16 Punkten.
In jedem Kontrollfeld, das Text anzeigt, können die Eigenschaften der Schrift angepasst werden.
Name | Datentyp | L/S | Eigenschaft |
FontName | string | L+S | Schriftart. |
FontHeight | single | L+S | Schriftgröße. |
FontWeight | single | L+S | Schriftstärke. |
FontSlant | integer | L+S | Art der Schrägstellung. |
FontUnderline | integer | L+S | Art der Unterstreichung. |
FontStrikeout | integer | L+S | Art des Durchstreichens. |
Englische Bezeichnung: Form
Name | Datentyp | L/S | Eigenschaft |
ApplyFilter | boolean | L+S | Filter aktiviert. |
Filter | string | L+S | Aktueller Filter für die Datensätze. |
FetchSize | long | L+S | Anzahl der Datensätze, die «am Stück» geladen werden. |
Row | long | L | Nummer der aktuellen Zeile. |
RowCount | long | L | Anzahl der Datensätze. Entspricht der Anzeige der Gesamtdatensätze in der Navigationsleiste. Da nicht direkt alle Datensätze über FetchSize in den Cache gelesen werden steht hier z.B. '41*', obwohl die Tabelle deutlich mehr Datensätze hat. RowCount gibt dann leider auch nur '41' aus. |
Englische Bezeichnung: Control – siehe auch FormComponent
Name | Datentyp | L/S | Eigenschaft |
Name | string | L+(S) | Bezeichnung für das Feld. |
Enabled | boolean | L+S | Aktiviert: Feld kann ausgewählt werden. |
EnableVisible | boolean | L+S | Sichtbar: Feld wird dargestellt. |
ReadOnly | boolean | L+S | Nur lesen: Inhalt kann nicht geändert werden. |
TabStop | boolean | L+S | Feld ist in der Tabulator-Reihenfolge erreichbar. |
Align | integer | L+S | Horizontale Ausrichtung: |
BackgroundColor | long | L+S | Hintergrundfarbe. |
Tag | string | L+S | Zusatzinformation. |
HelpText | string | L+S | Hilfetext als «Tooltip». |
Name | Datentyp | L/S | Eigenschaft |
Text | string | (L+S) | Inhalt des Feldes aus der Anzeige. Bei Textfeldern nach dem Lesen auch zur weiteren Verarbeitung geeignet, andernfalls nur in Ausnahmefällen. |
Spin | boolean | L+S | Drehfeld eingeblendet (bei formatierten Feldern). |
TextColor | long | L+S | Textfarbe. |
DataField | string | L | Name des Feldes aus der Datenmenge |
BoundField | object | L | Objekt, das die Verbindung zur Datenmenge herstellt und vor allem dem Zugriff auf den Feldinhalt dient. |
Englische Bezeichnung: TextField
Name | Datentyp | L/S | Eigenschaft |
String | string | L+S | Inhalt des Feldes aus der Anzeige. |
MaxTextLen | integer | L+S | Maximale Textlänge. |
DefaultText | string | L+S | Standardtext. |
MultiLine | boolean | L+S | Mehrzeilig oder einzeilig. |
EchoChar | (integer) | L+S | Zeichen für Kennwörter (Passwort-Eingabe verstecken). |
Englische Bezeichnung: NumericField
Name | Datentyp | L/S | Eigenschaft |
ValueMin | double | L+S | Minimalwert zur Eingabe. |
ValueMax | double | L+S | Maximalwert zur Eingabe. |
Value | double | L+(S) | Aktueller Wert |
ValueStep | double | L+S | Intervall bei Verwendung mit Mausrad oder Drehfeld. |
DefaultValue | double | L+S | Standardwert. |
DecimalAccuracy | integer | L+S | Nachkommastellen. |
ShowThousandsSeparator | boolean | L+S | Tausender-Trennzeichen anzeigen. |
Englische Bezeichnung: DateField
Datumswerte werden als Datentyp long definiert und im ISO-Format YYYYMMDD angezeigt, also 20120304 für den 04.03.2012. Zur Verwendung dieses Typs zusammen mit getDate und updateDate sowie dem Typ com.sun.star.util.Date verweisen wir auf die Beispiele.
Name | Daten-typ | Datentyp ab LO 4.1.1 | L/S | Eigenschaft |
DateMin | long | com.sun.star.util.Date | L+S | Minimalwert zur Eingabe. |
DateMax | long | com.sun.star.util.Date | L+S | Maximalwert zur Eingabe. |
Date | long | com.sun.star.util.Date | L+(S) | Aktueller Wert |
DateFormat | integer |
| L+S | Datumsformat nach Festlegung des Betriebssystems: |
DefaultDate | long | com.sun.star.util.Date | L+S | Standardwert. |
DropDown | boolean |
| L+S | Aufklappbaren Monatskalender anzeigen. |
Englische Bezeichnung: TimeField
Auch Zeitwerte werden als Datentyp long definiert.
Name | Daten-typ | Datentyp ab LO 4.1.1 | L/S | Eigenschaft |
TimeMin | long | com.sun.star.util.Time | L+S | Minimalwert zur Eingabe. |
TimeMax | long | com.sun.star.util.Time | L+S | Maximalwert zur Eingabe. |
Time | long | com.sun.star.util.Time | L+(S) | Aktueller Wert |
TimeFormat | integer |
| L+S | Zeitformat: |
DefaultTime | long | com.sun.star.util.Time | L+S | Standardwert. |
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. |
Englische Bezeichnung: FormattedControl
Ein formatiertes Feld wird wahlweise für Zahlen, Währungen oder Datum/Zeit verwendet. Sehr viele der bisher genannten Eigenschaften gibt es auch hier, aber mit anderer Bezeichnung.
Name | Datentyp | L/S | Eigenschaft |
CurrentValue | variant | L | Aktueller Wert des Inhalts; der konkrete Datentyp hängt vom Inhalt des Feldes und dem Format ab. |
EffectiveValue | L+(S) | ||
EffectiveMin | double | L+S | Minimalwert zur Eingabe. |
EffectiveMax | double | L+S | Maximalwert zur Eingabe. |
EffectiveDefault | variant | L+S | Standardwert. |
FormatKey | long | L+(S) | Format für Anzeige und Eingabe. Es gibt kein einfaches Verfahren, das Format durch ein Makro zu ändern. |
EnforceFormat | boolean | L+S | Formatüberprüfung: Bereits während der Eingabe sind nur zulässige Zeichen und Kombinationen möglich. |
Englische Bezeichnung: ListBox
Der Lese- und Schreibzugriff auf den Wert, der hinter der ausgewählten Zeile steht, ist etwas umständlich, aber möglich.
Name | Datentyp | L/S | Eigenschaft |
ListSource | array of string | L+S | Datenquelle: Herkunft der Listeneinträge oder Name der Datenmenge, die die Einträge liefert. |
ListSourceType | integer | L+S | Art der Datenquelle: |
StringItemList | array of string | L | Listeneinträge, die zur Auswahl zur Verfügung stehen. |
ItemCount | integer | L | Anzahl der vorhandenen Listeneinträge. |
ValueItemList | array of string | L | Liste der Werte, die über das Formular an die Tabelle weitergegeben werden. |
DropDown | boolean | L+S | Aufklappbar. |
LineCount | integer | L+S | Anzahl der angezeigten Zeilen im aufgeklappten Zustand. |
MultiSelection | boolean | L+S | Mehrfachselektion vorgesehen. |
SelectedItems | array of integer | L+S | Liste der ausgewählten Einträge, und zwar als Liste der Positionen in der Liste aller Einträge. |
Das (erste) ausgewählte Element aus dem Listenfeld erhält man auf diesem Weg:
1 oControl = oForm.getByName("Name des Listenfelds")
2 sEintrag = oControl.ValueItemList( oControl.SelectedItems(0) )
Hinweis
1 oControl = oForm.getByName("Name des Listenfelds")
2 iD = oControl.getCurrentValue()
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:
1 SUB Listenfeldfilter
2 DIM stSql(0) AS STRING
3 DIM oDoc AS OBJECT
4 DIM oDrawpage AS OBJECT
5 DIM oForm AS OBJECT
6 DIM oFeld AS OBJECT
7 oDoc = thisComponent
8 oDrawpage = oDoc.drawpage
9 oForm = oDrawpage.forms.getByName("MainForm")
10 oFeld = oForm.getByname("Listenfeld")
11 stSql(0) = "SELECT ""Name"", ""ID"" FROM ""Filter_Name"" ORDER BY ""Name"""
12 oFeld.ListSource = stSql
13 oFeld.refresh
14 END SUB
Hinweis
1 SUB Kontofilter_Feldstart(oEvent AS OBJECT)
2 DIM oFeld AS OBJECT
3 DIM inID AS INTEGER
4 oFeld = oEvent.Source.Model
5 inID = oFeld.ValueItemList(oEvent.Selected)
6 ...
7 END SUB
Englische Bezeichnung: ComboBox
Trotz ähnlicher Funktionalität wie beim Listenfeld weichen die Eigenschaften teilweise ab.
Hier verweisen wir ergänzend auf das Beispiel Kombinationsfelder als Listenfelder mit Eingabemöglichkeit.
Name | Datentyp | L/S | Eigenschaft |
Autocomplete | boolean | L+S | Automatisch füllen. |
StringItemList | array of string | L+S | Listeneinträge, die zur Auswahl zur Verfügung stehen. |
ItemCount | integer | L | Anzahl der vorhandenen Listeneinträge. |
DropDown | boolean | L+S | Aufklappbar. |
LineCount | integer | L+S | Anzahl der angezeigten Zeilen im aufgeklappten Zustand. |
Text | string | L+S | Aktuell angezeigter Text. |
DefaultText | string | L+S | Standardeintrag. |
ListSource | string | L+S | Name der Datenquelle, die die Listeneinträge liefert. |
ListSourceType | integer | L+S | Art der Datenquelle; gleiche Möglichkeiten wie beim Listenfeld (nur die Auswahl «Werteliste» wird ignoriert). |
Englische Bezeichnungen: CheckBox (Markierfeld) bzw. RadioButton (Optionsfeld; auch «Option Button» möglich)
Name | Datentyp | L/S | Eigenschaft |
Label | string | L+S | Titel (Beschriftung) |
State | short | L+S | Status |
MultiLine | boolean | L+S | Wortumbruch (bei zu langem Text). |
RefValue | string | L+S | Referenzwert |
Englische Bezeichnung: PatternField
Neben den Eigenschaften für «einfache» Textfelder sind folgende interessant.
Name | Datentyp | L/S | Eigenschaft |
EditMask | string | L+S | Eingabemaske. |
LiteralMask | string | L+S | Zeichenmaske. |
StrictFormat | boolean | L+S | Formatüberprüfung bereits während der Eingabe. |
Englische Bezeichnung: GridControl
Name | Datentyp | L/S | Eigenschaft |
Count | long | L | Anzahl der Spalten. |
ElementNames | array of string | L | Liste der Spaltennamen. |
HasNavigationBar | boolean | L+S | Navigationsleiste vorhanden. |
RowHeight | long | L+S | Zeilenhöhe. |
Englische Bezeichnung: FixedText – auch Label ist üblich
Name | Datentyp | L/S | Eigenschaft |
Label | string | L+S | Der angezeigte Text. |
MultiLine | boolean | L+S | Wortumbruch (bei zu langem Text). |
Englische Bezeichnung: GroupBox
Keine Eigenschaft dieses Kontrollfelds wird üblicherweise durch Makros bearbeitet. Wichtig ist der Status der einzelnen Optionsfelder.
Englische Bezeichnungen: CommandButton – für die grafische Schaltfläche ImageButton
Name | Datentyp | L/S | Eigenschaft |
Label | string | L+S | Titel – Text der Beschriftung. |
State | short | L+S | Standardstatus «ausgewählt» bei «Umschalten». |
MultiLine | boolean | L+S | Wortumbruch (bei zu langem Text). |
DefaultButton | boolean | L+S | Standardschaltfläche |
Englische Bezeichnung: NavigationBar
Weitere Eigenschaften und Methoden, die mit der Navigation zusammenhängen – z.B. Filter und das Ändern des Datensatzzeigers –, werden über das Formular geregelt.
Name | Datentyp | L/S | Eigenschaft |
IconSize | short | L+S | Symbolgröße. |
boolean | L+S | Positionierung anzeigen und eingeben. | |
ShowNavigation | boolean | L+S | Navigation ermöglichen. |
ShowRecordActions | boolean | L+S | Datensatzaktionen ermöglichen. |
boolean | L+S | Filter und Sortierung ermöglichen. |
Die Datentypen der Parameter werden durch Kürzel angedeutet:
•c Nummer der Spalte des gewünschten Feldes in der Datenmenge – ab 1 gezählt
•n numerischer Wert – je nach Situation als ganze Zahl oder als Dezimalzahl
•s Zeichenkette (String); die maximale Länge ergibt sich aus der Tabellendefinition
•b boolean (Wahrheitswert) – true (wahr) oder false (falsch)
•d Datumswert
Diese Methoden gelten sowohl für ein Formular als auch für die Ergebnismenge einer Abfrage.
Mit «Cursor» ist in den Beschreibungen der Datensatzzeiger gemeint.
Name | Datentyp | Beschreibung |
Prüfungen für die Position des Cursors | ||
isBeforeFirst | boolean | Der Cursor steht vor der ersten Zeile, wenn der Cursor nach dem Einlesen noch nicht gesetzt wurde. |
isFirst | boolean | Gibt an, ob der Cursor auf der ersten Zeile steht. |
isLast | boolean | Gibt an, ob der Cursor auf der letzten Zeile steht. |
isAfterLast | boolean | Der Cursor steht hinter der letzten Zeile, wenn er von der letzten Zeile aus mit next weiter gesetzt wurde. |
getRow | long | Nummer der aktuellen Zeile |
Setzen des Cursors | ||
beforeFirst | – | Wechselt vor die erste Zeile. |
first | boolean | Wechselt zur ersten Zeile. |
previous | boolean | Geht um eine Zeile zurück. |
next | boolean | Geht um eine Zeile vorwärts. |
last | boolean | Wechselt zur letzten Zeile. |
afterLast | – | Wechselt hinter die letzte Zeile. |
absolute(n) | boolean | Geht zu der Zeile mit der angegebenen Nummer. |
relative(n) | boolean | Geht um eine bestimmte Anzahl von Zeilen weiter: |
Maßnahmen zum Status der aktuellen Zeile | ||
refreshRow | – | Liest die Werte der aktuellen Zeile neu ein. Nach dem Abspeichern der Zeile sind das auch die aktuellen Werte. |
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. |
Die Methoden zum Lesen stehen bei jedem Formular und bei einer Ergebnismenge zur Verfügung. Die Methoden zum Ändern und Speichern gibt es nur bei einer Datenmenge, die geändert werden kann (in der Regel also nur bei Tabellen, nicht bei Abfragen).
Name | Datentyp | Beschreibung |
Maßnahmen für die ganze Zeile | ||
insertRow | – | Speichert eine neue Zeile. |
updateRow | – | Bestätigt Änderungen der aktuellen Zeile. |
deleteRow | – | Löscht die aktuelle Zeile. |
cancelRowUpdates | – | Macht Änderungen der aktuellen Zeile rückgängig. |
moveToInsertRow | – | Wechselt den Cursor in die Zeile für einen neuen Datensatz. |
moveToCurrentRow | – | Kehrt nach der Eingabe eines neuen Datensatzes zurück zur vorherigen Zeile. |
Name | Datentyp | Beschreibung |
Werte lesen | ||
getString(c) | string | Liefert den Inhalt der Spalte als Zeichenkette. Kann auch zum Auslesen aller anderen Spalten genutzt werden und hat den Vorteil, dass leere Felder direkt bestimmt werden können. Strings können immer noch später in andere Datentypen umgewandelt werden. |
getBoolean(c) | boolean | Liefert den Inhalt der Spalte als Wahrheitswert. |
getByte(c) | byte | Liefert den Inhalt der Spalte als einzelnes Byte. |
getShort(c) | short | Liefert den Inhalt der Spalte als ganze Zahl. |
getInt(c) | integer | |
getLong(c) | long | |
getFloat(c) | float | Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit. |
getDouble(c) | double | Liefert den Inhalt der Spalte als Dezimalzahl von doppelter Genauigkeit. – Wegen der automatischen Konvertierung durch Basic ist dies auch für decimal- und currency-Werte geeignet. |
getBytes(c) | array of bytes | Liefert den Inhalt der Spalte als Folge einzelner Bytes. |
getDate(c) | Date | Liefert den Inhalt der Spalte als Datumswert. |
getTime(c) | Time | Liefert den Inhalt der Spalte als Zeitwert. |
getTimestamp(c) | DateTime | Liefert den Inhalt der Spalte als Zeitstempel (Datum und Zeit). |
In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Für den Zugriff auf die Datenmenge gibt es verschiedene Datentypen: com.sun.star.util.Date für ein Datum, com.sun.star.util.Time für eine Zeit, com.sun.star.util.DateTime für einen Zeitstempel. | ||
getBinaryStream(c) | Object | Liest den Inhalt eines Binärfeldes (z.B. Bild) aus. So ein Inhalt könnte als Datei gespeichert werden. |
wasNull | boolean | Gibt an, ob der Wert der zuletzt gelesenen Spalte NULL war. Beim Auslesen z.B. mit getInt wird sonst für ein leeres Feld grundsätzlich '0' weitergegeben. |
Werte speichern | ||
updateNull(c) | – | Setzt den Inhalt der Spalte c auf NULL. |
updateBoolean(c,b) | – | Setzt den Inhalt der Spalte c auf den Wahrheitswert b. |
updateByte(c,x) | – | Speichert in Spalte c das angegebene Byte x. |
updateShort(c,n) | – | Speichert in Spalte c die angegebene ganze Zahl n. |
updateInt(c,n) | – | |
updateLong(c,n) | – | |
updateFloat(c,n) | – | Speichert in Spalte c die angegebene Dezimalzahl n. |
updateDouble(c,n) | – | |
updateString(c,s) | – | Speichert in Spalte c die angegebene Zeichenkette s. |
updateBytes(c,x) | – | Speichert in Spalte c das angegebene Byte-Array x. |
updateDate(c,d) | – | Speichert in Spalte c das angegebene Datum d. |
updateTime(c,d) | – | Speichert in Spalte c den angegebenen Zeitwert d. |
updateTimestamp(c,d) | – | Speichert in Spalte c den angegeb. Zeitstempel d. |
Mit diesen Methoden wird über BoundField aus einem Kontrollfeld der Inhalt der betreffenden Spalte gelesen oder geändert. Diese Methoden entsprechen fast vollständig denen im vorigen Abschnitt; die Angabe der Spalte entfällt.
Name | Datentyp | Beschreibung |
Werte lesen | ||
getString | string | Liefert den Inhalt der Spalte als Zeichenkette. |
getBoolean | boolean | Liefert den Inhalt der Spalte als Wahrheitswert. |
getByte | byte | Liefert den Inhalt der Spalte als einzelnes Byte. |
getShort | short | Liefert den Inhalt der Spalte als ganze Zahl. |
getInt | integer | |
getLong | long | |
getFloat | float | Liefert den Inhalt der Spalte als Dezimalzahl von einfacher Genauigkeit. |
getDouble | double | Liefert den Inhalt der Spalte als Dezimalzahl von doppelter Genauigkeit. – Wegen der automatischen Konvertierung durch Basic ist dies auch für decimal- und currency-Werte geeignet. |
getBytes | array of bytes | Liefert den Inhalt der Spalte als Folge einzelner Bytes. |
getDate | Date | Liefert den Inhalt der Spalte als Datumswert. |
getTime | Time | Liefert den Inhalt der Spalte als Zeitwert. |
getTimestamp | DateTime | Liefert den Inhalt der Spalte als Zeitstempel (Datum und Zeit). |
In Basic selbst werden Datums- und Zeitwerte einheitlich mit dem Datentyp DATE verarbeitet. Für den Zugriff auf die Datenmenge gibt es verschiedene Datentypen: com.sun.star.util.Date für ein Datum, com.sun.star.util.Time für eine Zeit, com.sun.star.util.DateTime für einen Zeitstempel. | ||
getBinaryStream(c) | Object | Liest den Inhalt eines Binärfeldes (z.B. Bild) aus. So ein Inhalt könnte als Datei gespeichert werden. |
wasNull | boolean | Gibt an, ob der Wert der zuletzt gelesenen Spalte NULL war. |
Werte speichern | ||
updateNull | – | Setzt den Inhalt der Spalte auf NULL. |
updateBoolean(b) | – | Setzt den Inhalt der Spalte auf den Wahrheitswert b. |
updateByte(x) | – | Speichert in der Spalte das angegebene Byte x. |
updateShort(n) | – | Speichert in der Spalte die angegebene ganze Zahl n. |
updateInt(n) | – | |
updateLong(n) | – | |
updateFloat(n) | – | Speichert in der Spalte die angegebene Dezimalzahl n. |
updateDouble(n) | – | |
updateString(s) | – | Speichert in der Spalte die angegebene Zeichenkette s. |
updateBytes(x) | – | Speichert in der Spalte das angegebene Byte-Array x. |
updateDate(d) | – | Speichert in der Spalte das angegebene Datum d. |
updateTime(d) | – | Speichert in der Spalte den angegebenen Zeitwert d. |
updateTimestamp(d) | – | Speichert in der Spalte den angegebenen Zeitstempel d. |
Die Methoden, mit denen die Werte einem vorbereiteten SQL-Befehl – siehe Vorbereitete SQL-Befehle mit Parametern – übergeben werden, sind ähnlich denen der vorigen Abschnitte. Der erste Parameter – mit i bezeichnet – nennt seine Nummer (Position) innerhalb des SQL-Befehls.
Name | Datentyp | Beschreibung |
setNull(i, n) | – | Setzt den Inhalt der Spalte auf NULL |
setBoolean(i, b) | – | Fügt den angegebenen Wahrheitswert b in den SQL-Befehl ein. |
setByte(i, x) | – | Fügt das angegebene Byte x in den SQL-Befehl ein. |
setShort(i, n) | – | Fügt die angegebene ganze Zahl n in den SQL-Befehl ein. |
setInt(i, n) | ||
setLong(i, n) | ||
setFloat(i, n) | – | Fügt die angegebene Dezimalzahl n in den SQL-Befehl ein. |
setDouble(i, n) | ||
setString(i, s) | – | Fügt die angegebene Zeichenkette s in den SQL-Befehl ein. |
setBytes(i, x) | – | Fügt das angegebene Byte-Array x in den SQL-Befehl ein. |
setDate(i, d) | – | Fügt das angegebene Datum d in den SQL-Befehl ein. |
setTime(i, d) | – | Fügt den angegebenen Zeitwert d in den SQL-Befehl ein. |
setTimestamp(i, d) | – | Fügt den angegebenen Zeitstempel d in den SQL-Befehl ein. |
clearParameters | – | Entfernt die bisherigen Werte aller Parameter eines SQL-Befehls. |
Über den Makrorekorder können die Befehle ausgelesen werden, die z. B. mit den Buttons aus der Navigationsleiste der Formulare verbunden sind. Diese Befehle haben häufig eine umfassendere Funktion als die Funktionen, die sonst für Makros vorgesehen sind.
1 SUB FormularNeuLadenKontrollfelderAktualisieren
2 DIM oDocument AS OBJECT
3 DIM oDispatcher AS OBJECT
4 DIM Array()
5 oDocument = ThisComponent.CurrentController.Frame
6 oDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
7 oDispatcher.executeDispatch(oDocument, ".uno:Refresh", "", 0, Array())
8 END SUB
Über den Dispatcher wird das Formular neu geladen und die Formularfelder aktualisiert. Würde nur das Formular über oForm.reload() neu eingelesen, nachdem aus einem anderen Formular heraus der Inhalt eines Listenfeldes geändert wurde, so würde die Änderung in dem Formular nicht angezeigt. Hier müsste auch noch jedes einzelne Feld mit oFeld.refresh() neu eingelesen werden.
Auch das Sichern eines Datensatz ist über .uno:RecSave einfacher als mittels der direkten Ansprache des Formulars. Bei der direkten Ansprache des Formulars muss erst geklärt werden, ob es sich um einen neuen Datensatz handelt, für den dann ein Insert durchgeführt wird, oder ob es sich um einen bestehenden Datensatz handelt, der dann ein Update erfordert.
Eine Übersicht über verschiedene UNO-Befehle befindet sich im Anhang des Handbuches. UNO-Befehle können auch z. B. über Extras → Anpassen → Symbolleisten → Beschreibung ermittelt werden.
Als erste Kategorie werden verschiedene Möglichkeiten vorgestellt, die zur Verbesserung der Bedienbarkeit von Base-Formularen dienen. Sofern nicht anders erwähnt, sind diese Makros Bestandteil der Beispieldatenbank «Medien_mit_Makros.odb».
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.
1 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 durchgeführt, die nach außen in der Regel keinen Wert zurück gibt. Weiter unten wird im Gegensatz dazu einmal eine Funktion beschrieben, die im Unterschied dazu Rückgabewerte erzeugt.
Das Makro hat jetzt den Namen «Aktualisieren». Um sicher zu gehen, dass keine Variablen von außen eingeschleust werden, gehen viele Programmierer so weit, dass sie Basic über Option Explicit gleich zu Beginn mitteilen: Erzeuge nicht automatisch irgendwelche Variablen, sondern nutze nur die, die ich auch vorher definiert habe.
Deshalb werden jetzt standardmäßig 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.
2 DIM oDoc AS OBJECT
3 DIM oDrawpage AS OBJECT
4 DIM oForm AS OBJECT
Das Formular liegt in dem momentan aktiven Dokument. Der Behälter, in dem alle Formulare aufbewahrt werden, wird als Drawpage bezeichnet. Im Formularnavigator ist dies sozusagen der oberste Begriff, an den dann sämtliche Formulare angehängt werden.
Das Formular, auf das zugegriffen werden soll, ist hier mit den Namen "Anzeige" versehen. Dies ist der Name, der auch im Formularnavigator sichtbar ist. So hat z.B. das erste Formular standardmäßig erst einmal den Namen "MainForm".
5 oDoc = thisComponent
6 oDrawpage = oDoc.Drawpage
7 oForm = oDrawpage.forms.getByName("Anzeige")
Nachdem das Formular jetzt ansprechbar ist und der Punkt, an dem es angesprochen wurde, in der Variablen oForm gespeichert wurde, wird es jetzt mit dem Befehl reload() neu geladen.
8 oForm.reload()
9 END SUB
Die Prozedur hat mit SUB begonnen. Sie wird mit END SUB beendet.
Dieses Makro kann jetzt z.B. ausgelöst werden, wenn die Abspeicherung in einem anderen Formular erfolgt. Wird z.B. in einem Kassenformular an einer Stelle die Anzahl der Gegenstände und (über Barcodescanner) die Nummer eingegeben, so kann in einem anderen Formular im gleichen geöffneten Fenster hierdurch der Kassenstand, die Bezeichnung der Ware usw. nach dem Abspeichern sichtbar gemacht werden.
1 SUB Filter
2 DIM oDoc AS OBJECT
3 DIM oDrawpage AS OBJECT
4 DIM oForm1 AS OBJECT
5 DIM oForm2 AS OBJECT
6 DIM oFeldList1 AS OBJECT
7 DIM oFeldList2 AS OBJECT
8 oDoc = thisComponent
9 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.
10 oForm1 = oDrawpage.forms.getByName("Filter")
11 oForm2 = oDrawpage.forms.getByName("Anzeige")
12 oFeldList1 = oForm1.getByName("Liste_1")
13 oFeldList2 = oForm1.getByName("Liste_2")
Zuerst wird der Inhalt der Listenfelder an das darunterliegende Formular mit commit() weitergegeben. Die Weitergabe ist notwendig, da ansonsten die Änderung eines Listenfeldes bei der Speicherung nicht berücksichtigt wird. Genau genommen müsste der commit() nur auf dem Listenfeld ausgeführt werden, das gerade betätigt wurde. Danach wird der Datensatz mit updateRow() abgespeichert. Es existiert ja in unserer Filtertabelle prinzipiell nur ein Datensatz, und der wird zu Beginn einmal geschrieben. Dieser Datensatz wird also laufend durch ein Update-Kommando überschrieben.
14 oFeldList1.commit()
15 oFeldList2.commit()
16 oForm1.updateRow()
Die Listenfelder sollen einander beeinflussen. Wird in einem Listenfeld z.B. eingegrenzt, dass an Medien nur CDs angezeigt werden sollen, so muss das andere Listenfeld bei den Autoren nicht noch sämtliche Buchautoren auflisten. Eine Auswahl im 2. Listenfeld hätte dann allzu häufig ein leeres Filterergebnis zur Folge. Daher müssen die Listenfelder jetzt neu eingelesen werden. Genau genommen müsste der refresh() nur auf dem Listenfeld ausgeführt werden, das gerade nicht betätigt wurde.
Anschließend wird das Formular2, das den gefilterten Inhalt anzeigen soll, neu geladen.
17 oFeldList1.refresh()
18 oFeldList2.refresh()
19 oForm2.reload()
20 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.
1 SELECT
2 "Feld_1" || ' - ' || "Anzahl" AS "Anzeige",
3 "Feld_1"
4 FROM
5 ( SELECT COUNT( "ID" ) AS "Anzahl", "Feld_1" FROM "Suchtabelle"
GROUP BY "Feld_1" )
6 ORDER BY "Feld_1"
Es wird der Feldinhalt und die Trefferzahl angezeigt. Um die Trefferzahl zu errechnen, wird eine Unterabfrage gestellt. Dies ist notwendig, da sonst nur die Trefferzahl ohne weitere Information aus dem Feld in der Listbox angezeigt würde.
Das Makro erzeugt durch dieses Vorgehen ganz schnell Listboxen, die nur noch mit einem Wert gefüllt sind. Steht eine Listbox nicht auf NULL, so wird sie schließlich bei der Filterung bereits berücksichtigt. Nach Betätigung der 2. Listbox stehen also bei beiden Listboxen nur noch die leeren Felder und jeweils 1 angezeigter Wert zur Verfügung. Dies mag für eine eingrenzende Suche erst einmal praktisch erscheinen. Was aber, wenn z.B. in einer Bibliothek die Zuordnung zur Systematik klar war, aber nicht eindeutig, ob es sich um ein Buch, eine CD oder eine DVD handelt? Wurde einmal die Systematik angewählt und dann die 2. Listbox auf CD gestellt so muss, um auch die Bücher zu sehen, die 2. Listbox erst einmal wieder auf NULL gestellt werden, um dann auch die Bücher anwählen zu können. Praktischer wäre, wenn die 2. Listbox direkt die verschiedenen Medienarten anzeigen würde, die zu der Systematik zur Verfügung stehen – natürlich mit den entsprechenden Trefferquoten.
Um dies zu erreichen, wurde die folgende Abfrage konstruiert, die jetzt nicht mehr direkt aus dem Filterergebnis gespeist wird. Die Zahlen für die Treffer müssen anders ermittelt werden.
1 SELECT
2 COALESCE( "Feld_1" || ' - ' || "Anzahl", 'leer - ' || "Anzahl" )
AS "Anzeige",
3 "Feld_1"
4 FROM
5 ( SELECT COUNT( "ID" ) AS "Anzahl", "Feld_1" FROM "Tabelle"
6 WHERE "ID" IN
7 ( SELECT "Tabelle"."ID" FROM "Filter", "Tabelle"
8 WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",
9 "Tabelle"."Feld_2" ) )
10 GROUP BY "Feld_1"
11 )
12 ORDER BY "Feld_1"
Diese doch sehr verschachtelte Abfrage kann auch unterteilt werden. In der Praxis bietet es sich häufig an, die Unterabfrage in einer Tabellenansicht ('VIEW') zu erstellen. Das Listenfeld bekommt seinen Inhalt dann über eine Abfrage, die sich auf diesen 'VIEW' bezieht.
Die Abfrage im Einzelnen:
Die Abfrage stellt 2 Spalten dar. Die erste Spalte enthält die Ansicht, die die Person sieht, die das Formular vor sich hat. In der Ansicht werden die Inhalte des Feldes und, mit einem Bindestrich abgesetzt, die Treffer zu diesem Feldinhalt gezeigt. Die zweite Spalte gibt ihren Inhalt an die zugrundeliegende Tabelle des Formulars weiter. Hier steht nur der Inhalt des Feldes. Die Listenfelder beziehen ihre Inhalte dabei aus der Abfrage, die als Filterergebnis im Formular dargestellt wird. Nur diese Felder stehen schließlich zur weiteren Filterung zur Verfügung.
Als Tabelle, aus der diese Informationen gezogen werden, liegt eine Abfrage vor. In dieser Abfrage werden die Primärschlüsselfelder gezählt (SELECT COUNT( "ID" ) AS "Anzahl"). Dies geschieht gruppiert nach der Bezeichnung, die in dem Feld steht (GROUP BY "Feld_1"). Als zweite Spalte stellt diese Abfrage das Feld selbst als Begriff zur Verfügung. Diese Abfrage wiederum basiert auf einer weiteren Unterabfrage:
1 SELECT "Tabelle"."ID"
2 FROM "Filter", "Tabelle"
3 WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",
"Tabelle"."Feld_2" )
Diese Unterabfrage bezieht sich jetzt auf das andere zu filternde Feld. Prinzipiell muss das andere zu filternde Feld auch zu den Primärschlüsselnummern passen. Sollten noch mehrere weitere Filter existieren, so ist diese Unterabfrage zu erweitern:
1 SELECT "Tabelle"."ID"
2 FROM "Filter", "Tabelle"
3 WHERE "Tabelle"."Feld_2" = COALESCE( "Filter"."Filter_2",
"Tabelle"."Feld_2" )
4 AND
5 "Tabelle"."Feld_3" = COALESCE( "Filter"."Filter_3",
"Tabelle"."Feld_3" )
Alle weiteren zu filternden Felder beeinflussen, was letztlich in dem Listenfeld des ersten Feldes, "Feld_1", angezeigt wird.
Zum Schluss wird die gesamte Abfrage nur noch nach dem zugrundeliegenden Feld sortiert.
Wie letztlich die Abfrage aussieht, die dem anzuzeigenden Formular zugrunde liegt, ist im Kapitel «Datenfilterung» nachzulesen.
Mit dem folgenden Makro kann über das Listenfeld gesteuert werden, welches Listenfeld abgespeichert werden muss und welches neu eingelesen werden muss.
Die Variablen für das Array werden in den Eigenschaften des Listenfeldes unter Zusatzinformationen abgelegt. Die erste Variable enthält dort immer den Namen des Listenfeldes selbst, die weiteren Variablen die Namen aller anderen Listenfelder, getrennt durch Kommata.
1 SUB Filter_Zusatzinfo(oEvent AS OBJECT)
2 DIM oDoc AS OBJECT
3 DIM oDrawpage AS OBJECT
4 DIM oForm1 AS OBJECT
5 DIM oForm2 AS OBJECT
6 DIM stTag AS String
7 stTag = oEvent.Source.Model.Tag
Ein Array (Ansammlung von Daten, die hier über Zahlenverbindungen abgerufen werden können) wird gegründet und mit den Feldnamen der Listenfelder gefüllt. Der erste Name ist der Name des Listenfelds, das mit der Aktion (Event) verbunden ist.
8 aList() = Split(stTag, ",")
9 oDoc = thisComponent
10 oDrawpage = oDoc.drawpage
11 oForm1 = oDrawpage.forms.getByName("Filter")
12 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.
13 FOR i = LBound(aList()) TO Ubound(aList())
14 IF i = 0 THEN
Das auslösende Listenfeld muss abgespeichert werden. Es hat die Variable aList(0). Zuerst wird die Information des Listenfeldes auf die zugrundeliegende Tabelle übertragen, dann wird der Datensatz gespeichert.
15 oForm1.getByName(aList(i)).commit()
16 oForm1.updateRow()
17 ELSE
Die anderen Listenfelder müssen neu eingelesen werden, da sie ja in Abhängigkeit vom ersten Listenfeld jetzt andere Werte abbilden.
18 oForm1.getByName(aList(i)).refresh()
19 END IF
20 NEXT
21 oForm2.reload()
22 END SUB
Die Abfragen für dieses besser nutzbare Makro sind natürlich die gleichen wie in diesem Abschnitt zuvor bereits vorgestellt.
Alternativ zu dieser Vorgehensweise ist es auch möglich, die Filterfunktion des Formulars direkt zu bearbeiten.
1 SUB FilterSetzen
2 DIM oDoc AS OBJECT
3 DIM oForm AS OBJECT
4 DIM oFeld AS OBJECT
5 DIM stFilter As String
6 oForm = thisComponent.Drawpage.Forms.getByName("MainForm")
7 oFeld = oForm.getByName("Filter")
8 stFilter = oFeld.Text
9 oForm.filter = " UPPER(""Name"") LIKE '%'||'" + UCase(stFilter) + "'||'%'"
10 oForm.ApplyFilter = TRUE
11 oForm.reload()
12 End Sub
Das Feld wird im Formular aufgesucht, der Inhalt ausgelesen. Der Filter wird entsprechend gesetzt. Die Filterung wird angeschaltet und das Formular neu geladen.
1 SUB FilterEntfernen
2 DIM oForm AS OBJECT
3 oForm = thisComponent.Drawpage.Forms.getByName("MainForm")
4 oForm.ApplyFilter = False
5 oForm.reload()
6 END SUB
Die Beendigung des Filters kann natürlich auch über die Navigationsleiste erfolgen. In diesem Fall wird einfach ein weiteres Makro genutzt.
Über diese Filterfunktion kann ein Formular auch direkt mit einem Filter z. B. für nur einen Datensatz gestartet werden. Aus dem startenden Formular wird ein Wert (z.B. ein Primärschlüssel für den aktuellen Datensatz) ausgelesen und an das Zielformular als Filterwert weiter gegeben.
Wird ein Formular geöffnet, so stehen über die Navigationsleiste verschiedene Filtermöglichkeiten zur Verfügung. Während in Tabellen, Abfaregn und über das Formularkontrollelement ein Filterdialog zur Verfügung steht, ist dieser über die Navigationsleiste des Formularfensters durch den Formularbasierten Filter ersetzt worden. Auch über eine einfache Schaltfläche lässt sich dieser Dialog nicht starten. Hier kann zur Zeit nur mit einem Makro nachgeholfen werden.
1 SUB<