TreeView ohne ActiveX

Im August 2012 verteilte Microsoft über Windows-Update für alle Office-Installationen ab Version 2003 automatisch Security Patches, die nur eine einzige Komponente austauschten: die ActiveX-Bibliothek mscomctl.ocx, welche, unter anderem, das viel verwendete TreeView-Steuerelement enthält. Dabei machte die COM-Version der Bibliothek einen Sprung von 2.0 auf 2.1, was VBA-Module, in denen deren Elemente angesprochen werden, teilweise inkompatibel macht. Ergebnis sind deshalb Datenbanken, die seltsame Fehler melden, wenn Formulare mit TreeViews oder Listviews aufgerufen werden.

In Foren und Blogs wurden Workarounds für das Problem diskutiert, die im Wesentlichen Manipulationen der Registry vorschlagen, und auch Microsoft veröffentlichte schließlich einen Patch zum Patch, ohne jedoch das Problem an sich zu lösen. Mit solchen Workarounds, die zudem Administratorrechte erfordern, mag man am Einzelplatz glücklich werden, wer aber Datenbanken für Unternehmen mit Dutzenden von Arbeitsplätzen entwickelt, steht vor ärgerlichen Umständen.

Die Auffassung von IT-Verantwortlichen, dass ActiveX-Komponenten zu vermeiden seien, bekommt hier verständlicherweise neue Nahrung. Dabei war die MSComCtl-Bibliothek bisher relativ unverdächtig – ist sie doch Bestandteil der Office-Installation selbst.

Das wirft die Frage auf, ob diese Steuerelemente denn nicht durch andere Lösungen zu ersetzen wären. Und selbstverständlich gibt es diese. Mögliche Alternativen sind API-Module, die etwa ein TreeView-Fenster nativ von Grund auf neu erstellen. Schwierigkeiten bekommt man hier indessen meist mit den notwendigen Subclassing-Routinen, die VBA instabil werden lassen.

Auch existieren Lösungen, die TreeViews allein mit Access-Bordmitteln realisieren. Von den Features, dem Komfort und der Performance her können diese allerdings nicht wirklich mit dem ActiveX-Pendant konkurrieren.

Schaut man sich um, so findet man TreeViews aber nicht alleine in Anwendungen, sondern etwa auch auf Internetseiten, wo sie häufig Navigationszwecken dienen.

Diese TreeViews sind zumeist in JavaScript oder PHP programmiert. Es gibt sie in zahlreichen Varianten – umfangreiche oder simple, mit oder ohne AJAX. Und viele können von den Möglichkeiten her durchaus mit dem MSComCtl-TreeView mithalten. Was liegt da näher, als solch ein geskriptetes TreeView in einem Webbrowser-Steuerelement anzuzeigen

So begab ich mich auf die Suche nach einem JavaScript-TreeView, das in den Möglichkeiten dem ActiveX-TreeView nahekommt. Einige Internetseiten bieten einen Überblick über freie TreeViews.

Nach dem Test diverser Lösungen fiel die Wahl auf das CoolJSTree, welches in einer abgespeckten freien sowie in einer umfangreicheren Bezahlversion (Professional) angeboten wird.

Das CoolJSTree

Sie finden diese Script-Lösung unter http://javascript.softdrawer.com/scripts/cooltree zum Download.

Das Paket enthält zahlreiche Beispiele, die Sie sofort im Browser begutachten können. Eine übersichtliche Dokumentation fehlt ebenfalls nicht.

Im Prinzip brauchen Sie die folgenden Dateien für ein CoolJSTree-TreeView:

  • die zentrale Datei cooltree.js, welche das JavaScript für den Aufbau des Baums enthält,
  • die tree_format.js, die Formatierungen der Nodes steuert,
  • die Datei tree_nodes.js, welche die Definition der anzuzeigenden Nodes beherbergt,
  • und ein Verzeichnis mit den Bilddateien für die Symbole der Nodes.

Es können aber, wie etliche der vom Entwickler beigesteuerten Beispiele zeigen, die Skripte natürlich auch zu einer kompakten Datei zusammengefasst oder sogar in das anzuzeigende HTML-Dokument selbst eingebaut werden. Dann müssen außer dieser HTML-Datei nur noch die Bilddateien vorhanden sein.

Von der Tabelle in den Browser

Wie aber kommt das JavaScript-TreeView als Steuerelement in ein Access-Formular, wie werden seine Knoten angelegt und gesteuert

Zuerst braucht es hier ein Webbrowser Control, das Access 2010 bereits als Steuerelement beisteuert, während frühere Versionen auf das ActiveX-Steuerelement Microsoft Web Browser angewiesen sind.

Beide sind im Prinzip identisch und stellen einen kleinen Internet Explorer zur Verfügung, nur die Steuerung über das Objektmodell fällt marginal unterschiedlich aus. Das Steuerelement ziehen Sie auf die Ausmaße, die das TreeView haben soll. Ab Access 2007 können Sie es schließlich noch verankern – etwa mit der Option Quer nach unten dehnen -, wodurch es sich zur Laufzeit der Formulargröße anpasst.

Inhalt wird in das Control üblicherweise über dessen Methode Navigate2 und Angabe einer HTML-Datei als Parameter geladen. Ob diese aus dem Internet bezogen wird oder lokal, spielt dabei keine Rolle.

Man könnte also über VBA einen HTML-String zusammenbauen, der die Elemente des TreeViews und das JavaScript enthielte, und ihn anschließend als HTML-Datei abspeichern, damit der Webbrowser sie laden kann – wäre da nicht das Rechtesystem und Zonenmodell des Internet Explorers! Denn das Webbrowser-Steuerelement weigert sich beharrlich, jegliches JavaScript in lokalen Dateien auszuführen.

Auch änderungen an den Zoneneinstellungen des Internet Explorers ändern daran nichts. Allerdings gibt es einen Trick, dem Browser einen Netzwerkpfad vorzugaukeln, wodurch dieser offenbar andere Sicherheitseinstellungen verwendet. Schreiben Sie den Pfad zur Datei als URL beispielsweise in dieser Form:

file://127.0.0.1/c$/meinverzeichnis/test.html

Mit dieser Pfadangabe (hier: Laufwerk C:) führt der Browser dann seltsamerweise auch integrierte JavaScripts aus.

Es gibt jedoch eine wesentlich elegantere Form, dem Control zur Anzeige von HTML-Inhalten zu verhelfen, als über den Umweg einer dynamisch erzeugten Datei.

Legen Sie zunächst ein leeres Dokument im Control an und schreiben Sie anschließend beliebigen Inhalt über die write-Methode desselben in es hinein. Das könnte so aussehen:

WebControlObject.Navigate2 "about:blank"
sHTML = "<HTML><BODY>Hallo!</BODY></HTML>"
WebControlObject.Document.write sHTML

Die write-Methode löst im Browser das erneute Rendern der Seite mit dem Inhalt der String-Variablen sHTML aus.

Das gezeigte Beispiel wird allerdings so nicht funktionieren, weil Sie vor dem Absetzen der write-Anweisung noch abwarten müssen, bis das leere Dokument komplett erzeugt ist. Dazu müsste vor der letzten Zeile noch über die ReadyState-Eigenschaft des WebControls der Status des Dokuments abgefragt werden, der mindestens den Wert Interactive haben muss. Das kann etwa mittels einer Schleife geschehen:

Do
    DoEvents
Loop Until WebControlObject.ReadyState >= READYSTATE_INTERACTIVE

Statt des simplen HTML-Strings oben ist nun der Aufbau der TreeView-Seite mitsamt den integrierten Skripten der Variablen sHTML zuzuweisen. Wie das in der Demo zum Beitrag gelöst ist und wie auf Ereignisse des TreeViews reagiert werden kann, erfahren Sie in den folgenden Abschnitten.

Eine einfache Demo-Anwendung

In der Demodatenbank TreeViewJS.mdb existiert ein Formular frmSample, das sich nach Klick auf die Schaltfläche Lade TreeView wie in Bild 1 präsentiert.

pic001.png

Bild 1: Einfaches Formular mit JavaScript-TreeView im Webbrowser-Steuerelement

Beim Laden des Formulars wird lediglich die Objektvariable oTree auf das WebControl-Objekt gesetzt:

    Private WithEvents oTree As SHDocVw.WebBrowser
    Set oTree = Me!ctlWeb.Object

Das wäre für den weiteren Code nicht unbedingt nötig, vereinfacht aber die Schreibweise im Modul.

Die eigentliche Routine zum Laden des TreeViews steht hinter dem Click-Ereignis cmdLoad_Click des Formulars. Nachdem hier ein leeres HMTL-Dokument im WebControl angelegt wurde, holt sich die Prozedur zuerst den Inhalt der Datei tree_template.html in eine String-Variable sDoc. Der Pfad zur Datei ist hier hartkodiert und steht auf dem Unterverzeichnis \trees der Datenbank; passen Sie das gegebenenfalls an. Im String werden dann diverse Platzhalter mit Inhalt gefüllt und abschließend der HTML-String einfach in das leere Dokument geschrieben:

    oTree.Document.write sDoc

Schon füllt sich das Browser-Fenster mit dem TreeView, in dem Sie die einzelnen Nodes mit der Maus ein- und ausklappen können.

Was hier in Kurzform dargestellt wurde, ist in Wirklichkeit natürlich etwas umfangreicher – die Prozedur verzweigt in verschiedene Unterfunktionen, die den Inhalt für die Platzhalter bereitstellen. Zunächst aber einiges zum Aufbau des HTML-Templates selbst.

Das Tree-Template

Statt der oben erwähnten drei eingebundenen CoolJSTree-JavaScripts sind deren Routinen alle in das Template selbst aufgenommen. Es enthält also alles, was eine HTML-Datei benötigt, und zusätzlich vier Skript-Abschnitte. Der wichtigste ist ganz am Ende zu finden:

    <script type="text/javascript">
    var tree = new COOLjsTree("tree1", TREE_ITEMS, TREE_FORMAT);
    </script>

Mit der Funktion COOLjsTree wird der Baum tatsächlich erstellt. Als Parameter werden eine beliebige Klassen-ID für den DIV-Abschnitt des TreeView erwartet, eine Variable, die die Nodes des Baums definiert, und eine Variable, die die Formatierungsanweisungen enthält. Die Funktion selbst ist im Skript-Block darüber mit seinem etwas kryptischen Code enthalten.

Die Variable TREE_FORMAT für die Formatierungen ist oben gleich nach dem BODY-Tag definiert. Dort steht kommentiert, welche Formatierungsmöglichkeiten fest vergeben werden können. Das sind etwa Angaben zu Einrückungen, Positionierung und vor allem zu den Icons. Der Aufbau des Blocks ist in der CoolJSTree-Dokumentation gut beschrieben. Das Interessante an den Abschnitten für die Icons sind die Pfadangaben zu den Bilddateien, die etwa so aussehen:

    ["folder.png", "folderopen.png", "page.png"]

Da die Icons irgendwo vom Browser gelesen werden müssen, es aber keine relative URL geben kann – das Dokument wurde ja aus dem Nichts per about:blank erzeugt -, müssen hier absolute Pfade stehen. Und deshalb sind die Dateinamen mit drei Fragezeichen als Präfix versehen.

Diese Platzhalter werden später per VBA durch das tatsächliche Icon-Verzeichnis ersetzt:

    sDoc = Replace(sDoc, "", sIconPath)

Der Vollständigkeit halber nachfolgend ein kleines Schnipsel, wie sich aus einem Windows-Pfad die URL für das Verzeichnis bildet:

    sIconPath = CurrentProject.Path & "\trees\icons\"
    sIconPath = Replace(sIconPath, "\", "/")
    sIconPath = "file://" & sIconPath

Statt der im icons-Verzeichnis mitgelieferten Icons können Sie selbstverständlich beliebige andere Bilddateien verwenden, die auch nicht zwingend im Format 16 x 16 daherkommen müssen. Das CoolJSTree passt seine Zeilen automatisch der Symbolgröße an.

Im TREE_FORMAT-Block steht allerdings nicht, wie die Texte des TreeViews formatiert werden sollen. Diese Node-Texte stellen allesamt A-Tags, also Hyperlinks dar. Also kann man sie einfach mit einer CSS-Style-Definition formatieren, die im HEAD-Abschnitt der HTML-Datei enthalten ist:

    <head>
    <style type="text/css">
    §§§
    </style>
    </head>

Viel ist das nicht – aber Sie werden sich denken können, dass auch hier das $$$ nur ein Platzhalter ist, der später per VBA mit der tatsächlichen Definition ersetzt wird, damit das TreeView von Access aus formatiert werden kann.

Die entsprechende Funktion im Formular dafür nennt sich GetStyleString() und bastelt einfach die Style-Anweisungen hartkodiert zusammen. (Vorgriff: In der anderen in der Beispieldatenbank enthaltenen Demo wird dieser Style-String allerdings dynamisch aus Klasseneigenschaften zusammengesetzt.)

Bliebe noch die Variable TREE_ITEMS als Parameter für die COOLjsTree-Funktion. Darin müssen die Nodes, also die Struktur des Baums, definiert sein. Das COOLJSTree sieht dafür etwa folgenden Aufbau vor:

    [0,[1],[2],[3],[4,[5],[6]]]

Jeder durch eine Zahl symbolisierte Node wird durch eine geöffnete und eine geschlossene rechteckige Klammer angegeben. Innerhalb der Klammer können jedoch wiederum weitere Nodes stehen, sodass eine hierarchische Struktur entsteht. Im Beispiel ist 0 das Root-Node, dem als Kindelemente die Nodes 1, 2, 3 und 4 untergeordnet sind. Der Node 4 seinerseits beherbergt die Unterelemente 5 und 6.

Statt der Zahlen sind jeweils mindestens drei Parameter einzusetzen: Optional die ID des Elements, zwingend sein Text, die URL für den Hyperlink, das Hyperlink-Zielfenster beziehungsweise Ziel-Frame und optional ein für den jeweiligen Node spezifischer Formatierungs-String. Beispiel für einen Node-Eintrag:

    [{"id":3}, ''Mein Node-Text'', ''http:/xyz.com'', ''_self']

Ein Mausklick auf den Node mit der ID 3 und der Aufschrift Mein Node-Text führte dazu, dass im selben Browser-Fenster zu http:/xyz.com navigiert würde – zu diesem Umstand später mehr. Die Variable TREE_ITEMS ist im Template so codiert:

    <script type="text/javascript">
    var iconpath;
    iconpath="";
    var TREE_ITEMS = [@@@];
    </script>

Abermals finden Sie hier einen Platzhalter, der im Formular dann über die Prozedur GetMenuString ersetzt wird, in der übrigens, wie bei den Styles, der Node-Aufbau hartkodiert ist:

    sMenu = GetMenuString()
    sDoc = Replace(sDoc, "@@@", sMenu)

Über dieses Template-System lässt sich schön die Baumstruktur dynamisch anlegen und anzeigen. Nebenbei sei erwähnt, dass das Template statt über eine Datei genauso gut auch aus einem Memo-Feld einer Tabelle bezogen werden könnte.

Auf Klicks reagieren

Üblicherweise will man bei Klick auf einen Node ein Ereignis auslösen und darauf reagieren. Das lässt sich mit dem CoolJSTree recht einfach realisieren, weil alle Nodes Hyperlink-Ziele enthalten.

Ein Klick führt also dazu, dass ein neues Dokument geladen werden will. Das aber kann man mit der Ereignisprozedur BeforeNavigate des WebControls abfangen (s. Listing 1).

Listing 1: Prozedur, die durch das Ereignis BeforeNavigate ausgelöst wird

Private Sub oTree_BeforeNavigate2(ByVal pDisp As Object, URL As Variant, Flags As Variant, TargetFrameName As Variant, PostData As Variant, Headers As Variant, Cancel As Boolean)
    If Right(URL, 1) = "x" Or Right(URL, 1) = "y" Then
        Dim sRet As String
        Dim n As Long
        n = InStrRev(URL, "\")
        sRet = Mid(URL, n + 1)
        sRet = Replace(sRet, "about:", "")
        LblInfo.Caption = "Click auf " & sRet
        Cancel = True
    End If
End Sub

URL ist in der Ereignisprozedur der Parameter, welcher das Hyperlink-Ziel zurückgibt. Findet die Routine hier am Ende ein x oder ein y, so erfolgt eine Spezialbehandlung. Das macht deshalb Sinn, weil in der Demo ein Node etwa so definiert wird:

    [{"id":3}, ''Node-Text'', ''3x'', null]

Als Hyperlink wurde 3x angegeben, also die Node-ID mit einem Suffix x kombiniert, was zur URL about:3x führt, weil about:blank quasi das Root-Verzeichnis der Webseite ist.

Per String-Behandlung wird zuerst die ID aus der URL ermittelt, die weiterverwendet werden kann – hier zur Ausgabe in einem Bezeichnungsfeld – und schließlich der Parameter Cancel auf True gesetzt. Das führt dazu, dass eben nicht zum Hyperlink-Ziel navigiert, der Sprung also abgebrochen wird, und das TreeView im Browser stehen bleibt, als wäre nichts geschehen.

Gibt es Anwendungsfälle für dieses einfache Formular

Da der Inhalt des TreeViews statisch ist, könnte man es etwa zu Navigationszwecken benutzen, um andere Formulare oder Berichte aufzurufen, oder aber, um eine Dokumentation bereitzuhalten, wie in Winhelp- oder CHM-Help-Dateien. Wenn lediglich hierarchische Strukturen dargestellt werden sollen, können Sie gar auf die Routine in BeforeNavigate verzichten.

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