Lektionen/Automation

Word aus Access: Serienbriefe

Profi12 Min. Lesezeit

Serienbriefe sind der Klassiker der Office-Automation: Aus einer Access-Tabelle sollen individuelle Briefe entstehen — jeder mit Anrede, Adresse und Betrag des jeweiligen Kunden. Word bringt zwar eine eigene Seriendruckfunktion mit, doch aus Access heraus hast du mit Automation die volle Kontrolle: Du öffnest eine Vorlage, füllst Platzhalter, speicherst als PDF und erzeugst in einer Schleife beliebig viele Dokumente.

Diese Lektion zeigt den kompletten Weg — inklusive des sauberen Beendens, damit kein WINWORD.EXE-Geisterprozess zurückbleibt.

Das Word-Objektmodell

Wie bei Excel steuerst du Word über sein Objektmodell: An der Spitze die Word.Application, darunter Documents (offene Dokumente), darunter der Inhalt mit Bookmarks (Textmarken), Range und Content. Von Access aus greifst du darauf zu, als wärst du in Words eigenem VBA.

Für IntelliSense setzt du im VBE unter Extras → Verweise die Microsoft Word xx.x Object Library. Die folgenden Beispiele nutzen Late Binding über CreateObject, damit sie ohne Verweis und versionsübergreifend laufen; die nötigen Konstanten schreibe ich als Zahl dazu.

Die Vorlage vorbereiten

Zwei Techniken sind gebräuchlich, um Platzhalter im Dokument zu füllen:

TechnikVorlage enthältVorteilNachteil
Textmarken (Bookmarks)benannte Textmarkenpräzise, mehrfach nutzbarTextmarke „verbraucht" sich beim Ersetzen
Find/ReplacePlatzhaltertext wie «Name»einfach, gut lesbarersetzt jedes Vorkommen

Für Textmarken legst du in Word an der Einfügestelle über Einfügen → Textmarke eine benannte Marke an (z. B. Anrede, Adresse). Für Find/Replace schreibst du sichtbare Platzhalter wie {Name} oder «Name» in die Vorlage.

Speichere die Vorlage als .dotx (echte Word-Vorlage) oder als normales .docx, von dem du eine Kopie öffnest.

Dokument aus Vorlage öffnen und Textmarken füllen

Documents.Add mit dem Vorlagenpfad erzeugt ein neues Dokument auf Basis der Vorlage — die Vorlage selbst bleibt unangetastet. Textmarken erreichst du über doc.Bookmarks("Name").Range.Text.

Public Sub BriefAusVorlage()
    Dim wdApp As Object
    Dim doc As Object

    Set wdApp = CreateObject("Word.Application")
    wdApp.Visible = True                     ' Word-Fenster zeigen

    ' Neues Dokument aus der Vorlage
    Set doc = wdApp.Documents.Add("C:\Vorlagen\Brief.dotx")

    ' Textmarken füllen
    FuelleTextmarke doc, "Anrede", "Sehr geehrter Herr Müller"
    FuelleTextmarke doc, "Adresse", "Hauptstr. 5, 12345 Berlin"
    FuelleTextmarke doc, "Betrag", Format(1234.5, "#,##0.00 €")

    doc.SaveAs2 "C:\Briefe\Mueller.docx"
End Sub

' Hilfsroutine: setzt Text an einer Textmarke und erhält die Marke
Private Sub FuelleTextmarke(doc As Object, marke As String, wert As String)
    Dim rng As Object
    If doc.Bookmarks.Exists(marke) Then
        Set rng = doc.Bookmarks(marke).Range
        rng.Text = wert
        doc.Bookmarks.Add marke, rng        ' Textmarke neu setzen
    End If
End Sub

Der Trick in der Hilfsroutine: Beim Schreiben in Range.Text verschwindet die Textmarke. Mit doc.Bookmarks.Add marke, rng legst du sie über dem neuen Text wieder an — so bleibt sie für spätere Zugriffe erhalten.

Alternative: Find/Replace

Wenn du lieber sichtbare Platzhalter verwendest, ersetzt du sie über das Find-Objekt des Dokumentinhalts. Content.Find.Execute mit Replace:=2 (= wdReplaceAll) tauscht alle Vorkommen:

Private Sub Ersetze(doc As Object, platzhalter As String, wert As String)
    With doc.Content.Find
        .Text = platzhalter
        .Replacement.Text = wert
        .Execute Replace:=2                 ' 2 = wdReplaceAll
    End With
End Sub

' Aufruf:
' Ersetze doc, "{Name}", "Herr Müller"
' Ersetze doc, "{Betrag}", "1.234,50 €"

Viele Briefe über ein Recordset

Der eigentliche Serienbrief entsteht in einer Schleife über ein Recordset: Für jeden Kunden öffnest du ein frisches Dokument aus der Vorlage, füllst es und speicherst es — hier gleich als PDF.

Public Sub SerienbriefErzeugen()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim wdApp As Object
    Dim doc As Object
    Dim pfad As String

    Set db = CurrentDb
    Set rs = db.OpenRecordset( _
        "SELECT Anrede, Name, Adresse, Betrag FROM tblKunden", _
        dbOpenSnapshot)

    Set wdApp = CreateObject("Word.Application")
    wdApp.Visible = False                    ' im Hintergrund, schneller

    Do Until rs.EOF
        Set doc = wdApp.Documents.Add("C:\Vorlagen\Brief.dotx")

        Ersetze doc, "{Anrede}", Nz(rs!Anrede, "")
        Ersetze doc, "{Name}", Nz(rs!Name, "")
        Ersetze doc, "{Adresse}", Nz(rs!Adresse, "")
        Ersetze doc, "{Betrag}", Format(Nz(rs!Betrag, 0), "#,##0.00 €")

        ' Dateiname pro Kunde
        pfad = "C:\Briefe\" & rs!Name & ".pdf"

        ' Als PDF exportieren: Format 17 = wdExportFormatPDF
        doc.ExportAsFixedFormat OutputFileName:=pfad, ExportFormat:=17

        doc.Close SaveChanges:=False         ' Dokument ohne Speichern schließen
        Set doc = Nothing

        rs.MoveNext
    Loop

    ' Aufräumen
    wdApp.Quit
    Set wdApp = Nothing
    rs.Close
    Set rs = Nothing
    Set db = Nothing

    MsgBox "Serienbrief fertig."
End Sub

Beachte wdApp.Visible = False: Im Hintergrund ist der Massenlauf spürbar schneller, weil Word nichts neu zeichnen muss. Nz() fängt Null-Werte ab, sonst bricht die Ersetzung mit Fehler ab.

Als PDF speichern

Das Herzstück ist ExportAsFixedFormat. Es erzeugt eine PDF ohne Umweg über einen Druckertreiber:

doc.ExportAsFixedFormat _
    OutputFileName:="C:\Briefe\Brief.pdf", _
    ExportFormat:=17           ' 17 = wdExportFormatPDF

Mit Early Binding kannst du statt der 17 die Konstante wdExportFormatPDF schreiben. Für den XPS-Export gäbe es 18 (wdExportFormatXPS) — für Briefe ist PDF der Standard.

Sauber beenden — der Geisterprozess

Wie bei Excel gilt: Wer Word unsichtbar startet und die Objektvariablen einfach fallen lässt, hinterlässt WINWORD.EXE als Geisterprozess im Task-Manager. Er belegt Speicher und kann die Vorlagendatei sperren.

Die Regel ist dieselbe: erst alle Dokumente schließen, dann Quit, dann jede Objektvariable auf Nothing — von innen nach außen. Und: jeden Zugriff mit wdApp. oder doc. qualifizieren, denn unqualifizierte Aufrufe erzeugen versteckte Referenzen, die das Beenden verhindern.

Damit auch bei einem Fehler mitten in der Schleife aufgeräumt wird, gehört alles in einen Fehlerbehandler:

Public Sub SerienbriefRobust()
    Dim wdApp As Object, doc As Object

    On Error GoTo Aufraeumen
    Set wdApp = CreateObject("Word.Application")
    wdApp.Visible = False

    Set doc = wdApp.Documents.Add("C:\Vorlagen\Brief.dotx")
    ' ... füllen und exportieren ...
    doc.ExportAsFixedFormat "C:\Briefe\Test.pdf", 17
    doc.Close SaveChanges:=False
    Set doc = Nothing

Aufraeumen:
    If Err.Number <> 0 Then MsgBox "Fehler: " & Err.Description
    On Error Resume Next
    If Not doc Is Nothing Then doc.Close SaveChanges:=False
    If Not wdApp Is Nothing Then wdApp.Quit
    Set doc = Nothing
    Set wdApp = Nothing
End Sub

Falle: Setze wdApp.Quit nie, während noch ein Dokument mit ungespeicherten Änderungen offen ist und Word unsichtbar läuft — dann hängt der Prozess auf einer unsichtbaren Rückfrage („Speichern?"). Schließe Dokumente immer explizit mit Close SaveChanges:=False.

Zusammengefasst

  • Word-Automation läuft über Word.ApplicationDocuments.Add(Vorlage)Bookmarks/Find zum Füllen der Platzhalter.
  • Textmarken sind präzise (nach dem Schreiben mit Bookmarks.Add neu setzen), Find/Replace mit Execute Replace:=2 ist einfacher bei sichtbaren Platzhaltern.
  • Eine Schleife über ein Recordset erzeugt pro Datensatz ein Dokument; Nz() fängt Null-Werte ab.
  • ExportAsFixedFormat mit ExportFormat:=17 speichert direkt als PDF, ganz ohne Druckertreiber.
  • Immer Dokumente mit Close SaveChanges:=False schließen, dann Quit, dann alle Objekte auf Nothing — sonst bleibt ein WINWORD.EXE-Geisterprozess.
Nächste Lektion
Klassenmodule: eigene Objekte