Veröffentlicht: 09. Nov 2000 | Aktualisiert: 15. Jun 2004
Objekte anbieten zu können, ist kein Privileg von Office. Über Klassen sind auch VBA-Programmierer in der Lage, die Funktionalität einer Office-Anwendung gezielt um neue Objekte zu erweitern. Zwar nicht ganz so elegant, wie Office es mit seinen fest eingebauten Objekten kann, doch auf jeden Fall objektkonform.
Auf dieser Seite
Eigene Objekte erzeugen
Das neue Tabellen-Objekt
Das Tabellen-Objekt wird erweitert
Fazit
Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:
Am Beispiel eines neuen Tabellen-Objekts möchte ich Ihnen zeigen, wie sich einige Einschränkungen des Table-Objekts von Word umgehen lassen. Dabei lernen Sie auch die Grenzen der Erweiterbarkeit durch Klassen kennen. Doch bevor wir uns dieser Aufgabe widmen, soll die Frage beantwortet werden, wie sich die Office-Objekte von außen, sprich von einer anderen Anwendung aus, ansprechen lassen.
Office-Veteranen dürfte DDE (Dynamic Data Exchange) noch in guter Erinnerung sein, das Word 6.0 und Access 2.0 zusammen brachte und für die damaligen Anforderungen eine hervorragende Lösung anbot. DDE wird zwar auch von Office 2000 unterstützt, aber außer von der Anwendung selbst (Serienbrief-Funktion) kaum noch benutzt.
Die Nachfolger von DDE sind die Objekte, die auf der Grundlage des Component Object Models (COM) den Zugang auf die Funktionalität einer Anwendung von innen (über VBA) und von außen (über eine beliebige Programmiersprache) regeln. Alle Office-Applikationen und viele andere Anwendungen liegen als COM-Server vor und lassen sich daher von anderen Programmen ansprechen. Das bedeutet, dass sich ihre Objekte so verhalten, als wären sie Teil der Anwendung, die auf den COM-Server zugreift. Dieses Prinzip wird als Automation bezeichnet. Damit Anwendung A auf die Objekte von Anwendung B zugreifen kann, müssen folgende Bedingungen erfüllt sein:
-
Die Objekte müssen von VBA oder einer Skriptsprache aus ansprechbar sein. Das ist bei den Office-Objekten durchweg der Fall, bei COM-Komponenten aber nicht selbstverständlich.
-
Es muss eine Typenbibliothek vorhanden sein, die die Namen der Objekte und der Eigenschaften und Methoden enthält. Auch das ist bei Office-Objekten durchweg der Fall.
-
Das Objekt muss über die CreateObject-Funktion instanzierbar sein.
Die erste Bedingung ist ein absolutes Muss, denn besitzt eine COM-Komponente eine aus der Sicht von VBA inkompatible Schnittstelle, lässt sie sich nicht ansprechen. Die zweite Bedingung ist in der Office-Programmierung der Normalfall. Möchten Sie etwa von Word auf die Outlook-Objekte zugreifen, fügen Sie im VBA-Editor über den Menübefehl Extras/Verweise einen Verweis auf die zuständige Typenbibliothek ein. Anschließend können die Outlook-Objekte so angesprochen werden, als wären sie von Anfang an ein Teil von Word.
Die dritte Bedingung wird relativ selten angewendet. Das könnte sich in Zukunft jedoch ändern. Eine gerade im Zusammenhang mit Office 2000 populärer werdende Methode besteht im Zugriff auf COM-Komponenten von einer Skriptsprache aus, etwa von VBScript. Skriptsprachen sind nicht in der Lage, den Inhalt einer Typenbibliothek auszuwerten. Sie sind auf eine Hilfskonstruktion angewiesen, die in der COM-Terminologie als IDispatch-Schnittstelle bezeichnet wird und die bei VBScript über die CreateObject-Funktion aufgerufen wird. Greift ein Programm über die Funktionen der IDispatch-Schnittstelle auf eine COM-Komponente zu, muss es vor dem Zugriff auf eine Eigenschaft oder Methode über einen zusätzlichen Funktionsaufruf erst erfragen, ob sie überhaupt zur Verfügung steht. Das klingt ein wenig umständlich, bietet aber den unschätzbaren Vorteil, dass der Zugriff sehr einfach ist. Das können Sie ausprobieren, indem Sie Notepad starten und die folgenden Zeilen eingeben, wenn der Windows Scripting Host auf Ihrem Rechner installiert ist:
Set wdApp = CreateObject("Word.Application.9")
wdApp.Documents.Add
wdApp.Selection.TypeText "Dieser Satz kommt direkt von VBScript!!! "
wdApp.ActiveDocument.SaveAs "ScriptRuftWord"
wdApp.Quit
Speichern Sie die Textdatei unter dem Namen ScriptRuftWord.vbs und öffnen Sie die Datei. Nach einem kurzen Augenblick befindet sich im aktuellen Verzeichnis eine Word-Datei mit dem Namen ScriptRuftWord.doc. Sie wurde von dem Skript erzeugt (Bild 2).
Selbstverständlich ist diese Art der Programmierung keine Alternative zu VBA, zumal das obige Skript genauso unter VBA funktioniert. Sie bietet sich an, wenn VBA nicht zur Verfügung steht, wie es beim Windows Scripting Host oder innerhalb eines HTML-Dokuments der Fall ist. Und es fällt sofort ein Nachteil auf. Aufgrund der fehlenden Typenbibliothek ist keine frühe Bindung möglich. Deshalb müssen Objektvariablen als As Object deklariert werden, wobei das stets implizit geschieht, da in VBScript bisher keine Datentypen angegeben werden können. Die späte Bindung bringt deutliche Performance-Nachteile mit sich und ist weniger komfortabel zu programmieren. Die Skriptprogrammierung wird aber im Zusammenhang mit HTML-Dokumenten, Datenzugriffsseiten und Web-Komponenten in der Office-Programmierung eine immer wichtigere Rolle spielen.
Lassen Sie uns zum Abschluss die CreateObject-Funktion etwas genauer unter die Lupe nehmen. Ihre Aufgabe ist es, eine COM-Komponente zu aktivieren, die über die bereits angesprochene IDispatch-Schnittstelle verfügen muss. Der Name, der in Klammern übergeben wird, ist nicht der Name der Komponente. Er besteht aus einer sehr langen Zahlenfolge, der sogenannten ClassID, die in der Registry eingetragen ist und die für den Namen der zu aktivierenden Komponente steht. Sehen Sie sich in der Registry den Eintrag Word.Application.9 an, werden Sie feststellen, dass der zuständige Eintrag LocalServer32 auf die Datei winword.exe verweist (Bild 1).
Das bedeutet, dass CreateObject nichts anderes macht, als Word zu starten. Dass Word dabei nicht sichtbar wird und die CreateObject-Funktion die Adresse des Application-Objekts erhält, liegt an dem Kommandozeilenargument /Automation, das beim Aufruf von winword.exe übergeben wird. Übrigens ist das Application-Objekt neben dem Document-Objekt bei Word das einzige Objekt, das sich von außen über CreateObject instanzieren lässt. Alle übrigen Objekte müssen über eines dieser beiden Automationsobjekte angesprochen werden. Die CreateObject-Funktion ist seit VBA 6.0 netzwerkfähig, so dass sie eine COM-Komponente auch auf anderen Netzwerkrechnern instanzieren kann.
Eigene Objekte erzeugen
Das Objektmodell einer Office-Anwendung ist ein festes Gebilde und nicht erweiterbar, denn Erweiterungen jeglicher Art würden Änderungen an der Programmdatei voraussetzen. Auch ist es in Office nicht möglich, vorhandene Objekte um neue Eigenschaften und Methoden zu erweitern oder die Arbeitsweise der vorhandenen Eigenschaften und Methoden zu modifizieren. Vielleicht wird ein zukünftiges Office diesen Komfort bieten. Das Component Object Model (COM) sieht die Möglichkeit prinzipiell vor.
Mit den in Office 2000 eingeführten Web-Komponenten rückt das gezielte Erweitern von Office-Funktionalität zumindest etwas näher. Bauen Sie sie beispielsweise mit Hilfe von Visual Basic in ein neues ActiveX-Steuerelement ein, hat es die Funktionalität der Web-Komponente. Das lässt sich mit VBA bisher nicht realisieren. Deshalb geht es im Folgenden um die Frage, wie sich die vorhandenen Objekte einer Office-Anwendung funktional erweitern lassen.
Ein neues Objekt zu definieren, ist grundsätzlich kein Problem und etwas, das sich auch weniger erfahrene VBA-Programmierer zutrauen können. Objekte werden in VBA durch Klassenmodule repräsentiert. Jedes Klassenmodul, das in ein Projekt eingefügt wird, steht für ein neues Objekt, dessen Eigenschaften, Methoden und seit VBA 6.0 auch Ereignisse frei festgelegt werden können. Aktiviert und damit nutzbar wird ein solches Objekt jedoch erst, wenn es, zum Beispiel über einen Dim-Befehl, instanziert wird. Allerdings ist seine Lebensdauer auf die Lebensdauer des VBA-Projekts beschränkt. Wird das Dokument geschlossen oder die Anwendung beendet, sterben auch alle instanzierten Objekte. Grundsätzlich ist das kein Nachteil. Sie müssen lediglich dafür sorgen, dass alle benötigten Objekte direkt nach dem Start der Office-Anwendung oder dem Laden eines Dokuments automatisch instanziert werden. Dann sieht es so aus, als wären sie ein fester Bestandteil des Objektmodells der Office-Anwendung.
Doch warum sollte man sich überhaupt die Mühe machen und Office um zusätzliche Objekte erweitern? Die vorhandenen Objekte leisten nicht immer das, was Sie von ihnen erwarten. Ein Beispiel ist das Table-Objekt von Word, das leider keine Möglichkeit bietet, eine Tabelle über ihren Namen anzusprechen. Enthält ein Dokument mehrere Tabellen, müssen Sie entweder ihre Position kennen, um sie ansprechen zu können, oder Sie behelfen sich mit Tricks wie ausgeblendeten Zeichen in der ersten Zelle. Außerdem fehlt dem Table-Objekt ein Ereignis, das bei einer Änderung der Tabelle ausgelöst wird. Sie können die Funktionalität hinzufügen, indem Ihre Klasse über den RaiseEvent-Befehl ein Ereignis auslöst. Auch wenn diese Erweiterung gewissen Einschränkungen unterworfen ist, kann sie die Programmierung von Tabellen zum einen erleichtern und macht zum anderen das Prinzip deutlich, nach dem Office-Objekte mit neuen Fähigkeiten ausgestattet werden.
Das neue Tabellen-Objekt
Im Folgenden wird auf der Grundlage eines Klassenmoduls ein Tabellen-Objekt definiert, mit dem sich die Verwaltung von Tabellen in einem beliebigen Word-Dokument vereinfachen lässt. Dabei geht es nicht darum, das vorhandene Table-Objekt zu kopieren oder um neue Eigenschaften und Methoden zu erweitern, sondern die zu einem Dokument gehörigen Table-Objekte über ein übergeordnetes Objekt ansprechen zu können. Das neue Tabellen-Objekt besitzt eine Name- und eine Überschrift-Eigenschaft, die das Range-Objekt zurückgibt, das oberhalb einer Tabelle steht. Alle Table-Objekte können wahlweise über das neue Objekt oder wie bisher direkt angesprochen werden.
Im Folgenden lernen Sie das Grundgerüst einer Klasse kennen, die als Bestandteil der globalen Dokumentvorlage Normal.dot automatisch mit dem Start von Word instanziert wird. Das neue Tabellen-Objekt wirkt dadurch wie ein Bestandteil des Word-Objektmodells, das Sie genauso nutzen können wie die Word-Objekte.
Führen Sie zur Implementation der neuen Klasse CTabellen und eines davon abgeleiteten Objekts mit dem Namen Tabellen die folgenden Schritte aus:
-
Laden Sie die Dokumentvorlage Normal.dot als Dokument.
-
Wählen Sie im VBA-Editor das Projekt Normal aus.
-
Fügen Sie zum Projekt ein Klassenmodul hinzu, das Sie CTabellen nennen.
-
Fügen Sie in den Deklarationsteil die folgenden beiden Befehle ein:
Private mcol_Tabellen As Collection
Private mlng_Zähler As Integer
Dadurch wird ein Collection-Objekt deklariert, in dem später alle Table-Objekte zusammengefasst werden.
Function Hinzufügen(tabTemp As Table) As Table
mlng_Zähler = mcol_Tabellen.Count + 1
mcol_Tabellen.Add Item:=tabTemp, _
Key:="Tabelle" & CStr(mlng_Zähler)
End Function
Sie fügt ein Table-Objekt zur Auflistung mcol_Tabellen hinzu, wobei für jede Tabelle ein Name generiert wird, der aus dem Wort Tabelle und einer fortlaufenden Nummer besteht. Er dient als Schlüssel für den späteren Zugriff auf die Auflistung.
Function AlleTabellenEntfernen()
Set mcol_Tabellen = Nothing
mlng_Zähler = 0
End Function
Die Methode wird vor dem Aufbau der Tabellen-Auflistung benutzt, wenn das Dokument geändert wird.
Function Item(varIndex As Variant) As Table
Set Item = mcol_Tabellen.Item(varIndex)
End Function
Über die Item-Methode wird auf ein Table-Objekt der Auflistung zugegriffen. Als Index wird entweder eine Zahl oder der interne Name einer Tabelle übergeben, zum Beispiel Tabelle1.
Property Get Überschrift(varIndex) As Range
Set Überschrift = mcol_Tabellen.Item(varIndex) _
.Range.Previous(Unit:= _
wdParagraph, Count:=1)
End Property
Durch die Übergabe des Namens oder der Nummer einer Tabelle erhalten Sie das Range-Objekt, das vor der Tabelle steht. Sie können mit der Eigenschaft eine Überschrift festlegen oder sie abfragen. Betrachten Sie die Eigenschaft lediglich als ein Provisorium, das zu eigenen Experimenten anregen soll.
Function Anzahl() As Long
If Not mcol_Tabellen Is Nothing Then
Anzahl = mcol_Tabellen.Count
Else
Anzahl = 0
End If
End Function
Private Sub Class_Initialize()
Set mcol_Tabellen = New Collection
End Sub
Wird die Klasse instanziert, wird auch die Auflistung instanziert.
Nun muss dafür gesorgt werden, dass die Klasse CTabellen mit dem Start von Word automatisch instanziert wird. Das lässt sich am besten mit dem New-Ereignis des Document-Objekts der Normal.dot erledigen, da es jedes Mal ausgelöst wird, wenn ein neues Dokument auf der Grundlage von Normal.dot angelegt wird.
-
Wählen Sie im Projekt-Explorer das Modul Document im Projekt Normal, zeigen Sie das Code-Fenster an und wählen Sie aus der Objektliste den Eintrag Document aus, um die Ereignisprozedur Document_New zu erzeugen. Fügen Sie den folgenden Befehl ein, um das Tabellen-Objekt zu instanzieren:
Set Tabellen = New CTabellen
Public Tabellen As CTabellen
-
Da eine Variable keine private Klasse als Rückgabewert besitzen darf, muss die Instancing-Eigenschaft des Klassenmoduls den Wert 2-PublicNotCreatable haben. Das bedeutet, dass die Klasse in anderen Modulen des Projekts, nicht aber von außen, sprich von anderen Anwendungen, instanziert werden kann.
Ziehen wir eine kurze Zwischenbilanz. Sie verfügen jetzt über eine globale Dokumentvorlage Normal.dot, die ein Tabellen-Objekt instanziert, wenn ein neues Dokument erzeugt wird. Mehr passiert nicht. Immerhin ist es möglich, alle Table-Objekte des aktuellen Dokuments zum Tabellen-Objekt hinzuzufügen:
Sub Test()
Dim tabTemp As Table
For Each tabTemp In ActiveDocument.Tables
ThisDocument.Tabellen.Hinzufügen tabTemp
Next
End Sub
Doch wo wird diese Prozedur ausgeführt? Zum Beispiel in einem allgemeinen Modul, das zum Projekt des aktuellen Dokuments gehört. Es ist wichtig zu verstehen, dass das Tabellen-Objekt nicht Teil von Normal.dot ist, auch wenn es dort deklariert und instanziert wird. Es gehört zu jenem Dokument, auf das sich das New-Ereignis bezieht. Werden zu diesem Objekt zum Beispiel drei Tabellen hinzugefügt, lässt sich die zweite Tabelle über ihren Namen ansprechen, den sie automatisch beim Hinzufügen zur Auflistung erhalten hat:
AnzahlSpalten = Tabellen.Item("Tabelle2").Columns.Count
oder
AnzahlSpalten = Tabellen.Item(1).Columns.Count
Das Tabellen-Objekt wird erweitert
Das bisher umgesetzte Beispiel ist hoffentlich lehrreich, allzu praktisch ist es aber noch nicht. So wäre es sinnvoll, wenn das Tabellen-Objekt über die vorhandenen Table-Objekte Bescheid wüsste, so dass Sie auf jedes Table-Objekt zugreifen könnten, ohne es erst hinzufügen zu müssen. Dazu muss gewährleistet sein, dass mit dem Einfügen einer Tabelle das neue Table-Objekt zur Auflistung hinzugefügt und beim Löschen einer Tabelle das entsprechende Table-Objekt wieder entfernt wird.
|
Listing1: Das Klassenmodul CTabellen.
Option Explicit
Private WithEvents mApp As Application
Event TabellenChange(Table As Table)
Private mcol_Tabellen As Collection
Private mlng_Zähler As Integer
Function Hinzufügen(tabTemp As Table) As Table
mlng_Zähler = mcol_Tabellen.Count + 1
mcol_Tabellen.Add Item:=tabTemp, _
Key:="Tabelle" & CStr(mlng_Zähler)
End Function
Function AlleTabellenEntfernen()
Set mcol_Tabellen = Nothing
mlng_Zähler = 0
End Function
Function Item(varIndex As Variant) As Table
Set Item = mcol_Tabellen.Item(varIndex)
End Function
Property Get Überschrift(varIndex) As Range
Dim tabTemp As Table
If TypeOf varIndex Is Table Then
For Each tabTemp In mcol_Tabellen
If varIndex.Range.InRange(tabTemp _
.Range) = True Then
Set Überschrift = tabTemp.Range. _
Previous(Unit:=wdParagraph, Count:=1)
Exit Property
End If
Next
Set Überschrift = Nothing
Else
Set Überschrift = mcol_Tabellen.Item _
(varIndex).Range.Previous(Unit:= wdParagraph, Count:=1)
End If
End Property
Function Anzahl() As Long
If Not mcol_Tabellen Is Nothing Then
Anzahl = mcol_Tabellen.Count
Else
Anzahl = 0
End If
End Function
Private Sub Class_Initialize()
Set mcol_Tabellen = New Collection
Set mApp = Application
End Sub
Private Sub mApp_DocumentBeforeClose _
(ByVal Doc As Document, Cancel As Boolean)
Stop
End Sub
Private Sub mApp_DocumentChange()
Stop
End Sub
Private Sub mApp_WindowSelectionChange (ByVal Sel As Selection)
Dim tabTemp As Table
If Sel.Range.Parent.Tables.Count <> _
mcol_Tabellen.Count Then
Set mcol_Tabellen = Nothing
Set mcol_Tabellen = New Collection
For Each tabTemp In Sel.Range.Parent.Tables
Me.Hinzufügen tabTemp
Next
End If
For Each tabTemp In Sel.Range.Parent.Tables
If Sel.InRange(tabTemp.Range) Then
RaiseEvent TabellenChange(tabTemp)
End If
Next
End Sub
|
Das WindowSelectionChange-Ereignis des Application-Objekts ist das einzige Ereignis, über das Word Veränderungen am Dokument mitteilt. Es tritt ein, wenn eine Tabelle in das Dokument eingefügt wird. Aus Platzgründen kann die erforderliche Erweiterung leider nicht Schritt für Schritt vorgestellt werden. Stattdessen finden Sie das Klassenmodul CTabellen am Ende des Beitrags (Listing 1). Gleich zu Beginn erwarten Sie zwei wichtige Befehle:
Private WithEvents mApp As Application
Event TabellenChange(Table As Table)
Der Private-Befehl deklariert eine Variable, die auf die Ereignisse des Application-Objekts reagieren kann. Wichtig dabei ist, dass der Variablen im Initialize-Ereignis ein Application-Objekt zugewiesen wird. Der zweite Befehl definiert ein neues Ereignis mit dem Namen TabellenChange, das in der WindowSelectionChange-Ereignisprozedur aufgerufen wird, wenn sich herausstellt, dass eine Änderung an einer Tabelle vorgenommen wurde. Als Argument wird dem Ereignis das geänderte Table-Objekt übergeben. Ausgelöst wird es über den RaiseEvent-Befehl in der WindowSelectionChange-Ereignisprozedur:
Private Sub mApp_WindowSelectionChange(ByVal Sel As Selection)
Dim tabTemp As Table
' Hat sich die Anzahl an Tabellen geändert?
If Sel.Range.Parent.Tables.Count <> mcol_Tabellen.Count Then
' Wenn ja, dann alle Table-Objekte neu in
' Auflistung aufnehmen
Set mcol_Tabellen = Nothing
Set mcol_Tabellen = New Collection
For Each tabTemp In Sel.Range.Parent .Tables
Me.Hinzufügen tabTemp
Next
End If
' Prüfen, zu welcher Tabelle das selektierte
' Range-Objekt gehört
For Each tabTemp In Sel.Range.Parent.Tables
If Sel.InRange(tabTemp.Range) Then
RaiseEvent TabellenChange(tabTemp)
End If
Next
End Sub
Mit Hilfe der InRange-Methode des übergebenen Selection-Objekts prüft die Prozedur, ob sich das Range-Objekt, das mit dem Selection-Objekt verbunden ist, in einer der Tabellen des Dokuments befindet. Wenn ja, wird ein TabellenChange-Ereignis aufgerufen, dem das betroffene Table-Objekt übergeben wird. Nun kommt das kleine Finale. Angenommen, das Klassenmodul liegt in Normal.dot vollständig vor. Dann darf die Deklaration in Normal.dot wie folgt erweitert werden:
Public WithEvents Tabellen As CTabellen
Unser Tabellen-Objekt ist jetzt in der Lage, auf ein Ereignis zu reagieren. Wählen Sie das Objekt Tabellen aus der Objektliste, das Ereignis TabellenChange aus der Ereignisliste aus und bauen Sie die Ereignisprozedur wie folgt auf:
Private Sub Tabellen_TabellenChange(Table As Table)
If Not Tabellen.Überschrift(Table) Is Nothing Then
MsgBox "Die geänderte Tabelle ist: " & _
Tabellen.Überschrift(Table).Text
End If
End Sub
Fazit
Wird eine Tabelle in einem neu angelegten Dokument geändert, wird die Überschrift der Tabelle ausgegeben. Damit wurde Word um ein Tabellen-Objekt erweitert, das auf Änderungen mit einem Ereignis reagiert. Beurteilen Sie selbst, wie nützlich eine solche Erweiterung ist. Der Aufwand lohnt sich mit Sicherheit nicht, wenn Sie hin und wieder Tabellen ansprechen möchten. Für diejenigen, die in einem Dokument Dutzende oder mehr Tabellen verwalten und diese einfacher ansprechen möchten, kann die vorgestellte Erweiterung eine interessante Lösung sein.
In jedem Fall haben Sie etwas mehr über das Zusammenspiel von Office-Objekten, Klassen und Ereignissen gelernt und ganz nebenbei erfahren, wie sich feststellen lässt, in welcher Tabelle sich die Textmarke gerade befindet.