Explorer Control

Für manche Aufgaben benötigen auch Datenbanken die Anzeige oder Auswahl von Dateien oder Ordnern des Betriebssystems. Manche davon lassen sich mit dem Dateiauswahldialog von Office erledigen. Hin und wieder ist aber eine unmittelbare Einsicht in das Dateisystem nützlich. Der Windows Explorer lässt sich mit Einschränkungen fernsteuern, doch schicker wäre ein Explorer direkt in einem Formular. Und das geht!

Explorer Browser Control aus Windows

Den Explorer komplett nachzubilden, ist in der Regel mit gewaltigem Aufwand verbunden. Hier sind unzählige Shell-Objekte zu berücksichtigen. Wer etwa das Access 2007 Praxisbuch kennt, findet in der zentralen Beispielanwendung dmsBase ein ActiveX-Steuerelement ShellExplorer.ocx, das zur Laufzeit weitgehend dem Windows Explorer gleicht. Die Programmierung dieses Controls benötigte über 20.000 Zeilen Code.

Diese Zeiten sind seit Windows Vista vorbei! Microsoft hat hier ein neues Control in den Shell-Komponenten verbaut, welches sich ExplorerBrowser nennt. Dabei handelt es sich um einen kompletten Explorer mit allem Zubehör als einbettbares Fenster. In gewisser Weise ähnelt diese Komponente dem Webbrowser Control, welches den Internet Explorer als ActiveX-Steuerelement zur Verfügung stellt. Nur gibt es die ExplorerBrowser-Komponente eben leider nicht als ActiveX-Control. Sie muss stattdessen über COM-Schnittstellen erzeugt und gesteuert werden. Das wäre weiter nicht tragisch, enthielte denn irgendeine Windows-Bibliothek, die sich in die Verweise von VBA eintragen ließe, die entsprechenden Schnittstellendefinitionen. Das ist jedoch nicht der Fall.

OLEEXP als Nachfolger von OLELIB

Zum Glück gibt es Typbibliotheken, die diesem Umstand abhelfen. Der Microsoft MVP für Visual Basic 6, Eduardo Morcillo, stellte einst die geniale Bibliothek olelib.tlb bereit, die in großem Umfang COM-Schnittstellen von Windows-Komponenten definiert. Mit dieser können Dinge erreicht werden, die sich auch mit Unmengen von API-Funktionen nicht realisieren lassen. Das Problem mit dieser Bibliothek aus dem Jahre 2003 ist, dass sie ziemlich ins Alter gekommen ist. Sie kann nur abbilden, was bis zu diesem Zeitpunkt bekannt war – und das sind maximal die Schnittstellen von Windows XP.

Das fiel auch anderen Programmierern auf. Es gibt immer noch rastlose Geister, die das altehrwürdige VB6, auf dessen Stand das Pendant VBA nun mal leider stehenblieb, weiterentwickeln. Viele kompetente Mitstreiter finden sich im Forums vbforums.com. Der User fafalone hat sich an die Arbeit gemacht, die olelib mit neuem Leben zu füllen, und in die abgeleitete neue Bibliothek oleexp.tlb Schnittstellendefinitionen eingebracht, die bis Windows 7 reichen. Und einer der Neuen ist das Interface zum ExplorerBrowser.

Es handelt sich, wohlgemerkt, nicht um eine aktive Komponente! Eine tlb ist lediglich eine Typbibliothek, die sich auf aktive Komponenten bezieht. Es ist deshalb in Ihrem Unternehmen nicht zu befürchten, dass sie bei der Rasterfahndung nach gefährdenden Komponenten durchfällt, wie manche andere ActiveX-Dateien. Sie können sie getrost in die Verweise Ihrer Datenbank laden, wozu sie noch nicht einmal zwingend registriert werden muss. Die Methode References.LoadFromFile erlaubt auch die Integration zur Laufzeit.

Registrieren der OLEEXP

Freilich ist es dennoch sinnvoll, die Bibliothek im System zu registrieren, damit sie in Zukunft auch für andere VBA-Projekte zur Verfügung steht. Im Verzeichnis zur Beispieldatenbank explorerctl.accdb finden Sie neben der oleexp.tlb noch die ausführbare Datei regtlibv12.exe, die solche Typbibliotheken registrieren kann. Das ist ein Kommandozeilen-Tool von Microsoft, das Sie wahrscheinlich auch auf Ihrem System als Bestandteil des NET-Frameworks 4 vorfinden. Der Einfachheit halber haben wir die Datei in das Verzeichnis der Datenbank kopiert.

Damit Sie nicht umständlich in der Windows-Kommandozeile herumhantieren müssen, ruft eine weitere Datei register_tlb.bat die regtlibv12 auf und übergibt ihr als Parameter die oleexp.tlb. Die Registrierung verlangt über die UAC automatisch nach erhöhten Rechten. Die Registrierung der Bibliothek erfolgt danach ganz im Stillen. Nach diesem Vorgang können Sie die Bibliothek über die Verweise von VBA und den Eintrag oleexp – olelib with modern interfaces by fafalone in Ihr Projekt laden.

Die Klasse ExplorerBrowser

Im VBA-Objektkatalog stellen Sie links oben die Bibliothek oleexp ein und navigieren danach zur Klasse ExplorerBrowser. Der Objektkatalog gibt leider keine Auskunft darüber, ob eine Klasse instanzierbar ist oder lediglich eine Interface-Definition darstellt. Sie behelfen sich damit, dass Sie das Schlüsselwort New ins VBA-Direktfenster schreiben und die gewünschte Bibliothek anfügen. Etwa so:

New oleexp.

Sobald Sie den Punkt anfügen, zeigt Intellisense alle Klassen an, die in der Library instanzierbar sind. Beim ExplorerBrowser ist das tatsächlich der Fall. Eine Instanz kann also per New-Operator erzeugt werden, und die sonst häufig benötigten API-Aufrufe mit CoCreateInstance et cetera entfallen. Bild 1 zeigt die recht überschaubaren und dennoch mächtigen Methoden der Klasse, auf die wir im Einzelnen noch zu sprechen kommen.

Die Klasse ExplorerBrowser und ihre Methoden im VBA-Objektkatalog

Bild 1: Die Klasse ExplorerBrowser und ihre Methoden im VBA-Objektkatalog

Sie erzeugen den Explorer im Formular also, indem Sie eine modulweit gültige Objektvariable deklarieren und ihr eine neue Instanz der Klasse zuweisen:

Dim oExplBrowser As oleexp.ExplorerBrowser
Private Sub Form_Load()
Set oExplBrowser = New oleexp.ExplorerBrowser
...

Damit tut sich indessen noch nichts. Erst müssen noch einige Methoden des Objekts aufgerufen werden, die grundlegende Einstellungen anweisen. Die wichtigste davon ist die Initialize-Methode, welche dem Objekt unter anderem sagt, wo der Explorer zu platzieren ist. Dazu gleich mehr. Davor zeigt Ihnen aber Bild 2, wie das Resultat aussieht. Auf der linken Seite findet sich der Verzeichnisbaum mit allen Elementen. Nicht nur das Dateisystem ist vorhanden, sondern etwa auch Netzlaufwerke, Bibliotheken und die Systemsteuerung! Rechts zeigen sich die Elemente des Verzeichnisses. Die Spaltenköpfe können auf gleiche Weise manipuliert werden, wie sonst auch. Der Toolbar oben enthält dieselben Elemente, wie der Windows Explorer. Die Ansicht und das Layout können also darüber gesteuert werden. Selbstverständlich funktionieren auch Drag And Drop und alle Kontextmenüs samt gegebenenfalls installierten Shell Extensions.

Im Formular fmExplorerSimple ist nur ein Windows Control im Detailbereich untergebracht, welches den Explorer abbildet

Bild 2: Im Formular fmExplorerSimple ist nur ein Windows Control im Detailbereich untergebracht, welches den Explorer abbildet

Ein Blick in die Entwurfsansicht des Formulars (s. Bild 3) verblüfft möglicherweise etwas. Hier finden Sie tatsächlich kein einziges Steuerelement, sondern nur den Detailbereich vor. Es ist Aufgabe der Initialize-Methode, auf dem Detailbereich das ExplorerBrowser Control zu rendern.

Die Entwurfsansicht des Formulars frmExplorerSimple stellt sich in der Tat äußerst simpel dar

Bild 3: Die Entwurfsansicht des Formulars frmExplorerSimple stellt sich in der Tat äußerst simpel dar

Initialisieren des ExplorerBrowsers

Die Initialize-Funktion hat folgende Syntax:

Initialize (hwnd As Long, rct As RECT,  pfs As FOLDERSETTINGS) As Long

Der Aufruf dieser einen Funktion führt schon zur Anzeige des Explorers im Formular – wenn denn die Parameter korrekt übergeben werden.

Das wäre zunächst das Fenster-Handle hwnd, das angibt, auf welchem Fenster das Control angebracht werden soll. Der Typ RECT für die Variable rct ist ebenfalls in der oleexp-Bibliothek definiert und steuert die Positionierung des Controls:

Type RECT
     Bottom As Long
     Left As Long
     Right As Long
     Top As Long
End Type

Left ist der horizontale Abstand zum linken Rand des Host-Fensters, Top der vertikale zum oberen Rand. Die Breite und Höhe des Controls ergeben sich aus Right und Bottom. Ist Left etwa 20 und Right 300, so wird die Breite des Controls 280 sein. Mit 30 für Top und 200 für Bottom entsteht eine Höhe von 170. Alle Angaben in Pixel.

Der letzte Parameter pfs muss eine Variable vom Typ oleexp.FOLDERSETTINGS sein. Der Typ setzt zwei Enumerationskonstanten zusammen:

Type FOLDERSETTINGS
     fFlags As FOLDERFLAGS
     ViewMode As FOLDERVIEWMODE
End Type

FOLDERFLAGS und FOLDERVIEWMODE sind Konstanten, die über den Or-Operator zusammengesetzt werden können. Beispiel:

Dim pfs As oleexp.FOLDERSETTINGS
pfs.fFlags = FWF_ALIGNLEFT Or FWF_NOWEBVIEW
pfs.ViewMode = FVM_DETAILS

Damit stellt das Control die Dateien dann einerseits auf Links angeordnet ein und verbietet die Web-Ansicht (fFlags), andererseits wird die Detail- beziehungsweise Report-Ansicht angeordnet (ViewMode). FVM_SMALLICON etwa würde zur Ansicht Kleine Symbole führen, FVM_THUMBNAIL zu Große Symbole.

Schon beim Initialisieren können also grundlegende Eigenschaften des Controls gesetzt werden.

Kopfzerbrechen bereitet indessen das benötigte Fenster-Handle hwnd. Zwar weist auch das Access.Form-Objekt eine Eigenschaft hwnd auf, doch dieses Handle bezieht sich auf das gesamte Fenster, nicht aber auf einen Formularbereich, wie den Detailbereich. Dieser, wie auch Formularkopf oder Formularfuß, sind tatsächlich eigene Fenster, die als Kindfenster des Hauptfensters fungieren. Zwar kann einer dieser Bereiche über die Section-Funktion des Formulars ermittelt werden (Form.Section (acDetail) etwa), doch dieses zurückgegebene Section-Objekt kennt kein Fenster-Handle. Wir kommen hier nicht umhin, API-Funktionen zu bemühen. Zum Glück ist der Code hierfür nicht sonderlich kompliziert. Listing 1 zeigt eine Hilfsroutine, die bei uns zum Einsatz kommt.

Private Const GW_CHILD As Long = 5
Private Const GW_HWNDNEXT As Long = 2 
Private Declare Function GetClassName Lib "user32.dll" Alias "GetClassNameA" (ByVal hwnd As Long, _
                                                ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindow Lib "user32.dll" (ByVal hwnd As Long, ByVal wCmd As Long) As Long 
Private Function GetDetailHwnd() As Long
     Dim hwnd As Long
     Dim ret As Long
     Dim rct As RECT
     Dim sClass As String
     
     hwnd = GetWindow(Me.hwnd, GW_CHILD)
     Do
         sClass = String(255, 0)
         ret = GetClassName(hwnd, sClass, 255)
         sClass = Left(sClass, ret)
        hwnd = GetWindow(hwnd, GW_HWNDNEXT)
     Loop Until sClass = "OFormSub"
     GetDetailHwnd = hwnd
End Function

Listing 1: Hilfsfunktion mit API zum Ermitteln des Fenster-Handles des Detailbereichs eines Formulars

Sie geht vom Handle des Hauptfensters (Me.hwnd) aus und befragt dann dessen Kindfenster mit der API-Funktion GetWindow. Dazu gehören allerdings nicht nur die Bereiche des Formulars, sondern etwa auch andere Fensterelemente, wie die Navigationsleiste. Die Bereiche lassen sich aber dadurch identifizieren, dass sie alle den Fensterklassennamen OFormSub tragen, den die Funktion GetClassName zurückgibt. Grundsätzlich sind sowohl Formularkopf wie -fuß vorhanden. Sind diese ausgeblendet, so existieren die Fenster zwar, haben jedoch die Höhe 0. Das zweite Kindfenster ist also immer der Detailbereich. Die Schleife identifiziert den Klassennamen und bricht ab, wenn der korrekte Name gefunden wurde. Da dann bereits das nächste Kindfenster über GetWindow mit GW_HWNDNEXT enumeriert wurde, steht in hwnd das richtige Handle, welches als Rückgabewert der Funktion dient.

Damit haben wir bereits alle nötigen Bestandteile beisammen, um das Formular mit eingebettetem Explorer zum Laufen zu bringen. Den Code des Beispielformulars mit dem kleinsten Aufwand (frmExplorerMinimal) demonstriert Listing 2. Die ganze Instanzierung des Explorer Controls findet im Ereignis Form_Load statt.

Private oExplBrowser As oleexp.ExplorerBrowser
Private lPIDL As Long, hwndDetail As Long
Private Sub Form_Load()
     Dim rct As oleexp.RECT
     Dim pfs As oleexp.FOLDERSETTINGS
     
     pfs.fFlags = FWF_ALIGNLEFT Or FWF_NOWEBVIEW
     pfs.ViewMode = FVM_DETAILS
     rct.Bottom = Me.Section(acDetail).Height \ 15
     rct.Right = Me.InsideWidth \ 15
     Set oExplBrowser = New oleexp.ExplorerBrowser
     oExplBrowser.Initialize GetDetailHwnd, rct, pfs
     oExplBrowser.SetOptions EBO_SHOWFRAMES
     lPIDL = SHGetSpecialFolderLocation(ByVal 0&, _
         CSIDL_DESKTOP)
     oExplBrowser.BrowseToIDList lPIDL, SBSP_ABSOLUTE
End Sub
Private Sub Form_Unload(Cancel As Integer)
     oExplBrowser.Destroy
     oleexp.CoTaskMemFree lPIDL
End Sub
(Zur <b>Hilfsfunktion GetDetailHwnd siehe Listing 1)

Listing 2: Der komplette Inhalt des Formulars frmExplorerMinimal

Erst werden die zwei Teile der FOLDERSETTINGS-Variablen pfs mit Vorgabewerten bestückt. Selbst das könnte im Prinzip entfallen, da beide dann eben den Wert 0 enthielten. Zwar gibt es eigentlich keine von Microsoft deklarierte ViewMode-Konstante, die diesem Wert entspricht, ein Fehler ereignet sich dabei aber nicht. Die Ansicht stellt sich bei Auskommentieren der beiden Zeilen einfach auf Große Symbole ein. Die Flags-Konstanten sind ohnehin alle optional.

Das Setzen der Abmessungen des Controls über die RECT-Variable rct allerdings ist zwingend erforderlich. Für Left und Right bleibt es bei 0, was das Control in der linken oberen Ecke des Detailbereichs anordnet. Die Höhe in Bottom ergibt sich aus der Höhe des Detailbereichs (Section acDetail), wobei der Wert durch Division mit 15 von Twips nach Pixel umgewandelt wird. Die Breite des Controls ergibt sich aus der des Formulars selbst (InsideWidth). Schließlich wird für die per New erzeugte Instanz oExplBrowser die Initialize-Methode aufgerufen und das Fenster-Handle des Detailbereichs über die Hilfsfunktion GetDetailHwnd (Listing 1) übergeben.

Nicht erwähnt haben wir bisher die Methode SetOptions des ExplorerBrowser-Objekts. Die hier anzugebenden Konstanten steuern das Gesamtlayout des Controls. Wichtig etwa ist das Setzen von EBO_SHOWFRAMES. Erst diese Zuweisung sagt dem Control, dass es den Explorer komplett nachbilden soll, und Treeview sowie Toolbar anzeigen soll. Unterbleibt das, so zeigt es lediglich die Dateiansicht an, wie in Bild 4. Für diese gibt es sicher ebenfalls Anwendungsfälle.

Ist EBO_SHOWFRAMES nicht gesetzt, so zeigt das Control nur den rechten Teil des Explorers

Bild 4: Ist EBO_SHOWFRAMES nicht gesetzt, so zeigt das Control nur den rechten Teil des Explorers

Unterschlagen haben wir auch, dass das Control erst wissen muss, welches Verzeichnis des Systems es überhaupt anzeigen soll. Dies übernimmt seine Methode BrowseToIDList. Lässt man sie weg, so zeigt das Control nur eine weiße Fläche.

Die Methode erwartet eine sogenannte ID-List. Das ist in der Shell-Welt im Grunde eine eindeutige ID eines Verzeichnisses oder einer Datei. Einen String für einen Verzeichnispfad kann man hier nicht angeben, sondern müsste diesen erst über eine Hilfsfunktion in eine ID umwandeln. Das erläutern wir später. Die andere hier verwendete Option ist das Ermitteln einer ID für einen der Standardordner von Windows über die API-Funktion SHGetSpecialFolderLocation. Ihr übergibt man die Konstante eines der Standardordner, wobei CLSID_DESKTOP den Desktop symbolisiert. Möchten Sie zu den Laufwerken navigieren, wie in Bild 4, so geben Sie stattdessen die Konstante CLSID_DRIVES an. Die möglichen Konstanten sehen Sie im Objektkatalog zur Bibliothek oleexp unter der Liste CSIDLs ein.

Die ermittelte ID speichert die formularglobale Variable lPIDL. Sie wird BrowseToIDList übergeben. Der zweite Parameter SBSP_ABSOLUTE sagt ihr zusätzlich, dass ein absoluter Sprung zum Ordner erfolgen soll. Möglich wäre auch ein relativer Sprung, den wir hier aber nicht erklären, weil dies zu deutlich höherem Aufwand führen würde.

Mehr gibt es nicht zu tun. Die gerade 40 Zeilen des Formularcodes zeigen, wie einfach ein Explorer anzulegen ist. Beim Schließen des Formulars (Form_Unload) muss allerdings der Explorer wieder abgebaut werden, damit es nicht zu Speicherlecks oder Abstürzen kommt. Die Destroy-Methode veranlasst dies. Außerdem sollte die erhaltene Ordner-ID in lPIDL noch freigegeben werden, was über die API-Funktion CoTaskMemFree geschieht. Wir haben damit nun einen funktionsfähigen Explorer im Formular. Wünschenswert wäre allerdings noch eine erweiterte Einflussnahme auf seine Gestalt und sein Verhalten, wie auch zusätzlich Interaktion mit dem Control und den angezeigten Ordnern und Dateien. Die aufgebohrte Version mit allerlei Zusatz-Features nennt sich in der Beispieldatenbank frmExplorerCtl.

Das Ultra-Komplettbeispiel

Bild 5 zeigt dieses Formular zur Laufzeit. Beschreiben wir kurz seine Funktionalität. Alle Bezeichnungen sind in Englisch gehalten, damit der Bezug zu den Methoden im VBA-Code etwas deutlicher wird.

Im Formular frmExplorerCtl können unten verschiedene Einstellungen des Explorer Controls zur Laufzeit vorgenommen werden

Bild 5: Im Formular frmExplorerCtl können unten verschiedene Einstellungen des Explorer Controls zur Laufzeit vorgenommen werden

Zunächst sind im Formular auch Kopf und Fuß angezeigt. Das Explorer Control befindet sich ausfüllend im Detailbereich. Im Formularkopf bewirkt ein Klick auf die Schaltfläche Reset to ““Computer““ den Sprung zu eben diesem Spezialordner, also der Laufwerksübersicht. Haben Sie sich im Explorer durch diverse Verzeichnisse gehangelt, so gelangen Sie über den Button Back zum vorigen Verzeichnis, mit Forward zum nächsten, so wie bei der Navigation im richtigen Explorer auch. Dir Up schaltet eine Verzeichnisebene aufwärts, falls möglich. Beim Desktop klappt das natürlich nicht. Die Textbox oben rechts zeigt das aktuelle Verzeichnis an. Dies entspricht der Adresszeile des Explorers. Eine Eingabe ist hier allerdings nicht möglich. Bei speziellen Elementen, wie denen der Systemsteuerung etwa, bleibt die Textbox leer.

Rechts unten gibt es eine mehrzeilige Textbox, die der Ausgabe von durch das Control ausgelösten Ereignissen dient wie auch der weiterer Informationen. In der Abbildung ist zu erkennen, dass etwa die Navigation zu Verzeichnissen abgefangen werden kann. Auch die Markierung von Dateien bleibt dem Formularcode nicht verborgen.

Im linken Bereich des Formularfußes können über zahlreiche Checkboxen und Optionsfelder Einstellungen für das Control vorgenommen werden. Diese beziehen sich allerdings alle auf die Dateiansicht im rechten Rahmen, der sogenannten Folder View. Dabei können Sie mit einem Klick auf die Schaltfläche Get die momentane Konfiguration der Folder View ermitteln. Die Checkboxen werden dann gemäß der Ansicht markiert. ändern Sie einzelne Markierungen, so wirken sich die neuen Einstellungen erst nach Klick auf den roten Button Apply view settings aus.

In der Mitte des Formularfußes schließlich können Sie über die vier blauen Schaltflächen verschiedene Aktionen auslösen, die lediglich als Beispiele die Steuerungsmöglichkeiten des Controls demonstrieren sollen. Edit 5th item etwa markiert das fünfte Element von oben in der Dateiansicht und bringt es in editierbaren Zustand, sodass Sie dessen Namen ändern können. Es spielt dabei keine Rolle, ob es sich um eine Datei oder ein Verzeichnis handelt.

Select items with ““P*““ markiert alle Elemente in der Ansicht, die mit dem Buchstaben P beginnen. Search for ““*.pdf““ stößt eine Suche nach allen Elementen Im Verzeichnis und seinen Unterverzeichnissen an, die PDF-Dokumente darstellen. Dabei bildet sich eine neue View mit den Suchergebnissen. Circle Items markiert visuell alle Elemente in der Ansicht der Reihe nach von oben nach unten mit einem Intervall von 80 Millisekunden. Sort By File Size DESC sortiert die Ansicht der Dateien nach deren Dateigröße absteigend, also nach der Spalte Größe.

Die Optionsfelder dienen der Einstellung des Ansichtsmodus. Report view etwa zeigt die Detailansicht, Small icons zeigt Kleine Symbole, Thumbnail view Miniaturabbilder. Diese Einstellung können Sie ja auch manuell über den Toolbar des Controls vornehmen. Bei allen Optionen für eine Symboldarstellung aktiviert sich zusätzlich rechts das Kombinationsfeld Icon Size. über dieses können Sie dann außerdem die quadratische Größe der Symbol-Icons bestimmen. Erlaubt sind hier Angaben von 16×16 bis 256×256.

Ganz in der Mitte befindet sich eine kleine Textbox mit der überschrift Sort String. Hier zeigt sich nach Klick auf den Get-Button die aktuelle Sortierung der View, wie das Control sie intern ausgibt. Sie beginnt immer mit dem Präfix prop:. Sind die Elemente nach Namen sortiert, so nennt sich die Sortierspalte System.ItemsNameDisplay. Sie können diesen String auch modifizieren! Geben Sie etwa System.ModifiedDate an, so sortiert sich die Ansicht nach dem änderungsdatum der Elemente, nachdem Sie auf den Apply-Button klicken. Eine absteigende Sortierung erreichen Sie übrigens, indem Sie nach prop: ein Minuszeichen einsetzen.

Zu erläutern wäre schließlich noch die Checkbox Allow file drop unten. Ist sie aktiviert, so können Sie von außerhalb des Formulars Dateien oder Verzeichnisse in die View ziehen und lösen damit den normalen Kopier-, Verschiebe- oder Verknüpfungsvorgang aus. Ist sie deaktiviert, so werden diese Aktionen unterbunden. Allerdings taucht dann trotzdem zur Information eine Messagebox auf, die den oder die Namen der gedragten Elemente ausgibt.

Trotz der zahlreichen Features des Formulars beschränkt sich sein Code auf circa 360 überschaubare Zeilen, von denen etliche Kommentare enthalten.

Was noch auf unserer ToDo-Liste steht gleich vorneweg: Es ist uns nicht gelungen, einzelne Bereiche des Controls ein- und auszuschalten. über SetOptions (s. Listing 2) und EBO_SHOWFRAMES kann bisher nur die Sichtbarkeit des Treeviews des Controls gesteuert werden. Den Toolbar ausschalten klappt indessen nicht. Zwar sieht Microsoft diese Möglichkeit im Prinzip vor, der dazu benötigte Umgang mit speziellen Interfaces und einer Callback-Funktion überfordert VBA – oder uns – jedoch etwas. Zusammengefasst kann mit dem Control in vielerlei Hinsicht über VBA interagiert werden. Schauen wir uns im Folgenden an, wie die Programmierung der einzelnen Features gelingt.

Verweise des VBA-Projekts

Für einige Aufgaben benötigen wir einen Bezug auf Klassenobjekte, die in der Bibliothek OLEEXP nicht vorhanden sind. Das sind vornehmlich Objekte der Windows Shell und deren Interfaces. Man findet sie aber alle in Komponenten von Windows. Alle hier aufgeführten Bibliotheken sind Bestandteil von Windows und brauchen deshalb nicht registriert zu werden. Bild 6 zeigt diese Libraries im Verweise-Dialog des Projekts.

Alle Verweise des Datenbank-VBA-Projekts stammen mit Ausnahme der Bibliothek OLEEXP aus gängigen Windows- und Office-Komponenten

Bild 6: Alle Verweise des Datenbank-VBA-Projekts stammen mit Ausnahme der Bibliothek OLEEXP aus gängigen Windows- und Office-Komponenten

Elementar ist die Bibliothek Microsoft Shell Controls And Automation, welche sich in der Systemdatei shell32.dll befindet. Sie enthält einige Klassen, die auch vom Browser Control verwendet werden. Der Verweis auf die Microsoft Forms 2.0 Object Library wäre an sich nicht erforderlich. Er lässt sich jedoch über den normalen Verweise-Dialog eigenartigerweise nicht mehr entfernen, nachdem er einmal gesetzt wurde. Den Verweis auf die OLEEXP-Bibliothek erläuterten wir ja bereits.

Browser Control resizen

Der Initialize-Methode des ExplorerBrowser-Objekts gibt man über den RECT-Parameter mit, wie groß die Ausdehnung des Controls im Formularfenster ausfallen soll. Man ist auf diese jedoch nicht festgelegt. Auch zur Laufzeit können Sie über die Methode SetRect eine andere Größe anweisen. Das ist praktisch, um das Control immer auf die Größe des Detailbereichs zu bringen, wenn das Formular verändert wird. Also setzen Sie in das Ereignis Bei Größenänderung des Formulars etwa den Code aus Listing 3.

Private Sub Form_Resize()
     Dim x As Long, y As Long
     Dim rct As RECT
     
     If oExplBrowser Is Nothing Then Exit Sub
     
     GetWindowRect GetDetailHwnd, rct
     y = rct.Bottom - rct.Top
     x = Me.InsideWidth \ 15
     oExplBrowser.SetRect 0, 0, 0, x, y
End Sub

Listing 3: Das Control macht hiermit Größenänderungen mit

Zur Sicherheit wird hier abgefragt, ob das Browser Control überhaupt erzeugt wurde. Dann ist der Inhalt der zuständigen Objektvariablen oExplBrowser nicht Nothing. Anschließend ermittelt die API-Funktion GetWindowRect abermals die Ausdehnung des Detailbereichs über dessen Fenster-Handle, welches Sie über die schon erwähnte Hilfsfunktion GetDetailHwnd bekommen. Die Höhe wird in der Variablen y gespeichert. Die Breite könnte eigentlich ebenfalls aus dem RECT-Typ rct ausgelesen werden; wir nehmen hier jedoch die Breite des Formulars selbst in InsideWidth. SetRect erwartet dann als ersten Parameter ein Pseudo-Handle, das wir unbeachtet lassen, und deshalb 0 zuweisen. Die nächsten beiden Parameter geben die linke obere Ecke des Controls im Detailbereich an, also dessen x- und y-Koordinaten. Auch hier verwenden wir 0. Dann folgen Breite und Höhe des Controls, die in den Variablen x und y gespeichert sind. Damit macht das Control alle Größenänderungen des Formulars mit, so als wäre es mit dem Verankern-Feature von Access ausgestattet.

Ereignisse abfangen

Wie Sie aus dem Objektkatalog (Bild 1) ablesen können, löst das ExplorerBrowser-Objekt selbst keine Ereignisse aus. Dafür ist nämlich ein separates Interface namens IExplorerBrowserEvents zuständig (Bild 7). Und Sie müssen dem Objekt erst mitteilen, wo sich dieses Interface befindet. Wie aber kann eine Instanz dieser Interface-Klasse angelegt werden

Die Klasse IExplorerBrowserEvents im VBA-Objektkatalog

Bild 7: Die Klasse IExplorerBrowserEvents im VBA-Objektkatalog

Hier kommt das selten genutzte Schlüsselwort Implements von VBA ins Spiel. In den Kopf des Moduls schreiben Sie das:

Implements IExplorerBrowserEvents

Beim Kompilieren weist VBA Sie nun per Fehler darauf hin, dass die Methoden dieser Schnittstelle im Modul eingebaut sein müssen. Sie suchen also in der linken oberen Combo des Modulfensters den Eintrag IExplorer-BrowserEvents heraus, worauf Sie in der rechten die einzelnen Methoden der Schnittstellen auswählen können. VBA legt dabei automatisch die zugehörigen Prozeduren an, ähnlich wie bei den Ereignissen einer Objektvariablen. Damit ist in der Formularklasse zusätzlich das Interface IExplorerBrowserEvents integriert. Es muss nur noch eine Verbindung zwischen Control und der Formularklasse hergestellt werden. Das übernimmt die Methode Advise des ExplorerBrowsers:

Implements IExplorerBrowserEvents
Private lEventCookie As Long
Private Sub Form_Load()
     ...
     oExplBrowser.Advise Me, lEventCookie
     ...
End Sub
Private Sub IExplorerBrowserEvents_OnNavigationPending _
     (ByVal pidlFolder As Long)
''''...
End Sub
Private Sub IExplorerBrowserEvents_OnNavigationComplete _
     (ByVal pidlFolder As Long)
Private Sub IExplorerBrowserEvents_OnNavigationFailed _
     (ByVal pidlFolder As Long)
Private Sub IExplorerBrowserEvents_OnViewCreated _
     (ByVal psv As oleexp.IShellView)

Der Methode Advise wird das Objekt übergeben, welches die Schnittstelle IExplorerBrowserEvents implementiert – hier das Formularobjekt durch Me -, sowie eine Variable, welche eine ID für die Verbindung entgegennimmt.

Sie wird noch benötigt und ist deshalb im Modulkopf als Variable lEventCookie vom Typ Long deklariert. Beim Entladen des Formulars sollte diese Verbindung wieder getrennt werden. Das wiederum geschieht über die Methode UnAdvise des Exlorer-Browsers:

olExplBrowser. UnAdvise lEventCookie

Es gibt also vier Ereignisse, die das Control selbst auslösen kann. OnNavigationPending passiert bei Anwahl eines Verzeichnisses im Treeview des Controls. Es besagt, dass die Navigation zum ihm noch aussteht. Der gewählte Ordner wird dabei durch den Parameter pidlFolder übergeben.

Er kann dann mithilfe einer API-Funktion in einen Verzeichnis-String umgewandelt werden:

Private Function GetPathFromPidl(pidl As Long) As String
     Dim pszPath As String
     pszPath = String(MAX_PATH, 0)
     If SHGetPathFromIDListW(pidl, StrPtr(pszPath)) Then
         If InStr(pszPath, vbNullChar) Then
             GetPathFromPidl = Left$(pszPath, InStr(pszPath, vbNullChar) - 1)
         End If
     End If
End Function

SHGetPathFromIDListW ist hier für die Umwandlung zuständig. Die API-Funktion ist im Kopf des Formularmoduls definiert. Im Beispiel sähe die Verwendung der Hilfsfunktion etwa so aus:

Private Sub IExplorerBrowserEvents_OnNavigationPending _
     (ByVal pidlFolder As Long)
     Debug.Print GetPathFromPidl( pidlFolder)
End Sub

Sobald Sie ein Verzeichnis im Control auswählen, wird dessen Pfad nun im VBA-Direktfenster ausgegeben. Im Beispielformular allerdings protokolliert die Prozedur AddToLog das Ereignis in der entsprechenden Textbox.

Das Control muss nun die Folder View, also die rechte Ansicht des Explorers, erzeugen. Ist das von Erfolg gekrönt, so tritt das Ereignis OnViewCreated auf den Plan. Als Parameter übergibt es eine Interface-Variable psv des Typs IShellView.

Diese Schnittstelle ist ebenfalls in der Bibliothek OLEEXP definiert. Man benötigt einen Verweis auf diese Schnittstelle, um später an die Elemente der Datei-und Ordnerliste heranzukommen. Deshalb wird in der Demo ein Verweis auf sie in der Formularvariablen IShView abgelegt:

Private IShView As oleexp.IShellView 
Private Sub IExplorerBrowserEvents_OnViewCreated _
                                                                            (ByVal psv As oleexp.IShellView)
     AddToLog "View created"
     Set IShView = psv
End Sub

Es kann sein, dass die Navigation zu einem Verzeichnis im Control fehlschlägt. Dies wäre etwa der Fall, wenn Sie eine ungültige ID-List als Parameter an die Methode BrowserToIDList übergeben. In diesem Fall ereignet sich OnNavigationFailed.

Ist der ganze Vorgang abgeschlossen, also das Verzeichnis im Treeview markiert und die Folder View dazu aufgebaut, so kommt es zum Ereignis NavigationComplete, auch hier wieder unter Angabe des angezeigten Ordners in pidlFolder.

über diese Ereignisse haben Sie also im Formularcode volle Kontrolle über die Verzeichnisse, zu denen im Control navigiert wird. Zugriff auf die Dateien und andere Elemente der Folder View besteht indessen noch nicht. Der ExplorerBrowser selbst enthält hierfür keinerlei Methoden. Die liefert erst die ShellView, welche über das Ereignis OnViewCreated zurückgeliefert und in der Variablen ShView zwischengespeichert wurde. Und hier wird es leider kompliziert, denn das Interface IShellView löst selbst wiederum keine Ereignisse aus. Es sind einige Tricks vonnöten, um aus der Schnittstelle ein neues Objekt abzuleiten, welches Ereignisse unterstützt.

Und das ist die Klasse ShellFolderView der Bibliothek Shell32, die wir in die Verweise des VBA-Projekts aufnahmen. Nehmen Sie sie im Objektkatalog unter die Lupe (Bild 8). Neben verschiedenen Properties oben, die vor allem das Layout der Elemente der View bestimmen, gibt es fünf Methoden zu ihrer Steuerung und fünf Ereignisse, die ausgelöst werden, wenn Sie Aktionen auf die Elemente ausführen. Das kann das Markieren von Dateien oder Verzeichnissen sein (SelectionChanged), das Ziehen von Elementen (BeginDrag) oder eine Aktion durch Doppelklicken beziehungsweise über das Kontextmenü eines Elements (VerbInvoked, DefaultVerbInvoked). EnumDone passiert dann, wenn alle Elemente eines Verzeichnisses vom Control ermittelt wurden, und hat damit lediglich informativen Charakter.

Die Klasse ShellFolderView der Shell32-Bibliothek zeigt ihre Methoden und Ereignisse im VBA-Objektkatalog

Bild 8: Die Klasse ShellFolderView der Shell32-Bibliothek zeigt ihre Methoden und Ereignisse im VBA-Objektkatalog

Am Interessantesten ist sicher das Ereignis SelectionChanged. ändern Sie etwa die Markierung oder Auswahl von Dateien im Control, so kommt es immer zu diesem Ereignis. Es übergibt allerdings keinen Parameter, aus dem sich etwas über die Art der Auswahl ableiten ließe. Dafür wäre dann die Methode SelectedItems zu bemühen, die eine Liste von FolderItems zurückgibt. Dazu später mehr.

Die Frage ist nun, wie man über die vom Control im Ereignis OnViewCreated erhaltene ShellView zu diesem Objekt ShellFolderView gelangt. Im Formularklassenmodul geschieht dies über die ausgelagerte Hilfsroutine ConnectToView. Ihr Aufruf wird direkt im entsprechenden Ereignis OnViewCreated vollzogen:

ConnectToView

Den Code der Routine finden Sie etwas gekürzt in Listing 4. Die Funktion GetItemObject der IShellView wird aufgerufen und dabei als Ergebnis durch das Flag SVGIO_BACKGROUND deren sogenanntes Dispatch-Interface abgefragt. Eben das ist die ShellFolderView, welche die Objektvariable oViewObj entgegennimmt. Sie ist im Modul oben deklariert:

Private Sub ConnectToView()
     DisconnectFromView
     
     IShView.GettemObject SVGIO_Flags.SVGIO_BACKGROUND, _
                                                     IIDDispatch, oViewObj
     If Not oViewObj Is Nothing Then
         If 0 = ConnectToConnectionPoint(Me, _
                               IID_DShellFolderViewEvents, 1&, _
                               oViewObj, lCookie, Nothing) Then
             Set oViewDispatch = oViewObj
         End If
     End If
End Sub
Private Sub DisconnectFromView()
     If Not oViewObj Is Nothing Then
         ConnectToConnectionPoint Nothing, _
                                     IID_DShellFolderViewEvents, _
                                     0&, oViewObj, lCookie, Nothing
         Set oViewDispatch = Nothing
         Set oViewObj = Nothing
         lCookie = 0&
     End If
End Sub

Listing 4: Erhalten des ShellFolderView-Objekts oViewObj

Private oViewObj As Shell32.ShellFolderView

Dieses Objekt löst leider noch keine Ereignisse aus, weil es ohne die Anweisung WithEvents deklariert ist. Fügten Sie diese ein, so passierte trotzdem nichts und die Ereignisse unterblieben. Grund dafür ist, dass die Verbindung zur Ereignisschnittstelle für die ShellView noch nicht hergestellt ist. Wir fanden aber eine geniale API-Funktion unter Windows, die diese Verbindung herstellen kann: ConnectToConnectionPoint. Ihr übergeben Sie die Formularklasse (Me) und die gewünschte Event-Schnittstelle DShellFolderViewEvents in Form einer GUID, welche in der Shell32-Bibliothek definiert ist. Auch hier ist, wie bei der Advise-Methode des Explorer-Browsers, ein Event-Cookie (lCookie) zu benutzen, welches später zum Kappen der Ereignisbindung benötigt wird. über diese API-Funktion ist nun die Verbindung zwischen ShellView und Formular erzwungen, weshalb auch Ereignisse an in ihm deklarierten Objektvariablen weitergeleitet werden. Das eigentliche Objekt, welches die Ereignisse auslöst, ist oViewDispatch, definiert im Modulkopf:

Private Withevents oViewDispatch As Shell32.ShellFolderView

Für dieses Objekt können Sie nun auf gewohnte Weise die fünf Ereignisprozeduren anlegen.

Es reicht allerdings nicht, die Verbindung zu dieser Schnittstelle nur einmal zuzuweisen, denn bei jeder neuen Verzeichniswahl im Control wird auch eine neue ShellView erzeugt. Deshalb auch der Aufruf der Routine aus dem Ereignis OnViewCreated. Bevor eine Neuzuweisung passiert, muss die gegebenenfalls bestehende alte Verbindung aber gelöst werden. Das übernimmt die zweite Prozedur DisconnectFromView, welche sich ebenfalls der API-Funktion ConnectToConnectionPoint bedient, nur dass hier als Zielobjekt nicht Me, sondern Nothing angegeben wird. Auch das zuvor erhaltene Event-Handle lCookie muss dabei angegeben werden.

über das nunmehr instanzierte ShellFolderView-Objekt haben Sie Zugriff auf alle Methoden in Bild 8. Zunächst interessiert Sie hier wohl das Ereignis SelectionChanged. Setzen Sie eine Ereignisroutine ein, wie in Listing 5. Die Funktion SelectedItems gibt eine Auflistung des Typs Shell32.FolderItems zurück, welche über eine For-Each-Schleife enumeriert werden kann. Die Elemente der Auflistung wiederum sind FolderItem-Objekte. Die Eigenschaft Path gibt Auskunft über den Pfad des Objekts. Dabei kann es sich wohlgemerkt sowohl um eine Datei wie auch um ein Verzeichnis handeln. Um was es geht, ermitteln Sie über die booleschen Eigenschaften IsFolder (Verzeichnis) oder IsLink (Verknüpfung) eines FolderItem-Objekts. Trifft beides nicht zu, so handelt es sich um eine Datei. Für Objekte wie die der Systemsteuerung gilt dies allerdings nicht. Hier wäre die Eigenschaft IsFileSystem zusätzlich abzufragen, welche bei virtuellen Elementen False zurückgibt.

Private Sub oViewDispatch_SelectionChanged()
     Dim oItm As Shell32.FolderItem
     Dim sObjs As String
     
     For Each oItm In oViewDispatch.SelectedItems
         sObjs = sObjs & vbCrLf & String(4, 32) & oItm.Path
     Next oItm
     AddToLog "Selected object(s): " & sObjs
End Sub

Listing 5: Enumerieren der in der ShellView markierten Elemente

Im Beispiel werden in der String-Variablen sObjs die Pfade der selektierten Elemente aneinandergehängt, um sie über die Hilfsroutine AddToLog in der Textbox des Formulars auszugeben.

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