Javascript-VBA-Bridge

Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.

Es gibt zahlreiche Anwendungsbereiche für einen eingebetteten Webbrowser in Access-Formularen. Ob es um die statische Anzeige von HTML-Dateien geht, um dynamisch generierte Webseiten, die etwa Berichte ausgeben, oder um die Nutzung von Webservices. Microsoft hat dies erkannt und in Access 2010 das neue Webbrowser-Steuerelement eingeführt. Während es einfach ist, den Inhalt einer Webseite von VBA aus zu steuern, sei der Content nun durch Stringverkettung erzeugt oder über das DOM-Objektmodell des Browsers, wird es schwierig, von einer eingebetteten Webseite aus über Skripte auf Access und VBA zuzugreifen, um etwa Daten abzuholen. Wie das dennoch geht, finden Sie im Folgenden beschrieben.

Webbrowser-Steuerelemente

Bis zu Access 2010 ist es notwendig, den Internet Explorer als ActiveX-Steuerelement in ein Formular einzufügen. Über das Entwurfsmenü Einfügen|ActiveX-Steuerelement…|Microsoft Web Browser wird ein Browser-Fenster im Detailbereich angelegt. Der Internet Explorer, egal in welcher Version, stellt dieses Control von Haus aus zur Verfügung. Das Control hat also mit Access oder dessen Installation selbst nichts zu tun.

In Access 2010 bedient man sich der Steuerelementesammlung im Entwurfs-Ribbon und wählt das Webbrowser-Steuerelement aus (s. Bild 1).

A2010CtlRibbon.png

Bild 1: Auswahl des Webbrowser-Steuerelements im Entwurf-Ribbon von Access 2010

Dieses neue Steuerelement ist im Grunde nichts anderes als ein Wrapper für das ActiveX-Control des Internet Explorers. Es weist somit weitgehend die gleichen Eigenschaften und Methoden auf. Der wesentliche Unterschied besteht darin, dass dieses Element sich direkt an ein Feld der Datenherkunft des Formulars binden lässt.

Dabei muss es sich um ein Textfeld handeln, das die URL der Seite enthält, zu welcher navigiert werden soll. HTML-Code kann man hier nicht direkt übergeben.

Dem ActiveX-Control hingegen muss man per VBA sagen, welche Seite es anzeigen soll. Dazu bedient man sich der Methode Navigate2 des Objekts:

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

In diesem Fall wird eine leere Webseite im Browser angelegt. Tatsächlich funktioniert dasselbe auch mit dem Webbrowser-Steuerelement von Access 2010, das diese Methode eigentlich gar nicht kennt.

Der feine Unterschied liegt hier in der Ansprache einmal des Steuerelements selbst (Me!ctlWeb) und davon abweichend des enthaltenen Steuerelements (Me!ctlWeb.Object). Das letztere Objekt stellt die Webbrowser-Klasse der Bibliothek ShDocVw dar, auf die automatisch ein Verweis im VBA-Projekt hinzugefügt wird – sowohl im Falle des ActiveX-Controls, wie auch beim Access 2010-Steuerelement. Damit stehen dann auch im Access 2010-Steuerelement dieselben Eigenschaften und Methoden des DOM-Objektmodells der MSHTML-Bibliothek zur Verfügung.

Meist wird man das Steuerelement im Formular so ausstatten, dass es die verfügbare Breite einnimmt. Bei veränderbaren Formularen ist es dann bis Access 2007 notwendig, im Resize-Ereignis des Formulars das Webbrowser-Steuerelement neu zu dimensionieren, indem man seine Eigenschaften Width und Height setzt.

Das klappt allerdings nur in Access 2003. In früheren Versionen ändert sich die einmal im gespeicherten Entwurf gesetzte Größe nicht und wird auf ewig beibehalten.

Unter Access 2007 und 2010 verwenden Sie dafür das Verankern-Feature (Tab Anordnen|Position|Anker) und stellen das Control etwa auf Nach unten und quer dehnen ein.

Web-Inhalte laden

Häufig werden Sie einfach nur eine Webseite im Internet darstellen wollen. Das Laden dieser Webseite erledigt wieder die Navigate2-Methode des Webbrowsers:

Me!ctlWeb.Object.Navigate2 "http://www.access-im-unternehmen.de"

Ebenso können Sie natürlich eine lokal im Dateisystem vorhandene HTML-Datei aufrufen. Als Parameter dient dann der ganz normale Pfad, wie sie ihn auch im Explorer angeben würden.

Interessant wird es, wenn Sie den Inhalt der Webseite dynamisch erzeugen möchten. Eine gängige Methode ist es, den HTML-Code dabei als String in VBA zusammenzusetzen, diesen lokal als Datei abzuspeichern und die Datei schließlich ins Webbrowser-Steuerelement zu laden. Dabei können etwa auch Inhalte aus Tabellen, die über Recordsets ausgelesen werden, zum Zuge kommen.

Einfacher, da ohne Umweg über das Dateisystem, kann dem Browser aber der Inhalt auch direkt zugewiesen werden. Dafür gibt es die write-Methode des geladenen Dokuments.

Der einzige Grund, das Dateisystem zu bemühen, wären Webseiten, die Grafiken oder Bilder enthalten. In diesem Fall müssen die Bilddateien parallel zu den aufgerufenen HTML-Dateien im Verzeichnis liegen. Per write oder andere Methoden gibt es keine Möglichkeit, Bildobjekte in eine Seite zu bekommen.

Ausnahme sind hier Systeme mit installiertem Internet Explorer 9, dem man neuerdings über HTML5 oder SVG dynamisch Bilder über Skripte und das MSHTML-Modell zuweisen kann. Aber das wäre Thema für einen weiteren Beitrag.

Web-Inhalte schreiben

Das Vorgehen, um den HTML-Code über die write-Methode in die Browser-Seite zu schreiben, lässt sich anhand Listing 1 verdeutlichen, das in der Beispieldatenbank im Formular frmWeb zu finden ist.

Listing 1: Webinhalt dynamisch schreiben

Private Sub cmdScriptor_Click()
    Dim oDoc As Object
    Dim sHTML As String
    CWeb.Navigate2 "about:blank"
    WaitForReady
    Set oDoc = CWeb.Document
    sHTML = "<form><input type=""button"" value=""Knopf"" onClick=""javascript:alert(''Huhu!'');""></input></form>"
    oDoc.write sHTML
    oDoc.body.Style.background = CStr("#D0D0B0")
    oDoc.write "Seite mit Knopf"
End Sub

Damit die write-Methode überhaupt angewandt werden kann, muss im Browser bereits ein Dokument geladen sein – schließlich ist write eine Methode des Document-Objekts.

Und das lässt sich am einfachsten erledigen, indem zunächst eine leere Seite mit about:blank erzeugt wird.

In der Prozedur sehen Sie, dass eine Hilfsvariable CWeb eingesetzt wird, die in der Prozedur nicht deklariert ist. Diese enthält einen Verweis auf das Webbrowser-Steuerelement und ist im Kopf des Moduls so deklariert, dass sie Ereignisse auslösen kann:

Private WithEvents CWeb As SHDocVw.WebBrowser

Die Zuweisung geschieht im Load-Ereignis des Formulars:

Set CWeb = Me!ctlWeb.Object

In weiteren Prozeduren des Formulars kann fortan diese Objektvariable verwendet werden, statt der länglichen Form mit dem Verweis auf das Steuerelement.

Nachdem der Browser angewiesen wurde, ein leeres Dokument zu erzeugen, kann auf dieses in VBA noch nicht unmittelbar zugegriffen werden. Der Browser braucht dafür einige Zeit, die asynchron zum VBA-Code abläuft.

Aus diesem Grund gibt es den Aufruf einer Warteschleife, die in die zusätzliche Prozedur WaitForReady ausgelagert ist:

Private Sub WaitForReady()
    Do
        DoEvents
    Loop Until CWeb.ReadyState >= READYSTATE_INTERACTIVE
End Sub

Der Browser gibt anhand eines Flags der Eigenschaft ReadyState kund, wie es um den Zustand des Dokuments bestellt ist. Ist dieser Wert größer oder gleich 3, was der Konstanten READYSTATE_INTERACTIVE entspricht, dann steht das Dokument für den Zugriff bereit.

Das geladene Dokument wird im weiteren Verlauf der Objektvariablen oDoc zugewiesen, die im Prozedurkopf As Object deklariert ist. An sich könnte man dafür auch gleich den korrekten Typ HTMLDocument verwenden. Doch in diesem Fall streikt VBA beim Aufruf der write-Methode wegen inkompatibler Variant-Datentypen.

Im String sHTML wird der Inhalt der Seite zusammengestellt. Dabei muss man sich vergegenwärtigen, dass das leere Dokument bereits durchaus HTML-Code enthält – nämlich die Elemente HTML und BODY. Die write-Methode schreibt also innerhalb der body-Tags.

Wie am Listing zu erkennen ist, können dabei auch mehrere write-Anweisungen hintereinander folgen. Der Code wird dann sequenziell in die Seite geschrieben.

Um zu demonstrieren, dass die Seite auch anders als durch HTML-Code zu beeinflussen ist, wird ihre Hintergrundfarbe über das DOM-Modell manipuliert, was über die style-Eigenschaft background des body-Elements geschieht.

Nach dem Auslösen der Prozedur über die Schaltfläche (s. Bild 2) ändert sich das Browser-Fenster und zeigt die rudimentäre Webseite an.

frmWeb.png

Bild 2: Das per VBA generierte Webformular im Browser-Steuerelement

Der HTML-Code der Variablen sHTML im Listing besteht aus einem HTML-Formular mit einem input-Element, also einer Schaltfläche, die direkt eine Zeile Javascript ausführt, nämlich die Anzeige eines Meldungsfensters.

Es lässt sich festhalten, dass dem Webbrowser-Steuerelement Web-Inhalte unmittelbar per VBA zugewiesen und dabei auch Javascript-Abschnitte integriert werden können.

Javascript-Funktionen per VBA aufrufen

Üblicherweise werden Javascript-Funktionen in Webseiten dazu verwendet, um auf Aktionen zu reagieren, welche von Ereignissen hervorgerufen werden. In der Regel handelt es sich dabei um Elemente innerhalb eines form-Tags, wie eben der input-Button in Bild 2.

Andere Ereignisquellen könnten das Laden der Seite selbst sein, Maus-Klick-Events auf bestimmte Elemente oder Zeitgeber, die in Intervallen Aufgaben erledigen sollen.

Geht der Umfang des Scripts über eine Zeile hinaus, dann werden solche Funktionen im Interesse besserer Strukturierung separat in den Quelltext geschrieben, anstatt, wie im Beispiel, das Skript direkt in das Element-Tag einzubinden. Der Quelltext hätte alternativ so wie in Listing 2 ausgesehen.

Listing 2: Einfache Webseite mit Javascript-Funktion

<script type="text/javascript">
function fuScript() {
     alert(''Huhuu!'');
}
</script>
<form>
    <input type="button" value="Knopf" onClick="fuScript()"/>
</form>

Hier wird im input-Element angegeben, dass auf Klick die Skript-Funktion fuScript aufgerufen werden soll.

Interessant wäre nun, wenn sich solche Funktionen, die ja auch weit umfangreicher programmiert sein können, nicht nur durch Ereignisse der Webseitenelemente ausführen ließen, sondern auch direkt aus VBA heraus.

Das würde eventuell umfangreiche Manipulationen der Seite über das Objektmodell DOM überflüssig machen, denn über Javascripte lassen sich Webinhalte erheblich einfacher steuern als über MSHTML.

Tatsächlich ist dies auf verblüffend einfache Weise möglich. Das window-Objekt des DOM-Modells kennt die Anweisung execScript, mit der beliebige Skripte im Quelltext aufgerufen werden können. Dabei wird sogar ein etwaiger Rückgabewert der Funktion berücksichtigt. An das window-Objekt kommt man wiederum einfach über die Eigenschaft parentWindow eines geladenen Dokuments.

Dieses window-Objekt stellt im Prinzip das Rahmenfenster des Browsers beziehungsweise eines Browser-Tabs dar.

Um das Skript in Listing 2 anzusprechen, reicht eine einzige VBA-Zeile aus:

Web.Document.parentWindow.execScript "fuScript()"

Wichtig ist, dass die Klammern für die Funktion mit angegeben werden. Innerhalb dieser Klammern können Sie auch Parameter übergeben, falls die Javascript-Funktion diese verlangen sollte. Selbstverständlich hat die Parameter-Syntax als String exakt so auszusehen, als würde die Funktion von einem Web-Element oder einem anderen Javascript aufgerufen.

Im Formular frmIDocHost der Beispieldatenbank finden Sie ein Beispiel dafür. Dort löst die Schaltfläche cmdExecScript eine Prozedur aus, welche die Zeile oben aufruft. Nur die Meldung wurde, wie in Bild 3 zu sehen, etwas verändert.

frmIDocHost1.png

Bild 3: Aufruf einer Javascript-Funktion aus VBA heraus

Im zweiten Teil des Beitrags zeigen wir Ihnen, wie sich diese Schnittstelle von VBA nach Javascript gewinnbringend einsetzen lässt, um eine GoogleMaps-Seite zu steuern.

VBA-Funktionen von Javascript aus aufrufen

So einfach sich die Schnittstelle in Richtung Browser gestaltet, so kompliziert wird sie leider in die andere. Woher sollen schließlich eine Webseite oder das Webbrowser-Steuerelement wissen, in welcher Anwendung sie zum Einsatz kommen und wo sie eingebettet sind

Dafür gibt es zwei speziell vorbereitete Schnittstellen des Internet Explorers, die Interfaces IDocHostUIHandler und ICustomDoc. Der Mechanismus sieht im Prinzip so aus:

Ist eine Webseite im Browser geladen, dann fragt das Webbrowser-Steuerelement in der Applikation, die es enthält, nach, ob diese das COM-Interface IDocHostUIHandler unterstützt.

Im Fall des in einem Access-Formular enthaltenen Webbrowser-Steuerelements ist das die Formularklasse, die befragt wird. Diese muss das Interface implementiert haben.

Fällt die Antwort positiv aus, dann ruft der Browser, je nach Situation und Zustand, verschiedene Methoden des Interfaces auf. Die wichtigste davon ist die Funktion GetExternal. Sie verlangt als Rückgabewert das Klassenobjekt, das quasi als Rahmen für Skripte im Browser herhalten soll.

Man übergibt ihr etwa die Formularklasse, also Me. Daraufhin leitet jede Angabe des window.external-Objekts in Javascript genau auf diese Klasse und ihre Methoden. Ist etwa im Formular eine VBA-Funktion fuTest() untergebracht, dann kann Javascript diese so ansprechen:

window.external.fuTest();

Das ist allerdings noch nicht die ganze Wahrheit. Damit der Browser überhaupt nach dieser Schnittstelle fragt, ist zuvor noch anzugeben, wo er nachfragen soll, wo sich also die Klasse mit dem implementierten Interface befindet. Diese ist ihm über Umwege mitzuteilen:

Erst muss wieder ein spezielles Interface des Browsers bemüht werden, das ICustomDoc. Es ist automatisch in jedem geladenen Web-Dokument implementiert.

Man kann deshalb per Set-Anweisung ein HTMLDocument auf ein ICustomDoc casten. Und schließlich übergibt man mit dessen Methode SetUIHandler dem Browser die Information, dass die Formularklasse das Objekt der Begierde ist.

Wie funktioniert dieses kompliziert wirkende Gebilde nun in der Realität

Schnittstellen bereitstellen

Zuerst brauchen wir die Definitionen der Schnittstellen ICustomDoc und IDocHostUIHandler. Leider sind diese in keiner Bibliothek zu finden, die Windows mitbringen würde. Daher wird eine externe Typbibliothek gebraucht, die diese Interface-Deklarationen enthält.

Wie so oft wird man bei E. Morcillos Bibliothek olelib.tlb fündig, die einen großen Teil der COM-Interfaces und Objektklassen enthält, welche von Windows und seinen Komponenten intern verwendet werden. Die Bibliothek ist Teil des Downloads zu diesem Beitrag, heißt aber, da etwas modifiziert, olelib_moss.tlb.

Diese Bibliothek muss als Verweis in das VBA-Projekt eingebunden werden. Im Unterschied zu ActiveX-Komponenten muss eine Typbibliothek in Form einer .tlb-Datei nicht zwingend im System registriert werden.

Es reicht, wenn VBA die Datei findet. Das ist allerdings eben nicht der Pfad der Datenbankdatei, denn der wird in die Suche nicht einbezogen.

Die .tlb-Datei sollte deshalb in das Systemverzeichnis von Windows gespeichert werden, also etwa windows\system32 oder windows\SysWOW64 bei 64bit-Windows. Leider sind hierfür wieder mal Administratorrechte nötig.

Handler definieren

Nun muss dem Webbrowser-Steuerelement gesagt werden, welche Klasse den IDocUIHost-Handler bereitstellt. Wie erwähnt, läuft das über das Interface ICustomDoc, das seinerseits Bestandteil eines jeglichen Webdokuments ist.

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

Testzugang

eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar