Formularfelder im Browser ansprechen und auswerten

Veröffentlicht: 11. Okt 2004 | Aktualisiert: 14. Nov 2004

Von Mathias Schiffer

Die Microsoft Newsgroups sind eine Quelle schier unerschöpflichen Wissens, das nahezu auf Knopfdruck abrufbar ist: Hunderte deutschsprachige Entwickler vom Einsteiger bis zum Profi treffen sich hier täglich virtuell, um Fragen zu stellen oder zu beantworten. Auch themennahe Probleme, Ansichten und Konzepte werden miteinander diskutiert. Sie kennen die Microsoft Newsgroups noch nicht? Detaillierte Information für Ihre Teilnahme finden Sie auf der Homepage der Microsoft Support Newsgroups.

Diese Kolumne greift regelmäßig ein besonders interessantes oder häufig nachgefragtes Thema aus einer der Entwickler-Newsgroups auf und arbeitet es aus.

Auf dieser Seite

 Aus der Visual Basic Newsgroup microsoft.public.de.vb:
 Abholen eines IE-Objekts
 HTMLWindow2 und HTMLDocument - Fenster und Dokumente im Internet Explorer
 Stolperfalle Frames
 forms-Collection im HTMLDocument
 Finale: Formularelemente ansprechen

Aus der Visual Basic Newsgroup microsoft.public.de.vb:

Ich muss für ein Projekt aus einem Formular im Internet Explorer eingetragene Werte auslesen können. Außerdem muss ich das Formular im Internet Explorer auch mit neuen Werten befüllen und es abschicken können. Ich komme aber mit dem Win32-API nicht weiter.

Das Win32-API ist für diesen Zweck nicht geeignet, da Formularfelder, die Sie im Internet Explorer sehen, keine eigenen Fenster sind und somit keine Fensterhandles haben, über die Sie mit Hilfe des Win32-API weiter vorgehen könnten.

Der Internet Explorer bietet jedoch ein ausgefeiltes und extrem umfassendes Objektmodell an, mit dessen Hilfe sich der Browser selber ebenso ansprechen lässt wie angezeigte Dokumente und deren Bestandteile.

 

Abholen eines IE-Objekts

Eine Instanz des Internet Explorers erzeugen

Für die Nutzung des Objektmodells des Internet Explorer benötigen Sie zunächst einen Objektverweis, den Sie durch das Neuanlegen einer Instanz des Internet Explorer selber erzeugen können. Damit bewirken Sie nichts anderes als einen - zunächst unsichtbaren - Start eines neuen Browserfensters.

Dies geht ganz einfach über die Verwendung von CreateObject oder, soweit Sie einen Projekt-Verweis auf die Bibliothek "Microsoft Internet Controls" setzen, mithilfe des New-Operators:

' CreateObject-Variante / Late Binding:
Dim objIE As Object
  
  Set objIE = CreateObject("InternetExplorer.Application")
  objIE.Navivate2 "About:blank"
  objIE.Visible = True
   
   
' Mit Verweis auf "Microsoft Internet Controls":
Dim objIE As SHDocVw.InternetExplorer
  
  Set objIE = New SHDocVw.InternetExplorer
  objIE.Navigate2 "About:blank"
  objIE.Visible = True

Bestehende Instanz des Internet Explorer ermitteln
Möchten Sie eine bereits bestehende Instanz des Internet Explorer ansprechen, können Sie allerdings nicht einfach auf die bewährte Funktion GetObject zurückgreifen. Denn der Internet Explorer trägt sich aus Scripting-Sicherheitsgründen nicht in die von GetObject genutzte systemweite Tabelle instanziierter ActiveX-Objekte (die sog. ROT = "Running Object Table") ein.

Sie können diesen Zugriff jedoch über die ShellWindows-Collection der ShDocVw-Bibliothek erhalten, die Sie mit dem oben schon genannten Verweis referenzieren. Ist der Wert der Document-Eigenschaft eines ShellWindow-Objekts vom Typ HTMLDocument, so ist das entsprechende Objekt ein Objektverweis auf eine laufende Instanz des Internet Explorer.

Mehr zu diesem Lösungsweg erfahren Sie bei Bedarf im MSDN Quickie "Laufende Instanzen des Internet Explorers ermitteln"; hier nur ein kurzer Codeausschnitt dazu, der den Dokumententitel (angezeigt in der Browser-Titelzeile) als Kriterium heranzieht:

' Notwendig: Verweis auf "Microsoft Internet Controls"
  
Private Function GetIE(ByVal WindowTitle As String) As Object
' Ermitteln des Objektverweises einer bestehenden IE-Instanz,
' hier auf Basis des zu übergebenden Dokumententitels.
Dim objShellWindows As New SHDocVw.ShellWindows

  ' Fehler beim Zugriff auf die Document-Eigenschaft ignorieren
  On Error Resume Next
  
  ' Iterieren über alle Shell-Windows
  For Each GetIE In objShellWindows
  
    ' Ist das Document vom Typ HTMLDocument, handelt es sich um ein IE-Fenster:
    If TypeName(GetIE.Document) = "HTMLDocument" Then
      
      If GetIE.Document.Title = WindowTitle Then
        Exit Function ' GetIE hat die passende Referenz
      End If
      
    End If
  
  Next GetIE
  
  ' Bei fehlendem Erfolg ist 'GetIE' hier 'Nothing'.
  ' Hinweis: Bei VB.NET wäre das nicht der Fall.

End Function

 

HTMLWindow2 und HTMLDocument - Fenster und Dokumente im Internet Explorer

Das Document-Objekt eines Internet Explorer-Objekts gibt Ihnen Zugriff auf die im IE dargestellten Dokumente vom Typ HTMLDocument. Diese werden in Fenstern vom Typ HTMLWindow2 dargestellt.

Neben einer Vielzahl anderer Aspekte werden diese Möglichkeiten mit der Typbibliothek MSHTML.tlb zur Verfügung gestellt (Verweis: "Microsoft HTML Object Library"). Natürlich können Sie unter Verzicht auf Ereignisse und Typsicherheit auch ohne das Einbinden der Typbibliothek arbeiten ("Late Binding"). Dafür können Sie entsprechende Objekte typunsicher "As Object" verwenden. In diesem Artikel bleiben wir jedoch bei dem notwendigen Verweis und damit bei der typsicheren Variante.

 

Stolperfalle Frames

Im einfachsten Fall denken wir natürlich daran, dass ein von uns zu erreichendes HTML-Formular direkt im Internet Explorer zur Verfügung stünde. Doch oft genug ist das gar nicht der Fall: Noch immer verwenden sehr viele Seitenersteller Frames, so dass das übergeordnete Dokumentenobjekt im Internet Explorer nicht mehr als lediglich die Definitionsseite für die Frame-Anordnung (das "Frameset") ist.

In einem solchen Fall müssen Sie sich nach dem Auffinden des primären Dokuments im Internet Explorer durch das Frameset hin zum gesuchten Frame samt seinem Dokument durchschlagen, um wieder am gewünschten Ausgangspunkt für weitere Schritte anzukommen.

Zunächst gilt es daher festzustellen, ob die "Hauptseite" im Internet Explorer eine Frameset-Definitionsseite darstellt.

Hierfür stellt ein HTMLDocument-Objekt die frames-Collection zur Verfügung. Sie enthält Verweise auf frame-Objekte, die als Frames für das Dokument definiert sind. Nicht über eine Count-, aber über eine length-Eigenschaft können Sie dabei die Anzahl der Elemente in der Collection ermitteln. Zweiter Unterschied zu einer typischen VB-Collection: Sie müssen diese Collection mit einer Zählervariable durchlaufen; eine Iteration mit For Each ist nicht möglich.

Hier zwei einfache Beispiele, wie Sie mit der frames-Collection arbeiten können:

' Notwendig: Verweis auf "Microsoft HTML Object Library"

Private Function HasFrames(ByVal Document As HTMLDocument) As Boolean
' Gibt zurück, ob ein HTMLDocument in Frames unterteilt ist
  
  'On Error Resume Next ' Bei Änderung auf Late Binding
  HasFrames = (Document.Frames.length) > 0
  
End Function
  
  
Private Function colFrames(ByVal Document As HTMLDocument) As Collection
' Erstellt aus einer frames-Collection eine VB-Collection, die
' Elemente der Collection sind vom Typ HTMLWindow2, die jeweils
' ein Document-Objekt vom Typ HTMLDocument beinhalten.
Dim i As Long
    
  ' Neue Collection erzeugen
  Set Frames = New Collection ' ggf. leere Collection zurückgeben
  
  'On Error Resume Next ' Bei Änderung auf Late Binding
  
  If Document.Frames.length > 0 Then
    For i = 0 To Document.Frames.length - 1
      colFrames.Add Document.Frames.Item(i) ' nach Wunsch: .Document
    Next i
  End If
  
End Function

Wenn's mal wieder länger dauert.
Denken Sie aber auch daran, dass ein Fenster, das Teil eines Framesets ist, in seinem Dokument auch selber wiederum ein Frameset definieren kann. Und auch ein Fenster eines solchen Framesets kann seinerseits wieder ein Frameset definieren (vgl. Abbildung 1).

Newsgroups0904_01

Abbildung 1: Das gesuchte Formular Form befindet sich in diesem Beispiel in einem Fenster des grünen Framesets, das sich in einem Fenster des roten Framesets befindet, das sich in einem Fenster des schwarzen Framesets befindet - kurz: Rekursion, ick hör' dir trapsen!

Allzu weit werden solche Verschachtelungen gerade im Praxisfall nicht gehen, die Stoßrichtung jedoch ist klar: Auf der Suche nach einer Form in einer nicht vorab einschätzbaren Frameset-Materialschlacht wird man auf eine rekursive Lösung angewiesen sein.

Ziel eines entsprechenden Sourcecodes muss es also sein, anhand eines Kriteriums rekursiv durch mögliche Frames von Framesets hinweg ein bestimmtes Dokument zu finden. Für das folgende Beispiel wird als Kriterium der Dokumentenname eines HTML-Dokuments verwendet (der im HTML-Header mithilfe des Tags <TITLE> vergeben wird und bei Hauptfenstern in der Titelzeile des Internet Explorers in der Titelzeile mit angezeigt wird).

' Notwendig: Verweis auf "Microsoft HTML Object Library"

Private Function FindFrame(ByVal Document As HTMLDocument, _
                  Optional ByVal FrameTitle As String _
                           ) As HTMLDocument
' Sucht auf Basis eines Dokuments nach einem Treffer für die
' Kriterien und berücksichtigt dabei, dass jedes Dokument auch
' noch Frames beinhalten könnte, die das gesuchte Dokument
' enthalten könnten (Rekursion).
Dim i As Long
  
  ' Ist das übergebene Dokument das gesuchte?
  If Document.Title = FrameTitle Then
    ' Ja - Ausstieg, Rekursion abwickeln
    Set FindFrame = Document
    Exit Function
  Else
    ' Prüfen, ob es in diesem Dokument Frames gibt
    If Document.frames.length > 0 Then
      ' Über frames iterieren (nicht mit For...Each möglich!)
      For i = 0 To Document.frames.length - 1
        ' Rekursion
        Set FindFrame = FindFrame( _
                        Document.frames.Item(i).Document, _
                        FrameTitle)
        ' Ausstieg im Trefferfall
        If Not FindFrame Is Nothing Then
          Exit Function
        End If
      Next i
    End If
  End If
  
End Function

So haben wir den Weg vom möglicherweise vorhandenen Frameset zu einem Dokument gefunden, das über dessen HTML-Tag TITLE definiert ist. Natürlich sind auch andere, ggf. sogar mehrere gleichzeitig relevante Kriterien denkbar und mitunter notwendig - etwa dann, wenn mehrere Dokumente den gleichen oder gar keinen Titel tragen.

Doch selbst damit ist der Weg zum gesuchten Formular auf einem Dokument noch nicht abgeschlossen.

 

forms-Collection im HTMLDocument

Ein Dokument im Internet Explorer kann durchaus mehr als nur ein Formular beinhalten. Denken Sie etwa daran, dass Sie bei vielen Anbietern durchgängig Textfelder zum Durchsuchen des Angebots finden. Andere Seiten bieten verschiedenste Themen zur Abstimmung an. Dies ist dann oft auch auf Seiten der Fall, auf denen Sie wichtigere Eingaben tätigen können (etwa für eine Bestellung beim Anbieter). Jedes dieser in sich geschlossenen Interaktionselemente ist Teil einer separaten Form des Dokuments.

Ein HTMLDocument bietet aus diesem Grund eine forms-Collection an. Hierin finden Sie Formularobjekte vom Typ HTMLFormElement. Ein HTMLFormElement wiederum besitzt, vergleichbar zur Controls-Collection von Visual Basic für eine Form, eine elements-Collection. Über diese Collection können Sie iterieren, um sämtliche Elemente des Formulars zu erfassen.

Auf Basis des HTMLDocument-Objekts ermitteln Sie also zunächst ein HTMLFormElement-Objekt, das Ihr gesuchtes Formular identifiziert. Im einfachsten Fall ist dies das erste Formular im Dokument, wobei die forms-Collection nullbasiert arbeitet:

Dim objHTMLForm As HTMLFormElement
  Set objHTMLForm = Document.forms.Item(0)

Suchen Sie nach einem anderen Formular in dem Dokument, so verwenden Sie dessen zugehörigen Index oder, allgemeiner, Sie durchwandern die forms-Collection des HTMLDocument-Objekts unter Prüfung eines Kriteriums (auch hier verwendet das Beispiel als Kriterium willkürlich den Namen des Formulars; andere Kriterien sind denkbar und ggf. notwendig). Diese Collection unterstützt auch den für "For Each" notwendigen Enumerator:

Private Function FindForm(ByVal Document As HTMLDocument, _
                 Optional ByVal FormName As String _
                          ) As HTMLFormElement
' Notwendig: Verweis auf "Microsoft HTML Object Library"
' Ermittelt in einem HTMLDocument dasjenige Formular, das den
' Namen FormName trägt. Bei Misserfolg: Rückgabewert Nothing.
  
  ' Iteration über die forms-Collection:
  For Each FindForm In Document.forms
  
    ' Auf erfülltes Kriterium prüfen:
    If FindForm.Name = FormName Then
      Exit For '
    End If
  
  Next FindForm
  
  ' Bei fehlendem Erfolg ist FindForm hier 'Nothing'.
  ' ACHTUNG: Bei VB.NET ist das nicht der Fall!
  
End Function

Nun haben wir endlich das richtige Formular im richtigen Dokument gefunden: Es war ja auch an der Zeit! Schließlich ist unsere eigentliche Aufgabe, die Elemente dieses Formulars ansprechen zu können!

 

Finale: Formularelemente ansprechen

Der Rest des Lösungsweges ist etwas einfacher als die anstrengenden Suchaktionen des richtigen Formulars im richtigen Dokument eines möglicherweise existierenden Frames.

Denn auch ein Formular-Objekt vom Typ HTMLFormElement hat eine besonders interessante Collection anzubieten: Die Eigenschaft elements erlaubt den Zugriff auf alle Elemente eines HTML-Formulars, von Textboxen über Optionsbuttons bis hin zu Schaltflächen. Dabei können Sie über den Index sowie über einen vergebenen HTML-Elementnamen als String auf ein Element zugreifen.

So können Sie etwa die Namen, Typen und Werte aller Form-Elemente einfach auflisten. Nehmen wir für unser Beispiel ein Formular an, das Sie mit den angereichten Werkzeugen über eine Objektvariable objHTMLForm referenziert haben:

Dim element As Object
  
  ' Alle Elemente der HTML-Form ausgeben:
  For Each element In objHTMLForm.elements
    
    ' Ausgabe der wichtigsten Eigenschaften:
    Debug.Print "Element " & element.Name & _
                " vom Typ " & element.Type & _
                ", Wert: " & element.Value & _
                ", Checked: " & element.Checked
  
  Next element

Beispiele für den Zugriff auf HTML-Elemente einer Form
Möchten Sie auf ein Textfeld im Formular zugreifen, das den Namen "Nachname" trägt? Kein Problem dank der elements-Collection:

' HTML-Quellcode für das Element im Beispiel:
<input type="text" name="Nachname" size="20">
  
  
  ' Textfeld in der Form mit Wert versehen
  objHTMLForm.elements("Nachname").Value = "Schiffer"
  
  
  ' Den Wert aus dem Textfeld auslesen
  Dim strNachname As String
  strNachname = objHTMLForm.elements("Nachname").Value

Ein Kontrollfeld ("Checkbox") mit dem Namen "AGBGelesen" abfragen oder ankreuzen? Aber sicher:

' HTML-Quellcode für das Element im Beispiel:
<input type="checkbox" name="AGBGelesen" value="OK" checked>Ich habe die AGB gelesen und akzeptiere sie.
  
  
  ' Kontrollfeld in der Form ankreuzen
  objHTMLForm.elements("AGBGelesen").Checked = True
  
  
  ' Den Status des Kontrollfelds auslesen
  Dim blnIsChecked As Boolean
  blnIsChecked = objHTMLForm.elements("AGBGelesen").Checked

Wie wäre es mit einem Array von vier Optionsfeldern des Namens "Zahlungsweise" und deren Werten "Rechnung", "Nachname", "Vorkasse" und "Bankeinzug"? Sie möchten per Bankeinzug zahlen? Gerne:

' HTML-Quellcode für das Element im Beispiel:
<p>Zahlungsweise:
<input type="radio" value="Rechnung" name="Zahlungsweise">Rechnung
<input type="radio" value="Nachnahme" name="Zahlungsweise">Nachnahme
<input type="radio" value="Vorkasse" name="Zahlungsweise">Vorkasse
<input type="radio" value="Bankeinzug" checked name="Zahlungsweise">Bankeinzug</p>
</P>
  
  
  ' Ermitteln, welches Optionsfeld angekreuzt ist:
  Dim strSelectedOption As String
   
  For Each element In objHTMLForm.elements("Zahlungsweise")
    If element.Checked Then
      strSelectedOption = element.Value
    End If
  Next element
   
  ' Ausgabe des Ergebnisses
  MsgBox strSelectedOption, vbInformation, _
         "Ausgewählte Zahlungsweise-Option"
   
  
  ' Ein Zahlungsweise-Optionsfeld auswählen, als Beispiel:
  ' Das zweite vorhandene Optionsfeld auswählen (nullbasiert):
  objHTMLForm.elements("Zahlungsweise")(1).Checked = True
   
  
  ' Ein Zahlungsweise-Optionsfeld auf Basis seines Werts
  ' auswählen, hier: "Bankeinzug"
  For Each element In objHTMLForm.elements("Zahlungsweise")
    If element.Value = "Bankeinzug" Then
      element.Checked = True
    End If
  Next element

Weitere Standardelemente eines Formulars lassen sich ebenso einfach ansteuern.

Auch das Absenden eines ausgefüllten Formulars sollte keine Schwierigkeiten bereiten:

' HTML-Quellcode für das Element im Beispiel:
<input type="submit" value="Absenden" name="btnSubmit">
  
  
  ' Absenden des Formulars
  objHTMLForm.submit

Dumm nur, wenn man beim Ausfüllen eines Formulars schlimme Fehler gemacht hat. Aber zum Glück weisen ja viele Formulare bis heute einen Button mit der Möglichkeit auf, alle gesetzten Werte zu löschen: Au revoir!

' HTML-Quellcode für das Element im Beispiel:
<input type="reset" value="Zurücksetzen" name="btnReset">
  
  ' Zurücksetzen des Formulars
  objHTMLForm.reset