Word-Briefe mit Access erstellen

Access-Stammtisch Stuttgart
www.access-stammtisch.de
Autor: Joachim Jauß


Download der Demo-Dateien

Bei größeren Access-Projekten stößt man schnell an die Grenzen der Access-Berichte. Diese eignen sich wunderbar, um fixe Listen mit Standardformaten auszugeben, oder auch die ein oder andere Zeichenfunktion auszugeben.
Meine Lösung heißt hier meist MS Word. Der Grund besteht darin, dass fest jeder der Access auf seinem System hat, auch mit Word arbeitet. Zumal ist der Umgang mit MS Word meist vertrauter als der Umgang mit den Access-Berichten. Somit hat der Anwender die Möglichkeit selbst Einfluß auf die Gestaltung zu nehmen.

Wo werden die Word-Briefe gespeichert?

Dies ist meist die zweite Frage die kurz nach der Entscheidung zur Ausgabe an Word folgt. Nachdem die Daten an Word abgeschickt wurden, weiß Access nichts mehr von Word. Word läuft als Exe-Server in einem eigenen Adressraum und kann nicht ohne weiteres wieder zu Access zurück.

Hier liegt der Ansatz für die folgende Demo:

Zugrunde liegt die Access-Anwendung WrdDemo.mdb (Access 97) sowie die Word-Vorlage Brief.dot. Die Word-Vorlage ist für diesen speziellen Einsatz etwas präpariert.
Sie enthält die für die Positionierung vorgesehenen Textmarken "Anrede", "Name", "Strasse", "Ort" und "Betreff". Und ein wenig VBA-Code der es ermöglicht, dass Word den Dateinamen in der Access-Datenbank selbst speichert.
Die Access-Anwendung enthält zwei Tabellen namens USysSettings in welcher der Pfad für die Vorlagendatei gespeichert wird, sowie eine Tabelle tblBriefe, in der die Briefe verwaltet werden. Das Formular frmWrdDemo wurde als Oberfläche entworfen. Als Kernstück für die Interaktion verwende ich eine separate Klasse "clsWord" die ich selbst ständig um weitere Methoden und Eigenschaften erweitere.

In dieser Demo kommen lediglich die Methoden OpenDoc, SetWordBookmark und Show zum Einsatz. Der Quellcode hierzu sieht folgendermaßen aus:
        Option Compare Database
        Option Explicit

        Private objWord As Word.Application
        Private objWordDoc As Word.Document
        Private objRange As Word.Range

        Private Const WORD_INITSTRING = "Word.Application"
        Private Const TWIPS = 567       'Ein Zentimeter = 567 twips

        '#################################################################################
        'Setzt den Wert einer Textmarke
        'SetWordBookmark
        '  strBookmark      = Name der Texmarke
        '  strText          = Text der eingefügt werden soll
        '  bInsert          = Fügt eine neue Textmarke in das Dokument ein (Standard = False)
        '  lngStartPosition = Die Startposition an der die Textmarke eingefügt werden soll
        '
        ' Rückgabewert      : Wurde eine neue Textmarke eingefügt, das Ende der Textmarke
        '                   : Konnte keine neue Textmarke einefügt werden lngStartPosition
        Public Function SetWordBookmark(strBookmark As String, _
                                        strText As String, _
                                        Optional bInsert As Boolean = False, _
                                        Optional lngStartPosition As Long = 0) As Long
        Dim objWordBookmark As Word.Bookmark
        Dim objWordRange As Word.Range
        Dim intLength As Integer

            intLength = Len(strText)
            If bInsert Then
                If Not objWordDoc.Bookmarks.Exists(strBookmark) Then
                    Set objWordRange = objWordDoc.Range(lngStartPosition, lngStartPosition)
                    objWordDoc.Bookmarks.Add strBookmark, objWordRange
                End If
            End If
            If objWordDoc.Bookmarks.Exists(strBookmark) Then
                Set objWordBookmark = objWordDoc.Bookmarks(strBookmark)
                objWordBookmark.End = objWordBookmark.Start
                objWordBookmark.Range.Text = strText
                objWordBookmark.End = objWordBookmark.Start + intLength
                SetWordBookmark = objWordBookmark.End
                objRange.Start = objWordBookmark.End
                objRange.End = objRange.Start
            Else
                SetWordBookmark = lngStartPosition
            End If
        End Function

        'Öffnet ein Dokument oder erstellt ein neues Dokument aufgrund einer Vorlage
        Public Function OpenDoc(strDocument As String, bIsTemplate As Boolean) As Word.Document
            If bIsTemplate Then
                Set objWordDoc = objWord.Documents.Add(Template:=strDocument)
            Else
                Set objWordDoc = objWord.Documents.Open(strDocument, , , False)
            End If
            Set objRange = objWord.ActiveDocument.Range
            objRange.Start = 1
            objRange.End = objRange.Start
            'benutzerdefinierte Eigenschaft des Word-Dokuments ansprechen
            On Error Resume Next
            objWordDoc.DataFilename = CurrentDb.Name
            Err = 0
            On Error GoTo 0
            Set OpenDoc = objWordDoc
        End Function

        Public Sub Show()
            objWordDoc.Windows(1).View.ShowBookmarks = False
            objWord.Visible = True
            objWord.WindowState = wdWindowStateMaximize
            objWord.Activate
        End Sub

        Private Sub Class_Initialize()
            'Initialisieren der Objekt-Variablen
            Set objWord = Nothing
            Set objWordDoc = Nothing
            Set objRange = Nothing
            'Initialisieren des Word-Objectes
            On Error Resume Next
                Set objWord = GetObject(, WORD_INITSTRING)
                If Err Then
                    Set objWord = New Word.Application
                End If
            On Error GoTo 0
        End Sub

        Private Sub Class_Terminate()
            Set objRange = Nothing
            Set objWordDoc = Nothing
            Set objWord = Nothing
        End Sub

Damit das Ganze auch funktioniert sollte über Extras/Verweise ein Verweis auf die Word-Bibliothek gesetzt werden.

Der Code, der sich hinter dem Formular versteckt, beschränkt sich darauf eine Objektvariable des Typs clsWord anzulegen und die entsprechenden Methoden in der richtigen Reihenfolge aufzurufen. Auf eine differenzierte Fehlerbehandlung hab ich der Einfachheit halber verzichtet.

Etwas ungewöhlicher wird es, wenn wir den Code hinter der Word-Vorlage anschauen.

     

'Konstanten für den Zugriff auf die Access-Datenbank Private Const VAR_DATAFILE = "varDataFile" Private Const TBL_NAME = "tblBriefe" Private Const TBL_BETREFFFELD = "Betreff" Private Const TBL_FILENAMEFIELD = "Datei" Private Const DIALOG_TITLE = "Word - Demo"

Zunächst einmal habe ich die Vorlage und somit auch den späteren Brief um die Eigenschaft DataFilename erweitert. Dies funktioniert wie in Access auch mit den Property Get / Property Let - Prozuduren.

Public Property Get DataFilename() As String On Error Resume Next DataFilename = Me.Variables.Item(VAR_DATAFILE).Value End Property Public Property Let DataFilename(strDataFilename As String) On Error Resume Next Me.Variables.Add Name:=VAR_DATAFILE, Value:=strDataFilename End Property

Die Property Let - Prozedur fügt dem Dokument eine neue benutzerdefinierte Variable hinzu, die ihren Inhalt auch über die Laufzeit der aktuellen Word-Sitzung hinaus behält. In Ihr wird der Pfad zu Access-Datendatei gespeichert in der sich die Tabelle für die Briefverwaltung befindet.

Die Property Get - Funktion liefert den Inhalt der Variablen.

Leider löst das Document-Objekt von Word zum Zeitpunkt des Speichern kein Ereignis aus. Aus diesem Grund wird beim Ereignis Document_Close überprüft, ob das Dokument auch gespeichert wurde. Ist dies nicht der Fall, bekommt der Anwender noch vor der Word-eigenen Aufforderung zum Speichern eine Messagebox angezeigt. Bestätigt er dies mit ja, wird der Save-Dialog von Word aufgerufen und das Dokument gespeichert.

Die Do Until...Loop zwingt den Benutzer entweder zu speichern, oder in der dokumenteneigenen Meldung auf Nein zu klicken. Anschließend wird die Eigenschaft DataFilename ausgelesen. Der folgende Code dürfte den eingefleischten Access-Programmierern bekannt sein. Eine Access-Datei wird in einem eigenen Arbeitsbereich geöffnet und in eine Tabelle ein neuer Datensatz eingetragen. Um nun die Aufforderung zum Speichern von Word zu verhindern, wird die Saved-Eigenschaft des Dokuments auf True gesetzt.

        

'Das Ereignis Close wird von Word aufgerufen, wenn das 'Dokument geschlossen wird. 'Zuerst wird geprüft, ob das Dokument gespeichert wurde und 'die Dokumentvariablen mit Werten gefüllt sind. Private Sub Document_Close() Dim ws As Workspace Dim db As DATABASE Dim rsBriefe As RECORDSET Dim strDataFilename As String Dim strTablename As String Dim bDocIsSaved As Boolean 'Prüfen ob das Dokument schon gespeichert wurde bDocIsSaved = Me.Saved Do Until bDocIsSaved If (MsgBox("Das Dokument ist noch nicht gespeichert!" & vbCrLf & _ "Möchten Sie nun speichern?", vbYesNo, DIALOG_TITLE) = vbYes) Then On Error Resume Next 'Beim Abbrechen des Speicherns durch den Benutzer tritt der 'Fehler 4198 auf Me.Save bDocIsSaved = Me.Saved Else bDocIsSaved = True End If Loop On Error GoTo 0 strDataFilename = CStr(Me.DataFilename) 'Wenn gültige Werte eingetragen wurden: If Me.Saved Then If (Len(strDataFilename) > 0) Then Set ws = DBEngine.CreateWorkspace("WordSpeicherFunktion", "Admin", "") If (Len(Dir(strDataFilename)) > 0) Then Set db = ws.OpenDatabase(strDataFilename) Set rsBriefe = db.OpenRecordset(TBL_NAME, dbOpenDynaset) rsBriefe.AddNew rsBriefe.Fields(TBL_BETREFFFELD) = Me.Bookmarks("Betreff").Range.Text rsBriefe.Fields(TBL_FILENAMEFIELD) = Me.Path & "\" & Me.Name rsBriefe.Update rsBriefe.Close db.Close Else MsgBox "Der Dateipfad der Datendatei hat sich geändert!" & _ " Ein Speichern des Dateinamens ist nicht möglich", vbOKOnly, DIALOG_TITLE End If ws.Close Else MsgBox "Die Dokumentvariablen enthalten ungültige Werte." & vbCrLf & _ "Die Datendatei kann deshalb nicht aktualisiert werden.", vbOKOnly, DIALOG_TITLE End If End If Me.Saved = True End Sub

Nun die Nachteile:
Wie Sie am Listenfeld im Formular frmWrdDemo sehen werden muß das Listenfeld manuell aktualisiert werden. Dies kann von Word aus nicht ohne weiters übernommen werden. Ist aber durchaus machbar :-).
Wie schon angesprochen bieten sich im Beispiel viele Fehlerquellen, die zu unerwünschten Dialogboxen führen können.
Für eine Demo sollte es allerdings genügen.