Office (Allgemein)


Office um eigene Objekte erweitern
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:

Bild03





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).

Bild01


















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).

Bild02
















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.

  • Fügen Sie die folgende Funktion ein, die die Hinzufügen-Methode implementiert:

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.

  • Fügen Sie die folgende Funktion ein, die die AlleTabellenEntfernen-Methode implementiert:

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.

  • Fügen Sie die folgende Funktion hinzu, die die Item-Methode des Tabellen-Objekts implementiert:

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.

  • Fügen Sie die folgende Property-Prozedur hinzu, die die Überschrift-Eigenschaft implementiert:

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.

  • Fügen Sie die folgende Funktion hinzu, die die Anzahl-Methode implementiert:

Function Anzahl() As Long 
If Not mcol_Tabellen Is Nothing Then 
  Anzahl = mcol_Tabellen.Count 
Else 
  Anzahl = 0 
End If 
End Function

  • Fügen Sie den folgenden Befehl in die Initialize-Ereignisprozedur ein:

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

  • Deklarieren Sie die Objektvariable Tabellen im Deklarationsteil des Dokumentmoduls:

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.


Page view tracker