Word automatisieren

Word bietet als Textverarbeitung ganz andere Möglichkeiten als Access-Berichte, wenn es um die Ausgabe von Texten geht. Natürlich liegen die Schwerpunkte bei Word auch ganz anders – so ist es grundsätzlich dafür ausgelegt, seine Texte nacheinander zu erfassen und nicht, wie etwa ein Access-Bericht, in einer durch die verschiedenen Berichtsbereiche vorgegebenen Struktur. Wir schauen uns in diesem Beitrag an, wie Sie Texte von Access nach Word bewegen und umgekehrt.

Im Gegensatz etwa zu Outlook ist Word eine Anwendung, von der man mehrere Instanzen erzeugen kann. Diese Erkenntnis ist wichtig, wenn Sie entscheiden, ob Sie für den VBA-gesteuerten Zugriff auf Word eine bestehende Instanz verwenden oder eine neue Instanz erstellen möchten. Dazu müssen Sie natürlich erst einmal herausfinden, ob auf dem Rechner bereits eine Word-Instanz geöffnet ist.

Da man grundsätzlich sagen kann, dass eine eigene Instanz einfacher zu handhaben ist, legen wir uns gleich darauf fest, jeweils eine eigene Instanz zu erzeugen. So können Sie diese Instanz bei der Erzeugung referenzieren und, wenn Sie diese nicht mehr benötigen, auch wieder beenden.

Wenn Sie von Access aus eine bestehende Instanz nutzen, müssten Sie sich diese gegebenenfalls mit einer anderen Anwendung teilen, die darauf zugreift, oder auch mit dem Benutzer, der gerade vor dem Rechner sitzt. Um eventuelle Seiteneffekte zu vermeiden, rufen wir für die Automation von Access aus lieber gleich eine eigene Instanz auf. Der einzige offensichtliche Nachteil ist, dass dies mehr Ressourcen benötigt als eine einzige Instanz.

Early Binding oder Late Binding

Die immerwährende Frage, ob Sie mit Early oder Late Binding arbeiten, kann man nur unter Betrachung des jeweiligen Kontext beurteilen. Wer eine Automation einer Office-Anwendung programmieren möchte, die unter allen gängigen Anwendungen läuft, sollte dies ohne Verwendung eines Verweises und dementsprechend mit Late Binding erledigen. Auf diese Weise verhindern Sie Probleme, die sonst beim Antreffen der falschen Version des Verweises auftreten.

Allerdings nehmen Sie sich damit auch die Möglichkeit, auf die Ereignisse zu reagieren, die vom Word-Objekt oder vom Document-Objekt ausgelöst werden. Interessant ist dies beispielsweise, wenn Sie beobachten wollen, ob der Benutzer ein Dokument öffnet oder schließt oder dieses speichert.

Sollten Sie solche Funktionen nutzen wollen, arbeiten Sie mit Early Binding, also unter Verwendung eines fest integrierten Verweises auf die jeweils aktuelle Version der Word-Objektbibliothek.

Diesen stellen Sie im Verweise-Dialog ein, den Sie über den Menüpunkt Extras|Verweise des VBA-Editors (Strg + G) öffnen – s. Bild 1. Anschließend können Sie mit der folgenden Anweisung eine Word-Instanz erstellen und mit einer entsprechenden Objektvariablen referenzieren:

Verweis auf die Word-Bibliothek

Bild 1: Verweis auf die Word-Bibliothek

Dim objWord As Word.Application
Set objWord = New Word.Application
Sleep 3000
objWord.Quit
Set objWord = Nothing

Dies erstellt eine Word-Instanz und schließt diese nach drei Sekunden wieder. Die gleiche Variante sieht beim Late Binding so aus:

Dim objWord As Object
Dim objDocument As Object
Set objWord = _
     CreateObject("Word.Application")
Set objDocument = objWord.Documents.Add

Um das Word-Fenster anzuzeigen und in den Vordergrund zu holen, sind ein paar Zeilen mehr nötig. Wir erzeugen zusätzlich ein neues, leeres Word-Dokument, machen die Word-Instanz mit der Visible-Eigenschaft sichtbar und holen das Anwendungsfenster mit der AppActivate-Methode nach vorn:

Dim objWord As Word.Application
Dim objDocument As Word.Document
Set objWord = New Word.Application
Set objDocument = objWord.Documents.Add
objWord.Visible = True
AppActivate objDocument.Name
Sleep 3000
objWord.Quit
Set objWord = Nothing

Auch diese Instanz soll nach drei Sekunden wieder verschwinden. Sie können auch ohne explizites Erzeugen eines Word-Objekts ein Dokument in Word erzeugen oder öffnen. Das geht schlicht so:

Dim objDocument As Word.Document
Set objDocument = New Word.Document
objDocument.Parent.Visible = True
AppActivate objDocument.Name
Sleep 3000
objDocument.Close

Nach dem Schließen bleibt die Word-Instanz allerdings bestehen. Sie müssen diese dann manuell schließen oder per Code über die Parent-Eigenschaft des Word-Dokuments:

objDocument.Parent.Quit

Dazu muss das Dokument natürlich noch geöffnet sein. Wenn das Dokument vom Erzeugen beziehungsweise öffnen bis zum Schließen noch bearbeitet wird, erscheint beim Versuch, Word zu schließen, ein Dialog, der fragt, ob das Dokument gespeichert werden soll (s. Bild 2).

Rückfrage vor dem Schließen eines Dokuments

Bild 2: Rückfrage vor dem Schließen eines Dokuments

Dies können Sie verhindern, indem Sie festlegen, was beim Schließen geschehen soll: Soll Word das Dokument ohne Rückfrage speichern Oder die änderungen verwerfen Dies legen Sie mit einem der folgenden Werte für den ersten Parameter der Close-Methode des Document-Objekts fest:

  • wdDoNotSaveChanges: Schließt das Dokument, ohne zu speichern.
  • wdPromptToSaveChanges: Fragt den Benutzer, ob das Dokument gespeichert werden soll (Standardwert).
  • wdSaveChanges: Speichert die änderungen.

Wenn Sie das Speichern-Verhalten auf diese Weise beeinflussen möchten, müssen Sie die Word-Instanz allerdings explizit instanzieren.

Anderenfalls könnten Sie diese nach dem Schließen des Document-Objekts nicht mehr refenzieren und müssten Word manuell schließen (sofern sichtbar) oder über den Task-Manager terminieren (wenn das Word-Fenster nicht eingeblendet wurde).

Wenn Sie selbst das Dokument per VBA mit Text füllen oder vorhandenen Text bearbeiten, werden Sie die änderungen vor dem Schließen speichern wollen:

Dim objWord As Word.Application
Dim objDocument As Word.Document
Set objWord = New Word.Application
Set objDocument = objWord.Documents.Add
objWord.Visible = True
AppActivate objDocument.Name
objDocument.Range = "Hallo"
Sleep 3000
objDocument.Close, 
objWord.Quit
Set objWord = Nothing

Haben Sie ein neues Dokument erstellt, wurde dies allerdings zuvor noch nicht gespeichert – ein Versuch, das Dokument zu schließen und automatisch zu speichern, schlägt fehl, da Word noch keinen Speicherort kennt. In diesem Fall speichern Sie es zuvor manuell unter dem gewünschten Namen. Dabei geben Sie optimalerweise direkt das gewünschte Format an, hier als Word-Dokument:

objDocument.SaveAs2 CurrentProject.Path 
& "\word.doc", wdFormatDocument

Dies verwendet das ältere .doc-Format. Wenn Sie das neue XML-Format verwenden möchten (Dateiendung .docx), verwenden Sie diese Anweisung:

objDocument.SaveAs2 CurrentProject.Path 
& "\word.docx", wdFormatXMLDocument

Beide Anweisungen speichern das Dokument im Verzeichnis der aktuellen Access-Datenbank.

Bestehendes Dokument öffnen

Sie können auch auf ein bereits bestehendes Dokument zugreifen. Dazu verwenden Sie die Open-Methode der Documents-Auflistung des Word-Objekts:

Set objDocument = objWord.Documents.Open(CurrentProject.Path & "\word.doc")

Auch dies gelingt mit Late Binding – in diesem Fall ohne vorherige Instanzierung eines Word-Objekts:

Set objDocument = GetObject( CurrentProject.Path & "\word.doc")

Sie müssen also einfach nur den Namen des zu öffnenden Dokuments als Parameter der GetObject-Methode angeben.

Text einfügen

Nachdem Sie ein Word-Dokument erstellt oder geöffnet haben, möchten Sie Text einfügen.

Dies gelingt am einfachsten, indem Sie dem mit der Range-Eigenschaft geliefernten Objekt des Word-Dokuments einen Text zuweisen:

objDocument.Range.Text = "Range-Objekt gefüllt"

Was aber ist dieses Range-Objekt überhaupt

Ein Range-Objekt markiert verschiedene Bereiche eines Dokuments. Das Range-Objekt des Document-Objekts etwa markiert den kompletten Inhalt. Wenn Sie der Text-Eigenschaft dieses Range-Objekts einen Text zuweisen, wird der komplette Bereich mit dem angegebenen Text überschrieben.

Sie können dies einfach experimentell prüfen, indem Sie eine Objektvariable für ein Document-Objekt im Kopf eines Standardmoduls deklarieren:

Public objDoc As Word.Document

Dieses füllen Sie dann mit der folgenden Prozedur:

Public Sub TextExperimente()
     Dim objWord As Word.Application
     Set objWord = New Word.Application
     Set objDoc = objWord.Documents.Add
     objWord.Visible = True
     AppActivate objDoc.Name
End Sub

Das Dokument bleibt geöffnet und Sie können über das Direktfenster mit der Variablen objDoc auf das Dokument zugreifen.

Wenn Sie dort beispielsweise die folgende Zeile eingeben, wird der entsprechende Text angelegt (s. Bild 3):

Einfügen von Texten über den Direktbereich

Bild 3: Einfügen von Texten über den Direktbereich

objDoc.Range.Text = "Text aus dem Direktbereich"

Um zu erkennen, welchen Bereich die Range-Eigenschaft jeweils zurückgibt, können Sie den Bereich per VBA markieren:

objDoc.Range.Select

Die Markierung ist nicht direkt sichtbar, also aktivieren Sie entweder manuell das Word-Fenster oder Sie aktivieren dieses mit dieser Anweisung:

AppActivate objDoc.Name

Tippen Sie einmal einige Absätze in das Dokument und wiederholen das Markieren des Range-Objekts von objDoc, erkennen sie, dass dieses Range-Objekt immer den kompletten Dokumentinhalt betrifft.

Beim Experimentieren werden Fehler auftreten, was dazu führt, dass die Objektvariable objDoc ihren Inhalt verliert.

Mit der folgenden kleinen Prozedur füllen Sie diese wieder – vorausgesetzt, während der Experimente gibt es nur eine Word-Instanz mit nur einem Dokument (s. Listing 1).

Public Sub AktuellesDokumentReferenzieren()
     Dim objWord As Word.Application
     Set objWord = GetObject(, "Word.Application")
     Set objDoc = objWord.Documents(1)
End Sub

Listing 1: Referenzieren des aktuellen Dokuments

Dokumentinhalt einlesen

Andersherum können Sie den Inhalt eines Range-Objekts auch über das Direktfenster auslesen. Dazu geben Sie beispielsweise mit der Debug.Print-Anweisung den Inhalt der Eigenschaft Text des aktuellen Range-Objekts aus.

Absätze

Ein Absatz in einem Word-Dokument ist ein Text, der mit dem Zeilenumbruch abgeschlossen wird, also dem Zeichen, das Sie unter VBA mit vbCr beziehungsweise Chr(13) darstellen können. Die durch dieses Zeichen voneinander getrennten Absätze können Sie mit der Paragraphs-Auflistung erfassen und mit einer Objektvariablen des Typs Paragraph referenzieren. In Bild 4 haben wir beispielsweise den Inhalt des zweiten Absatzes im Direktfenster ausgegeben.

Zugriff auf einen Absatz per Paragraphs-Auflistung

Bild 4: Zugriff auf einen Absatz per Paragraphs-Auflistung

Wollen Sie den Text eines Absatzes verändern, können Sie diesen gezielt referenzieren und anpassen – und zwar so:

objdoc.Paragraphs(2).Range.Text = "Neuer Text in Absatz 2"

Das Ergebnis fällt allerdings nicht wie gewünscht aus: Der Text des zweiten Absatzes wird zwar ersetzt, aber der zweite Absatz verschmilzt mit dem dritten Absatz. Der Grund ist einfach: Sie haben einen Absatz, dessen Text mit einem Zeilenumbruch abgeschlossen wurde, durch einen Text ohne Zeilenumbruch ersetzt. Die folgende Anweisung ersetzt den Absatz schließlich wie gewünscht:

objDoc.Paragraphs(2).Range.Text = "Neuer Text in Absatz 2" & vbCr

Nun wird genau der gewünschte Absatz ersetzt.

Absätze durchlaufen

Wenn Sie in objDoc eine Referenz auf ein geöffnetes Word-Dokument gespeichert haben, können Sie die Absätze des Dokuments über die Auflistung Paragraphs durchlaufen:

Dim p As Word.Paragraph
For Each p In objDoc.Paragraphs
     Debug.Print p.Range.Text
Next p

Dies gibt alle Absätze im Direktfenster aus, wobei jeweils eine Zeile frei bleibt – dies deshalb, weil Debug.Print ohnehin immer eine neue Zeile beginnt und jede Zeile mit vbCr einen eigenen Zeilenumbruch mitbringt. Die Paragraphs-Auflistung bringt auch die Möglichkeit mit, die Anzahl auszugeben:

objDoc.Paragraphs.Count

Dies ist wichtig, wenn Sie einmal alle oder bestimmte Absätze löschen möchten. In diesem Fall können Sie die Paragraphs-Auflistung nicht mit For Each durchlaufen, sondern rückwärts mit der For…Next-Schleife:

Dim p As Word.Paragraph
Dim i As Integer
For i = objDoc.Paragraphs.Count  To 1 Step -1
     Set p = objDoc.Paragraphs(i)
     Debug.Print p.Range.Text
Next i

Wollen Sie den Absatz nicht ausgeben, sondern löschen, verwenden Sie diese Zeile:

p.Range.Delete

Sie löschen also nicht das Paragraph-Objekt, sondern das dadurch markierte Range-Objekt.

Absatz hinzufügen

Dass Sie einen Absatz nicht über die Range-Eigenschaft des Dokuments hinzufügen können, wurde bereits weiter oben deutlich – dieses Objekt markiert nämlich das komplette Dokument.

Wenn wir beispielsweise einen Absatz hinter dem letzten bestehenden Absatz einfügen möchten, müssen wir ein Range-Objekt definieren, das bis zum Ende des Ziels reicht und dann den gewünschten Text mit der Methode InsertAfter einfügen.

In einem ersten, naiven Ansatz probieren wir es so:

Dim p As Word.Paragraph
Dim i As Integer
Dim rng As Word.Range
i = objDoc.Paragraphs.Count
Set rng = objDoc.Paragraphs(i)
rng.InsertAfter "Test"

Das heißt, wir ermitteln den Index des letzten Absatzes, erstellen ein Range-Objekt auf Basis dieses Absatzes und fügen dann mit der Methode Insert-After einen neuen Text ein.

Damit hängen wir allerdings keinen neuen Absatz an, sondern nur ein paar Zeichen an den letzten Absatz. Außerdem gibt es eine elegantere Methode, um den letzten Absatz zu ermitteln. Zusammen sieht die verbesserte Variante so aus:

Dim rng As Word.Range
Set rng = objDoc.Paragraphs.Last.Range
rng.InsertAfter vbCr & "Test"

Wir referenzieren den letzten Absatz direkt mit dem Range des mit der Eigenschaft Last ermittelten letzten Absatzes. Schließlich stellen wir dem neuen Text einen Zeilenumbruch voran, wodurch der Text gleich in einem neuen Absatz landet.

Daten aus Tabelle in Dokument schreiben

Nutzen wir die bisherigen Erkenntnisse doch, um den Inhalt einer Access-Tabelle in ein Word-Dokument zu schreiben – und zwar Zeile für Zeile.

In diesem Beispiel wollen wir wie in Bild 5 den Inhalt der Felder ArtikelID und Artikelname der Tabelle tblArtikel in je eine Zeile eines neu zu erstellenden Word-Dokuments eintragen.

Per VBA eingetragene Absätze

Bild 5: Per VBA eingetragene Absätze

Dies gelingt in einer einfachen Schleife, wobei mit jedem Durchlauf der letzte Absatz als Range-Objekt referenziert wird und die gewünschten Werte dahinter eingefügt werden.

Da wir in diesem Fall gleich mit dem ersten Absatz beginnen, hängen wir den Zeilenumbruch (vbCr) hinten an den anzufügenden Ausdruck an (s. Listing 2).

Public Sub Artikelliste()
     Dim objDocument As Word.Document
     Dim rng As Word.Range
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Set objDocument = New Word.Document
     AppActivate objDocument.Name
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT ArtikelID, Artikelname FROM tblArtikel", _
         dbOpenDynaset)
     Do While Not rst.EOF
         Set rng = objDocument.Paragraphs.Last.Range
         rng.InsertAfter rst!ArtikelID & vbTab & rst!Artikelname & vbCr
         rst.MoveNext
     Loop
End Sub

Listing 2: Einfügen der Datensätze einer Tabelle in ein Word-Dokument

Zeichenformat festlegen

Nun möchten wir vielleicht jeweils die ArtikelID fett drucken oder den Artikelnamen kursiv. Gerade die Hervorhebung einzelner Textelemente ist ja ein großer Vorteil von Word gegenüber her-kömmlichen Textfeldern in Access-Berichten.

Dazu liefert das Range-Objekt einige interessante Eigen-schaften – mehr dazu weiter unten.

Mit Textmarken arbeiten

Normalerweise versteht man unter dem Arbeiten mit Textmarken, dass diese in einer Dokumentvorlage festgelegt und dann später etwa per VBA mit konkreten Inhalten gefüllt werden.

In unserem Fall sollen die Textmarken etwas flexibler eingesetzt werden – beispielsweise, um einen Text nachträglich zu formatieren.

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