CLS: Tipps & Tricks: Word-VBA



Tipps & Tricks: Word-VBA

Auf dieser Seite finden Sie 7 Tipps & Tricks für Word-VBA. Soweit keine Einschränkungen dazu genannt werden, gelten diese Tipps & Tricks für alle Versionen von Word.

Wenn in den folgenden Erläuterungen Texte wie MsgBox formatiert sind, handelt es sich um konkret einzugebende Inhalte wie VBA-Code oder die Eingabe von Werten. Menüs wie Datei Speichern sind wie hier zu sehen formatiert. Schaltflächen oder Registerkarten auf Dialogen werden wie Menüs behandelt.

Alle Tipps sind nach bestem Wissen geprüft, aber selbstverständlich ohne Gewähr. Sollten Sie doch einen Fehler darin entdecken, würden wir uns freuen, wenn Sie uns per eMail Bescheid sagen.

Seit dem Office-ServicePack2 verändert eine Formular-Datei beim Speichern ihr (per VBA mit Protect gesetztes) Passwort, so dass sie nach dem nächsten Öffnen nicht mehr den Passwort-Schutz aufheben kann. Folgendes Szenario erzeugt den Fehler:

Schritt 1: Erstellen Sie ein neues Dokument und führen Sie diesen Code aus:
Public Const p_cstrPW = "abc"

Sub MachZu()
    ActiveDocument.Protect wdAllowOnlyFormFields, True, p_cstrPW
End Sub

Schritt 2: Jetzt speichern Sie das Dokument.

(Das Passwort ist noch in Ordnung, wie Sie mit dem Code aus Schritt 3 jetzt beweisen könnten. Bitte speichern Sie aber nur das Dokument, ohne den Schutz aufzuheben, um den Fehler zu sehen! )

Schritt 3: Schließen Sie es und öffnen es wieder, jetzt ist das Passwort ungültig. Der Formularschutz für diese Datei lässt sich ab jetzt weder manuell noch mit diesem Code aufheben:
Sub MachAuf()
    ActiveDocument.UnProtect p_cstrPW
End Sub

Ein manuell vergebenes Passwort funktioniert weiterhin, es liegt klar am Protect-Befehl. Ohne das ServicePack 2 funktioniert das Passwort, mit der gleichen Datei und ServicePack 2 nicht mehr!

Es gab inzwischen einen ersten Hotfix (KB 969961), aber der war noch schlimmer: der Passwort-Fehler ist beseitigt, aber dafür werden die Inhalte der ActiveDocument.Variables-Collection ohne Fehlermeldung gelöscht und durch "????????" ersetzt. Seit Anfang Juli 2009 existiert ein zweiter Hotfix (KB 970942), der endlich beide Probleme löst und Word 2007/SP2 damit wieder funktionsfähig macht.

Während es in sonstigem VBA-Code durchaus üblich ist, das Vorhandensein einer Datei mit

If ActiveDocument Is Nothing Then
' ...hier beliebiger Code, falls kein Dokument offen ist
End If

abzufragen, führt das in Callback-Prozeduren von Ribbons zu Laufzeitfehlern. Sie können stattdessen die Anzahl der (geöffneten) Dokumente mit folgendem Code prüfen:

If Documents.Count = 0 Then
' ...hier beliebiger Code, falls kein Dokument offen ist
End If

Das ist robust und erzeugt keine Probleme.

In geschützten Formularen von Word ist es ausgesprochen lästig, dass die so genannten contextualTabs (also die Kontext-abhängigen Register) wie Tabellentools oder Kopf- und Fußzeilentools sichtbar werden. Das geschieht, wenn das Formular in einer Tabelle organisiert ist, ist da aber völlig überflüssig, weil die Tabellenbearbeitung in den geschützten Bereichen ja gar nicht möglich ist.

Wenn Sie den XML-Code in der jeweiligen Dokumentvorlage wie folgt ändern, werden die Kontext-abhängigen Register für deren Dokumente immer automatisch ausgeblendet:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" >
<ribbon startFromScratch="false">
<contextualTabs>
<tabSet idMso="TabSetTableTools" visible="false" />
<tabSet idMso="TabSetHeaderAndFooterTools" visible="false" />
</contextualTabs>
</ribbon>
</customUI>

Diese Änderung wirkt sich ausdrücklich nur auf das jeweilige Dokument aus. Parallel geöffnete Word-Dateien auf Basis anderer Dokumentvorlagen zeigen bei Bedarf auch diese Kontext-abhängigen Register wie gewohnt an.

In Word2003 war es kein Problem, eine Option umzustellen, das ging rasend schnell. Der folgende Code ändert eine beliebige Option:

Sub OptionenAendern()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 100
With Options
.Pagination = False
End With
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

Das dauert in Word2003 für 100 Wiederholungen weniger als 1 Sekunde, in Word2007 jedoch volle 24 Sekunden! Natürlich werden Sie in Ihrem VBA-Code nicht 100 Mal eine einzige Option ändern, aber das gleiche Verhalten gilt auch für die Änderung mehrerer Optionen und kostet richtig viel Zeit. Vor allem müssen Sie es nach ihrem Code ja auch wieder zurückstellen!

Wenn Sie trotzdem noch solchen Code beschleunigen wollen, dann müssen Sie vorher immer den Wert auslesen und nur ändern, wenn es nötig ist:

Sub OptionenAendernSchneller()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 100
With Options
If .Pagination <> False Then .Pagination = False
End With
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

In diesem Fall braucht es auch in Word2007 nur 1 Sekunde, was erklärbar ist, weil nach spätestens der ersten Änderung ja die Bedingung immer False ist. Aber auch für mehrere Optionen (bei denen jede einzelne so vorgeprüft wird!), ist eine deutliche Verbesserung festzustellen.

Auch das Range-Objekt ist in Word2007 deutlich langsamer geworden. Bisher war es selbstverständlich, dass nur mit Range-Objekten gearbeitet wurde, weil ein Select immer zu einer langsamen Bildschirmaktualisierung führt.

Inzwischen ist es umgekehrt, Sie sollten also Range-Objekte nicht in zeitkritischen Schleifen einsetzen. Das lässt sich anhand einer einfachen Prozedur nachmessen, die in der Datei eine Textmarke Test erwartet:

Sub AendereFontRange()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 1000
With ActiveDocument.Bookmarks("Test").Range.Font
.Name = "Tahoma"
End With
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

Diese Prozedur ist in Word2003 in 1 Sekunde fertig. Hätten Sie dort mit der Select-Methode gearbeitet, wären Sie damals mit 11 Sekunden bestraft worden:

Sub AendereFontSelect()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 1000
ActiveDocument.Bookmarks("Test").Select
With Selection.Range.Font
.Name = "Tahoma"
End With
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

In Word2007 ist es umgekehrt: die Prozedur AendereFontRange benötigt 10 Sekunden, aber AendereFontSelect "nur" 7 Sekunden.

Mit einem Griff in die Trickkiste weisen Sie nicht der Selection direkt die neuen Eigenschaften zu, sondern machen es über den (unsichtbaren) FormatZeichen-Dialog:

Sub AendereFontSelectDialog()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 1000
ActiveDocument.Bookmarks("Test").Select
With Dialogs(wdDialogFormatFont)
.Font = "Tahoma"
.Execute
End With
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

Und siehe da: die Prozedur AendereFontSelectDialog benötigt "nur noch" 5 Sekunden, das ist doppelt so schnell wie im ersten Versuch. Und nur noch fünfmal so langsam wie mit dem alten Word...

Es gibt einen gefährlichen Fehler in Word 2007, durch den unfreiwillig die Ribbons einer Datei gelöscht werden! Wer aus einer alten (*.dot- oder *.doc-)Datei per Zwischenablage Text mit einem Positionsrahmen in eine neue (*.dot?- oder *.doc?-)Datei kopiert, verliert in diesem Moment ohne Warnung alle XML-Daten für das Ribbon.

Alte Textfelder (statt Positionsrahmen) sind offenbar ungefährlich, aber das Kopieren neu erzeugter Textfelder aus Word2007-Dateien zerstört das Ribbon ebenso. Neue Textfelder lassen sich in der Word2007-Datei ohne Probleme anlegen.

Das Konvertieren der alten Datei in das neue Word2007-Format vor dem Kopieren hilft nicht gegen den Fehler.

Während in bisherigen Word-Versionen selbstverständlich längere Aktionen (Auslesen vieler Datensätze, Bearbeiten umfangreicher Textmarken, o.Ä.) mit einer Fortschritts-Anzeige in der Statuszeile begleitet wurde, muss für Word2007 (und Access2007, vermutlich auch für Excel2007) dringend von der ausführlichen Nutzung der Statuszeile abgeraten werden!

Der folgende Code macht nichts, ausser den Wert von lngZahl in der Statuszeile anzuzeigen:

Sub ZeigeStatus()
Dim datStart As Date
Dim datEnd As Date
Dim lngZahl As Long
 
datStart = Now()
For lngZahl = 1 To 10000
StatusBar = "Zahl " & lngZahl
Next
 
datEnd = Now()
MsgBox lngZahl - 1 & " Durchläufe in " & Format(datEnd - datStart, "hh:nn:ss")
End Sub

In Word2003 können Sie kaum etwas lesen, so schnell läuft diese Prozedur: sie braucht weniger als 1 Sekunde. Lassen Sie die gleiche Prozedur jedoch in Word2007 laufen, dauert es geschlagene 48 Sekunden, bis die Meldung erscheint!

Als "Lösung" bleibt eigentlich nur, die Statuszeile in Schleifen auf keinen Fall zu benutzen, Machen Sie lieber einen grafischen Fortschrittsbalken in einem Formular, das ist tatsächlich schneller!