HTML-Editor-Steuerelement

Eine Access-Textbox, in einem Formular an ein Memo-Feld gebunden, ist eine spröde Angelegenheit, wenn es darum geht, ihren Inhalt ansprechend zu gestalten. Denn alle Formatierungsmöglichkeiten beziehen sich immer nur auf das ganze Feld, den gesamten Text. Ein einzelnes Wort etwa fett hervorzuheben, ist nicht möglich. Erst mit Access 2007 kam Farbe ins Spiel und der Richtext-Modus für Textfelder hielt Einzug, über den man Texte rudimentär formatieren kann. In allen früheren Access-Versionen bleibt man für diesen Zweck jedoch auf Fremdsteuerelemente angewiesen.

Die Anforderung, Texte zu formatieren, stellt sich auch in Datenbanken oft. Es müssen nicht gleich komplette Dokumente sein, die mit einem Datensatz verknüpft sind – es reichen bereits die berüchtigten Notiz- und Anmerkungsfelder, die man gern für Zusatzinformationen in Formulare einbaut, damit der Anwender dort alles unterbringen kann, was nicht in das Raster der vorgegebenen Eingabefelder passt.

Von dieser Möglichkeit wird dann erfahrungsgemäß auch reichlich Gebrauch gemacht, was nicht selten zu unübersichtlichen Textwüsten führt. Dabei würde nur wenig Formatierung bereits ausreichen, um eine gegliederte Darstellung des Textes zu ermöglichen.

In der Regel kommt ein Richtext-ActiveX-Steuerelement (richtx32.ocx) zum Einsatz, um dem Mangel abzuhelfen. Abgesehen davon jedoch, dass der Entwickler dann noch viel Arbeit vor sich hat, um das Steuerelement zusätzlich etwa mit einer Symbolleiste auszustatten, die es dem Anwender ermöglicht, den Text komfortabel zu editieren, kommen hier mindestens zwei Probleme ins Spiel.

Einmal ist das entsprechende OCX von Microsoft eigentlich nicht frei erhältlich, sondern entweder mit der Developer Edition von Access gebundelt, oder mit verschiedenen Entwicklungsumgebungen, wie Visual Basic 6.

Allerdings gibt es auch freie Alternativen, wie etwa das Steuerelement von Stephen Lebans oder das von vbAccelerator [1]. Zweitens muss man solch ein OCX mit der Datenbank weitergeben und ruft im Zweifelsfall Scherereien mit seiner Registrierung und den Verweisen im VBA-Projekt hervor.

Microsoft wurde sich dieser Unzulänglichkeit zum Formatieren offenbar bewusst und stellte mit Access 2007 den sogenannten Richtext-Modus für Textboxen bereit.

Unter der Voraussetzung, dass ein Memofeld einer Tabelle als Datenherkunft des Steuerelements dient, das seinerseits bereits in den Richtext-Modus versetzt wurde, kann in einer beliebigen Textbox der Inhalt über eine Popup-Toolbar formatiert werden (siehe Bild 1).

A2007_toolbar.png

Bild 1: Textformatierung in Access 2007 (Richtext) über Popup-Toolbar

Leider ist dieses Popup-Menü, das nur beim Markieren von Text erscheint, auch schon die einzige Möglichkeit, auf die Formatierung des Textes Einfluss zu nehmen, und seine Bedienung erfordert etwas Geschicklichkeit im Umgang mit der Maus. Eine programmgesteuerte Einflussnahme ist nicht gegeben und das Menü lässt sich auch nicht abschalten.

Intern wird die Formatierung dann über HTML-Tags gespeichert, weshalb die Bezeichnung Richtext irreführend ist. Theoretisch ist es möglich, diese HTML-Tags anschließend über VBA-Code zu verändern.

Indessen darf man allerdings nicht erwarten, dass im Richtext-Modus nun beliebige HTML-Auszeichnungen genauso angezeigt würden, wie in einem Webbrowser; die Access 2007-Textbox beherrscht tatsächlich nur jene, die sich auch über das Popup-Menü erzeugen lassen.

Genügend Gründe also, sich nicht nur für die Versionen vor Access 2007 Gedanken über alternative Lösungen zu machen …

Webbrowser-Steuerelement

Das Microsoft Webbrowser Control, welches Sie über die Liste der ActiveX-Steuerelemente in ein Formular einfügen können, stellt einen Mini-Browser für HTML-Dokumente bereit. Im kommenden Access 2010 wird ein solches Steuerelement übrigens standardmäßig zur Bordausstattung von Access gehören.

Es hat dort den Vorteil, sich direkt an ein Memofeld einer Tabelle binden zu lassen, was mit dem normalen Webbrowser-ActiveX zwar nicht möglich ist, uns andererseits aber, wie später noch ausgeführt wird, nicht gerade vor weltbewegende Programmierprobleme stellt.

Das Webbrowser-Steuerelement hat den entscheidenden Vorteil, dass es grundsätzlich auf jedem Windows-System vorhanden ist und mit der Installation von Access oder Office gar nichts zu tun hat. Der Internet-Explorer selbst stellt es zur Verfügung. Verweis- oder Registrierungsprobleme gibt es hier nicht.

Das Steuerelement ist tatsächlich identisch mit dem Anzeigebereich des Internet-Explorers und lässt sich daher auch auf gleiche Weise über dessen Objektmodell MSHTML, auch DOM genannt, steuern.

Dieses Document Object Model ist enorm umfangreich und leistungsfähig. Entwickler, die auch Webseiten mit Javascript erstellen, werden das wissen. Man kann auf jegliches Element eines HTML-Dokumentes sowohl zugreifen und dessen Eigenschaften ändern oder Aktionen darauf ausführen, wie auch neue Elemente erzeugen.

Stellen wir also fest, dass mit dem Webbrowser Control ein geeignetes und unkompliziertes Steuerelement bereitsteht, mit dem durchformatierter Text angezeigt werden kann.

Wie aber können wir den in diesem Steuerelement angezeigten Text editieren – der Internet Explorer bietet ja selbst auch keine Möglichkeit für manuelle änderungen

Webbrowser im Design-Mode

Die Tatsache, dass sich der Internet-Explorer mit nur einer Codezeile in einen Modus versetzen lässt, der das Dokument bearbeitbar macht, ist nur wenig bekannt.

Dabei kann Text nicht nur gelöscht oder hinzugefügt, sondern es können auch ohne viel Aufwand Formatierungen verändert werden. Der „echte“ Internet-Explorer birgt aber natürlich keine Steuer- oder Menüelemente, über die sich das realisieren ließe. Mit wenigen Zeilen VBA erwecken wir jedoch das Webbrowser-Steuerelement unter Access zum Leben, wie die folgende Anleitung zeigt:

  • Erstellen Sie ein neues Formular unter einer beliebigen Access-Version.
  • Fügen Sie im Formularentwurf über die Menüleiste (Einfügen|ActiveX-Steuerelement…) oder über das Ribbon (Entwurf|Steuerelemente), ein Microsoft Webbrowser-Steuerelement ein und nennen es ctlHTML.
  • Versionsabhängig: Bevor Sie das Formular speichern, sollten Sie das Steuerelement in Access-Versionen vor Access 2003 auf seine endgültigen Abmessungen bringen. Leider ist es in früheren Versionen nicht möglich, die Größe des Elements nachträglich zu ändern – weder im Entwurf noch zur Laufzeit. Zwar lässt sich durchaus der Container-Rahmen ändern, der enthaltene Browser zeigt sich davon jedoch unbeeindruckt und behält seine einmal gespeicherte Größe bei.
  • Schreiben Sie in die Form_Load-Ereignisprozedur (Beim Laden) folgende Zeile:
Me!ctlHTML.Object.Navigate2 "http://www.access-im-unternehmen.de"

Nach dem Öffnen des Formulars zeigt dieses die gewünschte Webseite an. Die Navigate2-Methode ist dafür verantwortlich und erledigt das Gleiche, wie die Eingabe einer URL in die Adresszeile des Internet-Browsers.

Unser Ziel ist es jedoch nicht, eine x-beliebige Webseite anzuzeigen, sondern neue HTML-Dokumente zu erzeugen und mit HTML-Strings aus einem Tabellenfeld zu speisen. Um ein solches neues Dokument im Steuerelement aufzubauen, reicht diese einfache Anweisung:

Me!ctlHTML.Object.Navigate2 "about:blank"

Diese Zeile ist unbedingt erforderlich, denn ohne sie zeigt das Steuerelement schlicht eine weiße Fläche an. Erst ein Rechtsklick zur Laufzeit offenbart den Unterschied: Mit about:blank gibt es ein Kontextmenü, ohne gibt es keines.

Wählen Sie im Kontextmenü dann den Eintrag Quelltext anzeigen, tritt dieser Inhalt zutage: <HTML></HTML> und manchmal auch <BODY></BODY>.

Jetzt wird es interessant. Wie kann man das neue HTML-Dokument mit weiterem Inhalt, etwa aus einer Tabelle, füllen

Auch das lässt sich erstaunlich einfach realisieren: Man schreibt den HTML-Text über die write-Methode des Dokuments. Zuvor aber noch ein kurzer Ausflug in das DOM-Modell.

DOM und MSHTML

Auf das Webbrowser-Steuerelement selbst haben Sie, wie oben angeführt, über dessen Object-Eigenschaft Zugriff.

Öffnen Sie im VBA-Editor den Objektkatalog, so finden Sie dort unter der Bibliothek SHDocVw, auf welche automatisch beim Einfügen eines Webbrowser-Controls verwiesen wird, das Klassenmodell zum Steuerelement. Die Klasse, die dem Control entspricht, nennt sich hier Webbrowser.

Sie zeigt allerlei Eigenschaften und Methoden, mit denen sich etwa die Gestalt des Steuerelements selbst einstellen lässt – unter anderem die bereits erwähnte Navigate2-Methode. Augenscheinlich findet sich jedoch nichts, was einen direkten Bezug zum HTML-Inhalt des gehosteten Dokuments hätte.

Die Bibliothek enthält derlei tatsächlich nicht. Dafür ist eine andere Bibliothek zuständig, die Sie manuell in die Verweise aufnehmen sollten und die ebenfalls vom Internet-Explorer-System bereitgestellt wird: MSHTML.

Öffnen Sie die Liste der Verweise (VBA-Editor, Menü Extras|Verweise…) und aktivieren Sie die Bibliothek Microsoft HTML Object Library.

Wenn Sie sich diese Bibliothek im Objektkatalog zu Gemüte führen, dann steht vermutlich die Frage im Raum, wie deren erschlagender Umfang von Klassen zu bändigen sei und wie die Verbindung zwischen dem Webbrowser-Control und der MSHTML-Bibliothek zustande kommt.

Das geschieht über genau eine Eigenschaft des Web-Controls, nämlich Document. Diese gibt ein Objekt der MSHTML-Klasse HTMLDocument zurück, welche im Grunde die oberste Ebene des DOM-Modells darstellt. Ein HTMLDocument enthält dann zahlreiche Unterobjekte, die ihrerseits haufenweise Eigenschaften und Auflistungen beheimaten. Nur ein exemplarisches Beispiel:

Das HTMLDocument enthält eine body-Eigenschaft, welche ein HTMLBody-Objekt zurückgibt. Das HTMLBody-Objekt kennt eine style-Eigenschaft in Form der HTMLStyle-Klasse. HTMLStyle wiederum zeigt eine Eigenschaft fontSize, die die Schriftgröße einstellt oder zurückgibt.

Wollten wir also die Schriftgröße des gesamten Bodys eines im Webcontrol geladenen Dokuments auf 11pt setzen, so ließe sich das auch ohne MSHTML über folgenden Ausdruck erledigen:

Me!ctlHTML.Object.Document.body.style.fontSize = "11pt"

Hier wird deutlich, dass der Verzicht auf die MSHTML-Bibliothek zu sehr langen Ausdrücken und zur Unübersichtlichkeit führt.

Einfacher ist es, wenn man das Dokument direkt einer Objektvariablen zuweist und dann alle weiteren Aktionen mit deren Hilfe ausführt:

Dim objDoc As MSHTML.HTMLDocument
Set objDoc = Me!ctlHTML.Document
With objDoc
    .body.style.fontSize = "11pt"
End With
Eine Alternative wäre auch dieser Code:
Dim objDoc As HTMLDocument
Dim objBody As HTMLBody
Set objDoc = Me!ctlHTML.Document
Set objBody = objDoc.body
With objBody
    .style.fontSize = "11pt"
    .style.font = "Arial"
End With

Dies soll lediglich verdeutlichen, wie verschachtelt das DOM-Modell ist und wie man mithilfe von Objektvariablen mehr Struktur in den Code bringen kann.

Völlig unverzichtbar wird MSHTML, wenn man auf Ereignisse reagieren will. Ein Objekt des Typs HTMLDocument etwa kann fast 40 Ereignisse auslösen.

Markiert man beispielsweise im angezeigten Dokument eine Passage, so löst dies ein Ereignis onselectionchange aus. Um auf dieses reagieren zu können, benötigt man eine Objektvariable mit dem Zusatz WithEvents:

Private WithEvents objDoc As MSHTML.HTMLDocument
...
Set objDoc = Me!ctlHTML.Document
...
Private Sub objDoc_onselectionchange()
    Debug.Print " Textauswahl wurde geändert"
End Sub

Die komplette Referenz zu DOM und MSHTML finden Sie unter [3] im Internet. An dieser Stelle soll es bei den wenigen grundsätzlichen Anregungen zur Arbeit mit der Bibliothek bleiben. Notiz am Rande: Die MSHTML-Bibliothek erweitert sich ständig im selbem Maße wie der Internet-Explorer.

Deshalb finden sich in der aktuellen Version etwa auch Klassen zum Phishing darin. Das gibt es aber erst seit Version 7. Wenn Sie mit der Bibliothek arbeiten, sollten Sie folglich genau darauf achten, dass keine Methoden Anwendung finden, die es auf einem etwaigen Zielsystem noch gar nicht gibt, weil dort vielleicht noch der Internet Explorer 6 installiert ist.

In der Referenz zu MSHTML ist jeweils angegeben, seit welcher Version eine Methode gültig ist.

HTML-Dokument bearbeiten

Kommen wir zur ursprünglichen Aufgabe zurück, ein Dokument im Web-Control in den bearbeitbaren Zustand zu versetzen.

Bisher sehen Sie nach dem Laden eines Dokuments über about:blank ja nur eine leere Fläche. Mit einer weiteren Zeile schreiben Sie nun HTML-Code hinein:

Dim strHTML As String
strHTML = "<body><strong>TEST</strong></body>"
ctlHTML.Document.write strHTML

Und schon erscheint die Ausgabe TEST im Steuerelement. Falls Sie hier auf die Idee kommen sollten, wie oben angeregt, eine Objektvariable einzusetzen, werden Sie keinen Erfolg haben:

Dim objDoc As HTMLDocument
Set objDoc = Me!ctlHTML.Document
objDoc.write strHTML

Dies führt zu einem Fehler, weil ausgerechnet die write-Methode von MSHTML.HTMLDocument nicht VBA-komform ist und als Parameter einen Variant-Typ vorsieht, den es unter VBA im Unterschied zu C++ nicht gibt.

Die Document.write-Methode – siehe oben – des Controls selbst hat damit aber offenbar keine Probleme und führt intern die notwendige Konvertierung durch.

Der hier hartkodierte Inhalt des Dokuments aus einer String-Variablen lässt sich selbstverständlich genauso gut aus einem Tabellenfeld auslesen, das wegen der möglichen Länge des Inhalts als Memofeld deklariert sein sollte. Im Formular könnte das mit nur einer Zeile so aussehen:

ctlHTML.Document.write Me!HTML_Memofeld.Value

Der Clou: Mit ebenfalls nur einer Zeile lässt sich das Dokument editierbar machen:

ctlHTML.Document.designMode = "On"

Nach dieser Anweisung können Sie den Cursor nun an eine beliebige Stelle im Dokument setzen und Text dazu schreiben oder löschen – gerade so, wie in einem Texteditor.

Den Inhalt des geänderten Dokuments lesen Sie anschließend aus dem Control aus und können ihn wieder im Datensatz speichern:

Me!HTML_Memofeld.Value = ctlHTML.Document.body.outerHTML

Hier wird nur der Body des Dokuments ausgelesen, aber mehr benötigen Sie auch nicht, weil das <HTML>-Tag ja bereits von einem neu per about:blank erzeugten Dokument angelegt wird. outerHTML bedeutet dabei, dass der HTML-Code inklusive des body-Tags und möglicher Attribute selbst zurückgegeben wird, während etwa innerHTML nur den eigentlichen Inhalt des Bodys enthielte.

Möchten Sie das Dokument schließlich wieder in den normalen Zustand versetzen, so schalten Sie den Design-Mode einfach wieder ab:

ctlHTML.Document.designMode = "Off"
Set objDoc = ctlHTML.Document

Wichtige Anmerkung dazu: Sollten Sie das Dokument einer Objektvariablen zugewiesen haben, so ist diese nach dem ändern des Design-Modes in der Regel nicht mehr gültig. Sie sollten deshalb nach jedem Wechsel des Design-Modes die Zuweisung erneut vornehmen.

HTML-Dokument formatieren

Mit dem bisher Beschriebenen sind Sie in der Lage, im HTML-Dokument des Steuerelements Text hinzuzufügen oder zu löschen. Was nun noch fehlt ist die Möglichkeit, ihn zu formatieren.

Dazu ist es für die wichtigsten Funktionen gar nicht notwendig, die HTML-Auszeichnungen per Code zu modifizieren. Normalerweise muss ja ein Text mit Formatierungs-Tags versehen werden, damit er sich in veränderter Gestalt präsentiert.

So braucht es etwa das Tag <em>, um ein Wort fett wiederzugeben: <em>TEST</em>. Diese Tags muss man jedoch nicht in den HTML-Code hineinflicken, sondern bedient sich einer komfortablen Methode von MSHTML, die das automatisch macht, nämlich der Anweisung execCommand.

Diese Methode eines HTMLDocument-Objekts erwartet in der Regel lediglich einen Anweisungs-String als Parameter und wirkt sich dann auf die aktuelle Auswahl im Dokument aus. Um eine mit der Maus markierte Passage fett zu formatieren, setzen Sie diese Anweisung ab:

objDoc.execCommand "Bold"

Manchmal wird auch ein zusätzlicher Parameter benötigt, der neben der Formatierungsanweisung noch einen Wert übergibt, wie etwa beim ändern der Schriftart:

objDoc.execCommand "Fontname", "Courier New"

Damit können Sie nun beispielsweise eine Schaltfläche mit der Aufschrift Fett versehen und in deren Click-Ereignisprozedur die Anweisung von oben schreiben. Sie erhalten somit einen einfachen WYSIWYG-HTML-Editor.

Eine gute Demo für einen solchen auf der execCommand-Methode beruhenden Editor unter Access finden Sie bei Stephen Lebans [2].

Wenn Sie erfahrem möchten, welche Kommandos die execCommand-Methode unterstützt und wie die genaue Syntax auszusehen hat, so finden Sie eine erschöpfende Zusammenstellung unter [4].

Nicht für jeden Zweck allerdings reicht diese Methode allein aus und der HTML-Code muss manchmal direkt modifiziert werden. Auch hier gibt es jedoch Unterstützung durch MSHTML in Gestalt der Anweisung pasteHTML.

Sie ersetzt den augenblicklich markierten Text durch einen im Parameter übergebenen HTML-Code. Das könnte etwa so aussehen:

objDoc. selection.createRange.pasteHTML "<em>TEST</em>"

Hier wird zunächst die aktuelle Auswahl im Dokument als selection-Objekt erhalten. Mit diesem selbst kann man aber noch nicht viel anfangen. Seine Funktion createRange erst gibt das markierte Element oder eine ganze Auflistung von Elementen zurück.

In der Regel wird das ein Objekt der MSHTML-Klasse IHTMLTxtRange sein. Und eben diese kennt die Methode pasteHTML, die genau dasselbe tut, als würden Sie HTML-Code aus der Zwischenablage ins Dokument einfügen. Die aktuelle Auswahl wird also durch den neuen HTML-Code ersetzt. Ist kein Text ausgewählt, so wird der HTML-String an jener Stelle eingefügt, an der sich der Cursor befindet.

Nun möchten Sie allerdings weniger einen komplett neuen HTML-Code einfügen, als vielmehr vorhandenen Text mit Formatierungs-Tags „umrahmen“. Dazu muss im ersten Schritt der ausgewählte Text erhalten werden, was ebenfalls über das selection-Objekt möglich ist:

strHTML = objDoc.selection.createRange.Text

Nun kann der Text mit den gewünschten Formatierungs-Tags zusammengesetzt werden:

strHTML = "<em>" & strHTML & "</em>"
objDoc. selection.createRange.pasteHTML strHTML

Dieses Beispiel ist an sich überflüssig, da ja bereits die execCommand-Methode mit dem Parameter Bold zum gleichen Ergebnis führt. Es demonstriert aber die grundsätzliche Vorgehensweise auch für kompliziertere Operationen.

Sie möchten eventuell erfahren, wie ein im Dokument markiertes Wort formatiert ist

Auch dazu reicht ein einzige Funktion aus, nämlich die queryCommandValue-Methode des selection-Objekts:

Wert = objDoc.selection.createRange.queryCommandValue ("Bold")

Hier wird abgefragt, ob die Auswahl fett formatiert ist. Der Ausdruck gibt True zurück, wenn das der Fall ist. Eine Rückgabe aller Formatierungen im Form eines Arrays oder einer Collection ist allerdings nicht möglich – es muss schon nach jeder möglichen Formatierung einzeln gefragt werden.

Die Rückgabe ist vom Datentyp Variant und kann je nach Kommando ein Bool-, Integer- oder String-Typ sein.

Nützlich ist das etwa, wenn Sie die Formatierung über den Zustand eines Formatierungs-Buttons signalisieren möchten. Die erwähnte Schaltfläche Fett könnte etwa als Umschaltfläche daher kommen und bei fett markiertem Text in gedrücktem Zustand erscheinen, so wie das auch in Textverarbeitungen geschieht.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar