Rechnungen in Word schreiben

Die Grenzen des Berichtsgenerators, den Access von Hause aus mitbringt, sind bei gehobenen Ansprüchen rasch erreicht. Manch einer mag sich da schon die vielfältigen Formatierungsmöglichkeiten von Word gewünscht haben. Nun, warum nicht: Gestandene Word-Profis werden es weniger gern hören, aber aus Sicht des Datenbank-Entwicklers ist Word schließlich nichts anderes als eine Erweiterung der Alma Mater Access.

Wie Sie Word zum Ausgeben der Daten Ihrer Datenbank einsetzen, erfahren Sie im vorliegenden Beitrag am Beispiel einer Rechnung. Diese können Sie zwar auch in Form eines Access-Berichts ausgeben, aber was, wenn der Sachbearbeiter den Feinschliff gern in Word vornehmen möchte

Grundlegende Informationen zum Zugriff auf Word per VBA finden Sie übrigens im Beitrag Von Access nach Word (s. Shortlink 360).

Aufbau einer Rechnung

Vor der ersten Zeile Code lohnt ein Blick auf das gewünschte Resultat – sprich: den Aufbau des zu erstellenden Rechnungsdokuments (siehe Bild 1). Dieses enthält zunächst konstante Bereiche, die entweder der Geschäftspapiervordruck oder die Word-Vorlage in Form vorgegebener Elemente liefert, hier also der eigentliche Briefkopf und Angaben im Fußbereich wie Grußformel, eventuell eine feste Bankverbindung und ähnliches.

Demo.tif

Bild 1: Aufbau des Word-Dokuments

Des Weiteren gibt es variable Bereiche, die der Code Ihrer Anwendung manipulieren soll – Anschriftenfeld und Rechnungsnummer – und einen (oder mehrere) Listenbereiche, die ebenfalls variabel sind, hier die Rechnungspositionen.

Interessant sind die variablen Bereiche, die der Code Ihrer Anwendung manipulieren soll. Die einfach variablen und die Listendaten finden sich in der Datenbank in der Regel als 1:n-Beziehung; im gewählten Fall einer Rechnungserstellung also zum Beispiel Bestellungen und Bestelldetails.

Die konstanten und einfachen variablen Bereiche entsprechen dabei dem Berichtskopf beziehungsweise -fuß, die sich wiederholenden variablen Bereiche (hier zur Anzeige der Bestelldetails) dem Detailbereich eines Berichts.

Alles, was Sie über die Word-Oberfläche erledigen können, ist auch mittels Code machbar – wie beispielsweise das Erzeugen eines beliebig komplexen Dokuments aus einer völlig leeren Vorlage. Das wäre allerdings ein Berg überflüssiger Arbeit. Die natürliche Faulheit des Programmierers (man kann es auch Intelligenz nennen) und Erfahrung im Entwurf von Access-Berichten weisen den rechten Weg: Die Word-Vorlage liefert die fixen Elemente, alles andere fügen die passenden Routinen hinzu.

Besonderheiten der Textverarbeitung

Das Word-Objektmodell und damit der Aufbau eines Word-Dokuments bringen für Access-Entwickler Ungewohntes mit sich:

Nach einem Page-Objekt, das eine Seite repräsentiert, oder nach Analogien zu Textfeldern, die als wohl definierte Behälter für die per Code zu schreibenden Werte dienen, sucht man vergeblich.

Das ist auch gut so, denn eine Textverarbeitung ist in erster Linie dafür gedacht, Fließtext aufzunehmen, dessen Umbruch sich nach Schriftart und -größe, nach Seitenrändern und ähnlichem richtet. Wo nun also konkret ein bestimmtes Textstück landet, zum Beispiel dritte Seite, zweiter Absatz, zweites Wort, steht beim Befüllen des Dokuments noch nicht fest.

Zusätzlich gilt es einige Dinge zu beachten, die einem Datenbankprogrammierer gar zu leicht von der Hand gehen, in einer Textverarbeitung aber mindestens mieser Stil sind:

Die Konstante vbCrLf etwa ist in Word nicht einfach ein Zeilenwechsel, sondern hat die spezielle Bedeutung des Absatzes (Paragraph). Da die Zeilen einer Adresse beispielsweise insgesamt einen Absatz bilden, ist hier die weiche Zeilenschaltung (ASCII-Code 11) zu verwenden:

Anrede & Chr(11) & Vorname & " " & Name & Chr(11) & Straße & Chr(11) & Chr(11) & PLZ & " " & Ort

Abstände vor und nach Absätzen erzeugen Sie nicht durch mehrere vbCrLf, sondern durch entsprechende Absatzformate:

ParagraphFormat.SpaceBefore = 12
ParagraphFormat.SpaceAfter = 6

Dadurch würden Sie bei Schriftgrößen von 10 bis 12 Punkt vor dem Absatz eine volle und danach eine halbe Leerzeile einfügen.

Für Einrückungen sind nicht Tabulatoren oder Leerzeichenfolgen zuständig, sondern eine passende Formatierung. Die folgende erzeugt etwa einen linken Einzug von zwei Zentimetern:

ParagraphFormat.LeftIndent = _
InchesToPoints(2 / 2.54)

Wichtige Objekte

Um nun überhaupt an einer definierten Position schreiben zu können, brauchen Sie Elemente, die Sie in den Text einbauen und per Code ansprechen können. Als wichtigste sind hier zu nennen:

  • Textmarken (Bookmarks)
  • Felder (Fields)
  • Tabellen (Tables)

Die größte Bedeutung kommt hier den Textmarken zu – insbesondere können Sie diese mit einem Namen versehen.

Felder und Tabellen lassen sich nur durch einen numerischen Index ansprechen, wodurch Sie bei der Programmierung entweder die Topologie der Vorlage genau kennen müssen oder mittels Bookmarks umgehen können: Dazu fassen Sie beispielsweise eine Tabelle vollständig in eine Textmarke ein und erzeugen darüber einen Objektverweis auf die enthaltene Tabelle. Der erforderliche Code braucht noch nicht einmal eine zusätzliche Zeile. Mit Feldern ist das ebenfalls möglich.

Eine Besonderheit, die sich direkt auf Ihre Programmiergewohnheiten niederschlägt, ist folgende: Sämtliche Auflistungen in Word beginnen mit den Indexwert 1.

Hinweis für Makro-Fans

Eine kleine Warnung an Entwickler, die mit dem Makro-Rekorder von Word aufgezeichnete Makros als Grundlage für Eigenkreationen verwenden: Der Recorder arbeitet prinzipbedingt intensiv mit Objekten wie ActiveDocument und Selection beziehungsweise nimmt selbst Selektionen (Markierungen) vor.

Viele interne Funktionsprozeduren, die eigentlich einen Objektverweis zurückgeben, werden in der Art einer Befehlsliste mit Sub-Syntax aufgerufen. Für die Automation von einer anderen Anwendung aus ist dieses Vorgehen im Allgemeinen unbrauchbar: Sie sollten daher immer mit definierten Objektvariablen arbeiten und Bereiche mit Range-Objekten identifizieren.

Word starten

Einen Verweis auf eine Word-Instanz erhalten Sie mit unterschiedlichen Methoden. Verbreitet ist die Variante, zu versuchen, eine laufende Instanz anzusprechen und im Fehlerfall eine neue Instanz zu starten.

Das Vorhandensein einer Word-Instanz ist auch per API-Funktion prüfbar. Beide Varianten finden Sie in der Beispieldatenbank zu diesem Beitrag.

Eine kaum bekannte Methode, die ohne Fehlerprovokation und ohne API auskommt, ist die aus Listing 1.

Listing 1: Word-Instanz holen

Dim wdApp As Word.Application
Dim wdDoc As Word.Document, wdDocDummy As Word.Document
Dim tDoc As String
tDoc = "Pfad\und\Name.doc"
Set wdDocDummy = CreateObject("Word.Document")
Set wdApp = wdDocDummy.Application
Set wdDoc = wdApp.Documents.Add(tDoc)
wdDocDummy.Close

Alle Methoden liefern jedoch das gleiche Ergebnis – einen gültigen Objektverweis auf das gewünschte Dokument.

Textmarken verwenden

Wie schon erwähnt, steuert man das Word-Dokument am sinnvollsten mittels Textmarken. Es gibt hier zwei Typen, die offene I-Bookmark und die begrenzte [ ]-Bookmark.

Beide weisen Sie über den Menüeintrag Einfügen/Textmarke zu. Der Unterschied liegt im markierten Bereich: Liegt eine Markierung vor, umfasst die Textmarke diesen Bereich, sonst fügt Word eine offene Textmarke an der Stelle der Einfügemarke ein.

Falls Sie die Textmarken auch sehen möchten, wählen Sie den Menübefehl Extras/Optionen/Ansicht/Textmarken aus (Word 2007: Word-Optionen/Erweitert/Dokumentinhalt anzeigen/Textmarken anzeigen).

Per Code funktioniert dies wie folgt:

ActiveWindow.View.ShowBookmarks = [True|False]

Beim Beschreiben verhalten sich beide Typen unterschiedlich. Eine offene Textmarke schreibt den Text direkt hinter ihre Position, wobei sie selbst unbeschadet bleibt. Ein Beispiel sind die folgenden Zeilen, die sich auf eine offene Textmarke namens Test beziehen:

With wdDoc.Bookmarks("Test")
     .Range.Text = "Erstens"
     .Range.Text = "Zweitens"
     .Range.Text = "Drittens"
End With

Wenn das Zeichen | die Position der Textmarke angibt, sorgen die obigen Zeilen nacheinander für folgende Ausgabe:

I Erstens
I ZweitensErstens
I DrittensZweitensErstens

Eine begrenzte Textmarke verhält sich anders. Sie wird durch den eingefügten Text ersetzt. Die Textmarke [ ] wird durch die Anweisung

.Range.Text = "Erstens"

in den Text

Erstens

umgewandelt. Der Aufruf der Anweisung

.Range.Text = "Zweitens"

und damit der Versuch, der Textmarke einen weiteren Ausdruck zuzuweisen, löst einen Laufzeitfehler aus, da eine begrenzte Textmarke beim Beschreiben zerstört wird.

Das ist nicht weiter tragisch: Einerseits will man ja einen Text nicht mehrfach überschreiben, andererseits kann man die Textmarke, falls man sie doch mehrfach zu brauchen meint, mit folgendem Trick retten:

'[]-Bookmark retten
Set wdRng = _
wdDoc.Bookmarks("Test").Range
wdRng.Text = "Irgendwas"
wdDoc.Bookmarks.Add "Test", wdRng

Dabei ist wdRng eine Variable vom Typ Word.Range.

Wer keinen Laufzeitfehler riskieren möchte, verwendet zusätzlich die folgende Anweisung:

If wdDoc.Bookmarks.Exists("Test") _
 Then ...

Das Range-Objekt

Das Range-Objekt ist der Schlüssel für die gesamte Word-Programmierung. Es steht für einen Bereich, den man sich wie eine Markierung im Word-Dokument vorstellen kann. Ein Range hat immer einen Anfang und ein Ende sowie eine sich daraus ergebende Ausdehnung.

Der kleinstmögliche Range hat die Ausdehnung 0 und entspricht einer Cursor-Position – für die älteren Leser: einem Lichtmarkenzeiger. Ansonsten kann alles ein Range sein, was sich markieren lässt: etwa ein Buchstabe, ein Wort, ein Absatz, eine Tabelle, eine Tabellenzelle, ein Feld, ein Abschnitt oder das ganze Dokument.

Der große Vorteil des Range-Objekts liegt in seiner Eigenschaft als virtuelle Markierung. Es kann dadurch nicht mit einer eventuellen Textauswahl eines Benutzers interferieren, außerdem lassen sich beliebig viele Ranges parallel verarbeiten. Obendrein ist ein Range erheblich schneller als eine echte Auswahl, die es unter dem Begriff Selection ebenfalls gibt.

In der Praxis legen Sie einen Range nicht durch Anfangs- und Endposition fest, sondern durch die Range-Eigenschaft, die viele Word-Objekte zur Verfügung stellen (s. Tab. 1).

Bereich

Bedeutung

Tab. 1: Bereiche und ihre Bedeutung

Fast alle anschaulichen Word-Objekte haben eine Range-Eigenschaft, die einen Verweis auf ein Range-Objekt zurückgibt, gelegentlich ergeben aber auch Eigenschaften anderen Namens ein Range-Objekt.

Eine Logik gibt es dahinter nicht. Nun ja, die Word-Entwickler waren wohl keine Datenbänkler, also üben wir Nachsicht …

In medias res

Die bisherigen Erwägungen setzen Sie jetzt direkt in die Praxis um. Den vollständigen lauffähigen Code finden Sie in der Beispieldatenbank.

Wie eingangs erwähnt, bereiten Sie das Word-Dokument am besten von Hand vor. Um das oben abgebildete Dokument zu erzeugen, fügen Sie die Elemente aus Bild 2 hinzu oder Sie verwenden einfach die mitgelieferte Beispieldatei VorlageBriefTab.doc.

Briefkopf.tif

Bild 2: Der Briefkopf mit seinen Textmarken

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar