Umsteigen auf OpenOffice: Von VBA zu OpenOffice-Basic - Teil 2

07.08.2006 von Rene Martin
Wer von Microsoft Office auf OpenOffice migrieren will, steht vor einem Problem – zumindest wenn er viele Makros und Scripts mit VBA erstellt hat. Wir zeigen die wichtigsten Unterschiede und geben Hinweise zur Umstellung der Scripts.

Mit dem Durchbruch des Open-Source-Betriebssystems Linux fand das kostenlose OpenOffice.org (im Folgenden OOo genannt) und damit auch das von Sun unterstützte StarOffice mehr und mehr Nutzer. Ebenso wie bei Microsoft Office liegt eine Basic-Sprache darunter, mit der die Applikation (von innen und außen) gesteuert werden kann.

Bislang gibt es aber noch sehr wenige Quellen zu OOo-Basic. Dieser Artikel stellt das Objektmodell vor, zeigt, wie man an die Befehle gelangt und geht der Frage nach, ob sich ein Umstieg von VBA nach OOo-Basic lohnt.

Im ersten Teil haben wir einige grundlegende Unterschiede zwischen VBA und OOo-Basic behandelt. Im zweiten Teil des Workshops stellen wir Ihnen nun exemplarisch einige wichtige Objekte von OOo-Calc vor. Des weiteren gehen wir auf einige Schwachpunkte von OOo ein und zeigen Ihnen, aus welchen Quellen Sie Informationen zur Programmierung OOo-Basic beziehen können.

Einige Objekte von Calc

Vielleicht haben Sie sich bis hierher bereits die Frage gestellt, wie man einen Überblick über die Objekte, Interfaces, Eigenschaften und Methoden, Strukturen und Konstanten erhält. Die Hilfe von OOo beschreibt zwar den Sprachkern vollständig, die Dialoge teilweise, aber die Objekte der Applikation gar nicht. Sämtliche Befehle lassen sich jedoch mit folgenden drei Schlüsselwörtern auflisten:

Obj.DBG_Properties
Obj.DBG_Methods
Obj.DBG_SupportedInterfaces

Das Ergebnis können Sie anzeigen oder in eine Zelle oder ein Writer-Dokument schreiben lassen. Verlangen die Methoden einen bestimmten Wert, so ist das weitere Vorgehen leicht. Bei Objekten, Strukturen und Konstanten konsultieren Sie die Referenzliste, um die verlangten Datentypen zu ermitteln. Leider befindet sich dort lediglich eine Aufstellung sämtlicher Schnittstellen, Strukturen, Konstanten, Methoden und Eigenschaften – ohne Beispiele und Beschreibungen zum weiteren Vorgehen.

Objekt-Typ sicher stellen

Wenn Sie überprüfen möchten, ob es sich bei einem Objekt wirklich um ein „Calc-Dokument“ handelt, dann finden Sie es heraus, indem Sie überprüfen, ob es den Service com.sun.star.sheet.SpreadsheetDocument unterstützt:

If Thiscomponent.SupportsService("com.sun.star.sheet.SpreadsheetDocument") = False Then
MsgBox "Sie sind nicht im richtigen Dokument"
Else
...

Auf die Liste der Tabellenblätter greifen Sie über die Sammlung (Collection) Sheets zu. Jedes einzelne Blatt lässt sich über seine Nummer (die Zählung beginnt konsequenterweise bei allen Sammlungen bei 0) oder seinen Namen ansprechen.

Sub Blatt_Zugriff1
Dim objDatei as Object
Dim objBlaetter As Object
Dim objBlatt As Object

objDatei = ThisComponent
objBlaetter = objDatei.Sheets

'oder so:
objBlaetter = objDatei.getSheets()
objBlatt = objBlaetter.getByName("Tabelle1")

'oder so:
objBlatt = objDatei.Sheets("Tabelle1")
End Sub

Die Methode hasByName(String) der Schnittstelle XNameAccess verrät, ob ein Tabellenblatt mit einem bestimmten Namen innerhalb der Datei vorhanden ist. Die Eigenschaft Count und die Methode getCount() ermitteln die Anzahl der Tabellenblätter:

MsgBox objBlaetter.Count
MsgBox objBlaetter.getCount()

Dabei ist zu beachten, dass das erste Blatt die Indexnummer 0 besitzt, so dass die eine Schleife nur bis getCount() – 1 beziehungsweise Count – 1 hochzählen darf.

Löschen und Einfügen

Das Löschen eines Blattes erfolgt mit der Methode removeByName, mit der Methode insertByName oder insertNewByName(String, Index) wird ein neues eingefügt, mit movebyName wird ein Blatt verschoben.

Ein Blatt lässt sich ausblenden, indem seine Eigenschaft IsVisible auf false gesetzt wird. Dann jedoch hat der Benutzer keine Möglichkeit mehr, dieses Blatt in OOo wieder einzublenden.

Jedes Tabellenblatt enthält eine Liste mit seinen Zeilen und Spalten. Diese sind über die Sammlungen Rows beziehungsweise Columns des Spreadsheet-Objekts verfügbar und unterstützen die Services com.sun.star.table.TableRows beziehungsweise com.sun.star.table.TableColumns.

Sub ZeilenUSpalten1
Dim objDatei as Object
Dim objBlatt As Object
Dim objErsteZeile As Object
Dim objErsteSpalte As Object

objDatei = ThisComponent
objBlatt = objDatei.Sheets(0)
objErsteSpalte = objBlatt.Columns(0)
objErsteZeile = objBlatt.Rows(0)
End Sub

Das Beispiel in Listing 3 erzeugt zwei Objekte, die auf die erste Zeile und die erste Spalte eines Spreadsheets verweisen und legt diese in den Objektvariablen objErsteZeile und objErsteSpalte ab.

Zugriff auf Zellen

Ein Tabellenblatt setzt sich zusammen aus einer zweidimensionalen Liste mit Zellen. Jede Zelle ist über ihre X- und Y-Position eindeutig bestimmt. Die linke obere Zelle hat die Position (0 / 0) oder den Namen A1. Das Beispiel in Listing 4 erzeugt ein Objekt, das auf die linke obere Zelle („A1“) verweist und dort einen Text einfügt.

Sub Zellzugriff01
Dim objDatei as Object
Dim objBlaetter As Object
Dim objBlatt As Object
Dim objZelle As Object

objDatei = ThisComponent
objBlatt = objDatei.Sheets(0)
objZelle = objBlatt.getCellByPosition(0, 0)
objZelle.String = "Hallo Leute, ich bin der Neue!"
objBlatt.Columns(0).OptimalWidth = True

End Sub

Hinweis: Ebenso wie der Name der Zelle A1 lautet, wird sie über getCellByPosition(0, 0) zuerst mit dem Wert für die Spalte und anschließend mit dem Zeilenwert angesprochen, also A1 und nicht R1C1 wie bei Excel!

Namen und Bereiche

Sie können die Zelle ebenso über ihren Namen ansprechen:

objZelle = objBlatt.getCellRangeByName("B1")
objZelle.String = "Hallo Leute, ich bin auch eine Überschrift!"
objBlatt.Columns(1).OptimalWidth = True

OOo-Basic unterscheidet nicht zwischen einer Zelle und einem Zellbereich. Das heißt, Sie können mit

objZelle = objBlatt.getCellRangeByName("B1:C7")
objZelle = objBlatt.getCellRangeByName("$B$1:$C$7")

auf einen Bereich zugreifen. Wenn Sie dies per Zahlen (beispielsweise über eine Schleife) erledigen möchten, dann so:

objZelle = objBlatt.getCellRangebyPosition(2, 1, 3, 7)

Während die meisten der Eigenschaften (beispielsweise Formatierungen) gesetzt werden können, kann allerdings nicht in alle Zellen gleichzeitig geschrieben werden! Im folgenden Beispiel wird der Zellbereich blau formatiert:

objZelle = objBlatt.getCellRangebyPosition(2, 1, 3, 7)
objZelle.setPropertyValue("CellBackColor", RGB(0, 0, 255))

Zellposition ermitteln

Umgekehrt lässt sich die Zellposition mit der Struktur getRangeAddress() ermitteln:

MsgBox "Der Bereich beginnt bei Zeile: " & objBereich.getRangeAddress().StartRow & ", endet bei Zeile: " & objBereich.getRangeAddress().EndRow & "." & Chr(13) & "Der Bereich beginnt bei Spalte: " & objBereich.getRangeAddress().StartColumn & ", endet bei Spalte: " & objBereich.getRangeAddress().EndColumn & "." & Chr(13) & "Der Bereich liegt auf Blatt: " & objBereich.getRangeAddress().Sheet & "."

Eine Tabellenzelle kann in OOo-Basic Text, Zahlen, Formeln oder gar nichts enthalten. Je nachdem, was in die Zelle geschrieben wird, muss eine andere Eigenschaft verwendet werden: Text wird über die Eigenschaft String eingetragen, Zahlen als Value und Formeln über Formula (siehe Listing 5).

Sub Zellzugriff02
Dim objDatei as Object
Dim objBlaetter As Object
Dim objBlatt As Object
Dim objZelle As Object

objDatei = ThisComponent
objBlatt = objDatei.Sheets(0)
objZelle = objBlatt.getCellByPosition(0, 0)
objZelle.String = "Ausgaben Januar Netto"
objZelle = objBlatt.getCellByPosition(0, 1)
objZelle.Value = 25896
objZelle = objBlatt.getCellByPosition(0, 2)
objZelle.Value = 79825.45
objZelle = objBlatt.getCellByPosition(0, 3)
objZelle.Value = -6358.25
objZelle = objBlatt.getCellByPosition(0, 4)
objZelle.Formula = "=SUM(A2:A4)"
objZelle = objBlatt.getCellByPosition(0, 5)
objZelle.String = "MWSt:"
objZelle = objBlatt.getCellByPosition(0, 6)
objZelle.Formula = "=A5*16%"
objBlatt.Columns(0).OptimalWidth = True
End Sub

Zell-Cursor

Wenn sichergestellt ist, dass auf einem Tabellenblatt ein zusammenhängender Bereich vorliegt (das heißt ohne Leerzeilen und ohne Leerspalten), lassen sich die linke obere und rechte untere Zelle mit Hilfe des Zell-Cursors bestimmen.

Achtung: Auf einem Tabellenblatt kann nur ein solcher Cursor existieren. Der Cursor hat nichts mit der sichtbaren Markierung des Tabellenblatts zu tun.

Den Cursor erzeugen Sie mit der Methode createCursor() oder besser mit createCursorByRange(Zelle). Die zweite Methode verlangt als Objekt eine Zelle. Im ersten Fall müsste man ihm eine Zelle oder einen Bereich mit den bekannten Methoden (getCellByPosition, getCellRangeByPosition oder getCellRangeByName) zuweisen. Für den Zell-Cursor stehen die Methoden gotoStart(), gotoEnd() und collapseToCurrentRegion() zur Verfügung:

Daneben stellt ein „ausgedehnter“ Cursor die Sammlungen Columns und Rows zur Verfügung, deren Anzahl wiederum mit der Eigenschaft Count ermittelt wird. Das Beispiel aus Listing 6 zeigt mögliche Verwendungen des Zell-Cursors.

Sub ZellBereichErmitteln
Dim objDatei As Object
Dim objBlatt As Object
Dim objZelle as Object
Dim objCursor As Object
Dim strInfo As String

objDatei = ThisComponent
objBlatt = objDatei.Sheets.getByIndex(0)
objZelle = objBlatt.getCellRangebyName("A1")
objCursor = objBlatt.createCursorByRange(objZelle)
objCursor.gotoEnd()
strInfo = "Die untere Zeile liegt in Nr.: " & objCursor.getRangeAddress().EndRow + 1 & Chr(13) & "und die rechte Spalte liegt in Nr.: " & objCursor.getRangeAddress().EndColumn
MsgBox strInfo
objCursor.gotoStart()
strInfo = "Die obere Zeile liegt in Nr.: " & objCursor.getRangeAddress().EndRow + 1 & Chr(13) & "und die linke Spalte liegt in Nr.: " & objCursor.getRangeAddress().EndColumn
MsgBox strInfo
objCursor = objBlatt.createCursorByRange(objZelle)
objCursor.collapseToCurrentRegion()
strInfo = "Der Bereich verfügt über " & objCursor.Columns().Count & " Spalten " & Chr(13) & "und über " & objCursor.Rows().Count & " Zeilen."
MsgBox strInfo
End Sub

Ein Beispiel

In einer Calc-Datei befinden sich mehrere Tabellenblätter. Sie werden – bis auf eines – zum Schluss ausgeblendet:

For i = 0 To ThisComponent.Sheets.getCount() - 1
If ThisComponent.Sheets.getByIndex(i).Name <> "ReportingTool" Then
ThisComponent.Sheets.getByIndex(i).IsVisible = False
End If
Next

Auf dem nicht ausgeblendeten Tabellenblatt befinden sich die Schaltflächen, mit deren Hilfe die Dialoge geöffnet werden. Der erste Dialog trägt neue Verkäufer ein, löscht vorhandene oder ändert zugewiesene Informationen wie beispielsweise Zielvorgaben oder das Aufgabengebiet. Über einen zweiten Dialog tragen Sie Verkaufsinformationen ein.

Kontrollkästchen fragen Sie über die Eigenschaft State ab. Arbeiten Sie mit Zahlenfeldern, dann muss der Wert als Value und nicht als String eingetragen werden. Etwas umständlich sind das Auslesen und Eintragen von Datumsangaben aus Datumsfeldern – sie müssen mit der Funktion CDateFromIso konvertiert werden:

objBlattDaten.getCellByPosition(12, intZaehler).Value = CDateFromIso(Dlg.getControl("txtBestelldatum").Model.Date)
.getCellByPosition(12, intZaehler).NumberFormat = lngNumberFormatId

Datensuche

Die gleiche Maske lässt sich auch für die Datensuche verwenden. Dazu blenden wir zunächst alle Steuerelemente aus und die benötigten danach wieder ein:

For intZaehler = 0 To ubound(Dlg.Controls)
Dlg.Controls(intZaehler).Visible = False
Next

Dlg.getControl("cmdSuchen").Visible = True
Dlg.getControl("txtVertragsnummer").Visible = True
Dlg.getControl("lblVertragsnummer").Visible = True
[…]
Dlg.getControl("cmdSuchen").Visible = True
Dlg.getControl("cmdOK").Label = "Ändern"
Dlg.Execute()

With objBlattDaten
Dlg.getControl("txtVertragsnummer").Text = .getCellByPosition(0, intZaehler).String
Dlg.getControl("lstVerkaeufer").selectItem(objBlattDaten.getCellByPosition(2, intZaehler).String, True)
Dlg.getControl("txtKunde").Text = .getCellByPosition(3, intZaehler).String
Dlg.getControl("txtOrt").Text = .getCellByPosition(4, intZaehler).String
Dlg.getControl("lstGeraete").selectItem(objBlattDaten.getCellByPosition(5, intZaehler).String, True)
Dlg.getControl("lblSegmente").Text = .getCellByPosition(6, intZaehler).String
Dlg.getControl("lstVertragsart").selectItem(objBlattDaten.getCellByPosition(7, intZaehler).String, True)
Dlg.getControl("chkSupport").State = .getCellByPosition(8, intZaehler).Value
Dlg.getControl("txtMonatlicheKopienverpflichtung").Value = .getCellByPosition(9, intZaehler).Value
Dlg.getControl("txtHardware").Value = .getCellByPosition(10, intZaehler).Value
Dlg.getControl("txtService").Value = .getCellByPosition(11, intZaehler).Value
Dlg.getControl("txtBestelldatum").Model.Date = CDateToIso(objBlattDaten.getCellByPosition(12, intZaehler).Value)
Dlg.getControl("txtInstallationsdatum").Model.Date = CDateToIso(objBlattDaten.getCellByPosition(13, intZaehler).Value)
Dlg.getControl("chkNeukunde").State = .getCellByPosition(14, intZaehler).Value
End With

Daten auswerten

Liefert die Suche Daten, dann werden diese angezeigt (siehe Listing 8). Auch stellen mögliche Auswertungen kein Problem mehr dar. Wird beispielsweise ein Monat ausgewertet, dann lässt sich wie folgt filtern:

If Month(objBlattDaten.getCellByPosition(12, intZaehlerZeilen ).Value) = intListenPos + 1 Then

Die Beschriftung und Formatierung von Zellen erledigen folgende Zeilen:

objLinie.Color = RGB(0, 0, 255)
objLinie.OuterLineWidth = 100

With objBlattNeu.getCellByPosition(intZaehlerSpalten, intZaehlerZeilenNeu + 4)
.CharWeight = com.sun.star.awt.FontWeight.BOLD
.CharColor = RGB(255, 0, 0)
.CellBackColor = RGB(255, 255, 0)
.BottomBorder = objLinie
.VertJustify = com.sun.star.table.CellVertJustify.CENTER
End With

Daten sichern

Damit der Benutzer seine Dateien sichern oder die Reporting-Datei für das nächste Jahr als „leere“ Datei speichern kann, stellt OOo-Basic die Services com.sun.star.ui.dialogs.FilePicker und com.sun.star.ui.dialogs.FolderPicker zur Verfügung. Diese zeigen einen Dialog an, über den der Benutzer eine Datei beziehungsweise einen Ordner auswählt (siehe Abbildung 8):

objVerzeichnisDlg = CreateUnoService("com.sun.star.ui.dialogs.FolderPicker")
If objVerzeichnisDlg.execute() Then
Dlg.getControl("txtPfad").Text = objVerzeichnisDlg.Directory
End If

Makros verteilen

Prozeduren und Funktionen liegen in Modulen. Diese wiederum sind an Bibliotheken gebunden, die entweder an Dateien hängen und nur in diesen zur Verfügung stehen (wie im obigen Beispiel beschrieben). Sollen Prozeduren global zur Verfügung stehen, dann können Bibliotheken bequem in die OOo-Applikation eingebunden werden. So lassen sie sich auf mehreren Rechnern verteilen.

Zugriff von außen

OOo stellt selbst keine Schnittstelle zur Verfügung, mit deren Hilfe sich ein Verweis in VB, VS.NET oder Microsoft Office einbinden lässt. Allerdings gibt es im Internet ein Paket, mit dem ein solcher Zugriff mit Early Binding ermöglicht wird. Wollen Sie mit Late Binding arbeiten, so steht dem auch nichts im Wege – mit kleinen Abwandlungen auch in VB.NET:

Dim objManager As Object
Dim objDesktop As Object
Dim objDatei As Object
Dim objBlaetter As Object
Dim objBlatt As Object
Dim objZelle As Object
Dim objCursor As Object
Dim intZeilen As Integer
Dim intZaehler As Integer
Dim strListe As String
Dim NoArgs()
Dim strDatei As String

strDatei = "file:///C:/Test/Reporting-Tool.sxc"

Set objManager = CreateObject("com.sun.star.ServiceManager")
Set objDesktop = objManager.createInstance("com.sun.star.frame.Desktop")
Set objDatei = objDesktop.loadComponentFromURL(strDatei, "_blank", 0, NoArgs())
Set objBlaetter = objDatei.Sheets.CreateEnumeration
objBlaetter.nextElement
Set objBlatt = objBlaetter.nextElement

Set objZelle = objBlatt.getCellByPosition(0, 0)
Set objCursor = objBlatt.createCursorByRange(objZelle)
objCursor.collapseToCurrentRegion
intZeilen = objCursor.Rows.Count

For intZaehler = 1 To intZeilen
strListe = strListe & vbCr & objBlatt.getCellByPosition(0, intZaehler).String
Next

MsgBox strListe

Kritik

Nach dem oben beschriebenen Beispiel drängt sich die Frage auf, ob sich ein Umstieg von VBA auf OOo-Basic lohnt. Derzeit sprechen noch einige Gründe deutlich gegen OOo-Basic:

1.) Einige Fehler lassen sich nicht sauber abfangen. Das folgende Programm meldet beispielsweise die Zahl 0:

On Error Goto Ende

Dim i As Integer
i = "drei"
MsgBox i
Exit Sub

Ende:
MsgBox "Es trat ein Fehler auf!"

2.) Einige Funktionen und Methoden sind fehlerhaft, beispielsweise die Funktion Format (Pitonyak verwendet in seinem Buch „OpenOffice.org Macros Explained“ sogar ein eigenes Symbol für Bugs).

3.) Wenn beim Testen ein Fehler im Code ist, stürzt OOo häufig ab. Hier spiegelt sich möglicherweise das schlechte Fehlermanagement wider, das OOo-Basic in seinen Prozeduren zur Verfügung stellt.

4.) Bislang gibt es scheinbar keine einfachen Möglichkeiten, Symbole, Menüpunkte und Tastenkombinationen zu programmieren. Man müsste dies „per Hand“ auf jedem Rechner erzeugen. Ebenso wenig lassen sich Steuerelemente direkt in Dokumente einbinden beziehungsweise vorhandene Ereignisse „umbelegen“. Auch der Zugriff auf die internen Dialoge scheint nicht möglich zu sein.

5.) Unterschiedliche Bezeichnungen erleichtern nicht gerade die Erlernbarkeit: Dlg.getControl("txtOrt").Enable jedoch: Dlg.getControl("txtOrt").Model.Enabled.

6.) Das größte Manko ist jedoch die schlechte und nicht ausreichende Dokumentation. Während sich zu VBA Tausende von Internet-Seiten, Büchern oder Artikeln in Fachzeitschriften finden, existiert über OOo-Basic nur sehr spärliche Literatur. Die wichtigsten Titel haben wir im Anhang aufgelistet.

Fazit

Wer sich mit dem Gedanken trägt, von MS Office auf das Open-Source-Produkt OpenOffice.org umzusteigen, sollte noch warten, bis es etwas ausgereifter ist und ausreichend Literatur dazu existiert. Sonst steht der Zeitaufwand beim Suchen von Befehlen in keinem Verhältnis zur Kostenersparnis, die OOo mit sich bringt.

Quellen

Folgende Bücher zu OOo-Basic sind verfügbar:

Im Internet finden Sie auf diesen Seiten Informationen zu OOo-Basic:

PDFs von Sun, die zum Download bereitstehen, leider nicht vollständig und mit einigen Fehlern

Komplette Dokumentation der API leider ohne Beispiele, sondern lediglich eine immense Ansammlung von Befehlen

1000 Seiten umfassende Dokumentation über die Steuerung von OOo mit Java.

Schließlich bieten die folgenden deutschsprachigen Newsgroups und Foren Hilfe zu OOo (ohne Garantie auf Vollständigkeit):

OpenOffice.org deutschsprachig http://de.openoffice.org

Das deutsche OpenOffice.org-Portal: http://www.ooo-portal.de/

Eine OpenOffice.org-Distribution http://www.openoffice-cd.de

Deutsches Forum rund um OpenOffice.org: http://de.openoffice.info

Das OpenOffice.org- und StarOffice-Forum http://www.industrie24.com

Das StarOffice- & OpenOffice.org-Forum auf WinTotal: http://www.wintotal-forum.de

Peter Herzogs Spotlight.de StarOffice-Forum: http://www.spotlight.de

Open-Craft.org: Freie Software für freie Handwerker: http://www.open-craft.org

Das StarOffice- & OpenOffice.org-Forum: http://supportforum.sun.com

(René Martin/mha)