{"id":55000420,"date":"2024-04-01T00:00:00","date_gmt":"2024-04-23T12:47:48","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=420"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"DateiauswahlDialogAssistent_programmieren","status":"publish","type":"post","link":"https:\/\/vbentwickler.de\/DateiauswahlDialogAssistent_programmieren\/","title":{"rendered":"Dateiauswahl-Dialog-Assistent programmieren"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg08.met.vgwort.de\/na\/962fc236c7a5413bbbc0ecdbbc96f624\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Dateidialoge ben&ouml;tigt man immer wieder. Ob man nun Dateien zum &Ouml;ffnen oder Bearbeiten ausw&auml;hlen m&ouml;chte, ob man einen Pfad zum Speichern einer Datei braucht oder ob man ein Verzeichnis selektieren will &#8211; am einfachsten geht das mit den praktischen Dateidialogen. Die Office-Bibliothek bietet sogar alle ben&ouml;tigten Varianten &uuml;ber die FileDialog-Klasse an. Dumm ist nur, dass man nicht st&auml;ndig Filedialoge programmiert, sondern nur alle paar Wochen, Monate oder sogar Jahre. Dann muss man sich immer wieder einarbeiten, um die verschiedenen Parameter &#8211; Titel, Schaltfl&auml;chenbeschriftungen, Standardverzeichnis und -dateiname, Filter, Dateiendungen und so weiter zu definieren. Wie sch&ouml;n w&auml;re es doch, wenn wir solche Dateidialoge mit einem kleinen Assistenten zusammenstellen k&ouml;nnten. Also machen wir uns ans Werk und schaffen einen solchen Wizard!<\/b><\/p>\n<h2>Wann, wie und wo nutzen wir den Dateidialog-Assistenten?<\/h2>\n<p>Die erste Frage, die sich stellt, ist: Wo und wie wollen wir diesen Assistenten aufrufen und was genau soll dieser eigentlich produzieren? Als Erstes f&auml;llt uns ein, dass er eine Funktion liefern soll, die den Dateidialog aufruft und uns die gew&uuml;nschte Datei oder das Verzeichnis zur&uuml;ckliefert.<\/p>\n<p>Wir k&ouml;nnten uns also darauf beschr&auml;nken, diesen Assistenten f&uuml;r das Zusammenstellen des ben&ouml;tigten Codes zu nutzen und diesen dann beispielsweise an der aktuellen Stelle im Codefenster des VBA-Editors einzuf&uuml;gen oder diesen in die Zwischenablage zu schreiben, damit wir diesen selbst einf&uuml;gen k&ouml;nnen.<\/p>\n<p>In vielen F&auml;llen mag das ausreichen: Dann ruft man den Dateidialog aus einer anderen Prozedur heraus auf und verwendet die gelieferten Informationen in den folgenden Codezeilen f&uuml;r den gew&uuml;nschten Zweck.<\/p>\n<p>In anderen F&auml;llen spielt die Benutzeroberfl&auml;che eine Rolle: Dann m&ouml;chte man vielleicht in einem Access-Formular ein Verzeichnis etwa zum Exportieren von Daten anzeigen und ausw&auml;hlen. Dazu f&uuml;gt man diesem ein Textfeld oder ein Bezeichnungsfeld zur Anzeige des Pfades hinzu und legt daneben eine Schaltfl&auml;che an, mit der man den Dateiauswahl-Dialog aufruft.<\/p>\n<p>Letztlich w&uuml;rde auch hier erst einmal der Assistent zum Zusammenstellen von Funktionen zum Anzeigen von Dateidialogen ausreichen.<\/p>\n<p>Man k&ouml;nnte die ben&ouml;tigten Steuerelemente und die Programmlogik zum Aufruf der Funktionen dann selbst hinzuf&uuml;gen &#8211; immerhin ist das der wesentlich weniger aufwendige Teil dieser Aufgabe.<\/p>\n<p>Wir werden uns also zun&auml;chst darauf beschr&auml;nken, den Code f&uuml;r die Anzeige von Dateidialogen abh&auml;ngig von den gew&uuml;nschten Parametern zu produzieren.<\/p>\n<p>Wo wollen wir diese Funktionalit&auml;t bereitstellen? Sinnvoll ist ein COM-Add-In f&uuml;r den VBA-Editor, denn dort werden wir den zu erstellenden Code auch nutzen.<\/p>\n<p>Wo dort wollen wir die Funktion aufrufen? Hier gibt es drei m&ouml;gliche Stellen:<\/p>\n<ul>\n<li>einen Men&uuml;eintrag, beispielsweise unterhalb des Eintrags <b>Add-Ins <\/b>oder in einem eigenen Hauptmen&uuml;<\/li>\n<li>ein Eintrag in der Symbolleiste<\/li>\n<li>ein Eintrag im Kontextmen&uuml;, das erscheint, wenn wir mit der rechten Maustaste in das Codefenster klicken<\/li>\n<\/ul>\n<h2>Werkzeug f&uuml;r das Erstellen des COM-Add-Ins<\/h2>\n<p>Die Entwicklungsumgebung und Programmiersprache twinBASIC hat sich als zuverl&auml;ssiges Tool f&uuml;r die Programmierung von COM-Add-Ins und COM-DLLs f&uuml;r Office-Anwendungen und den VBA-Editor erwiesen, sodass wir diesen auch in diesem Artikel f&uuml;r die Erstellung des gew&uuml;nschten Assistenten nutzen werden.<\/p>\n<h2>COM-Add-In bis zur eigentlichen Funktion<\/h2>\n<p>Im Artikel <b>twinBASIC: COM-Add-In f&uuml;r den VBA-Editor <\/b>(<b>www.access-im-unternehmen.de\/421<\/b>) stellen wir eine Vorlage f&uuml;r die Programmierung von COM-Add-Ins f&uuml;r den VBA-Editor vor &#8211; und erl&auml;utern, welche Schritte n&ouml;tig sind, um diese Vorlage in ein eigenes, neues COM-Add-In umzuwandeln.<\/p>\n<p>Genau das haben wir gemacht und so ein neues, fast leeres COM-Add-In erzeugt. Die einzigen Elemente, die dieses COM-Add-In enth&auml;lt, dienen zur Anzeige von Men&uuml;eintr&auml;gen in einem neuen Men&uuml;punkt im Hauptmen&uuml; des VBA-Editors sowie eines Eintrags eines Kontextmen&uuml;s, das erscheint, wenn wir mit der rechten Maustaste in das Codefenster klicken.<\/p>\n<h2>Informationen zum Erstellen des Codes ermitteln<\/h2>\n<p>Bevor wir starten, stellen wir eine Liste der Informationen zusammen, die wir vor dem Erstellen des Codes f&uuml;r die Anzeige eines Dateiauswahl-Dialogs vom Entwickler ermitteln m&uuml;ssen.<\/p>\n<p>Was ben&ouml;tigen wir also alles?<\/p>\n<ul>\n<li>Um was f&uuml;r einen Dateidialog soll es sich handeln? Es gibt Dialoge f&uuml;r die Auswahl von bestehenden Dateien, f&uuml;r zu erstellende Dateien und f&uuml;r Verzeichnisse. Dies k&ouml;nnen wir mit einer Optionsgruppe oder einem Kombinationsfeld abfragen. Au&szlig;erdem gibt es noch den Typ zum direkten &Ouml;ffnen von Dateien, der in Word oder Excel zum Einsatz kommen kann.<\/li>\n<li>Einfache oder Mehrfachauswahl. Soll der Benutzer keine, eine oder sogar mehrere Dateien ausw&auml;hlen k&ouml;nnen? Wenn er mehrere Dateien ausw&auml;hlen k&ouml;nnen soll, m&uuml;ssen wir uns &uuml;berlegen, wie wir diese Dateien &uuml;bergeben.<\/li>\n<li>Beschriftung des Dialogtitels<\/li>\n<li>Beschriftung der Schaltfl&auml;che zum Ausw&auml;hlen<\/li>\n<li>Ordner, der beim &Ouml;ffnen des Dialogs angezeigt werden soll, einstellen<\/li>\n<li>Datei, die beim &Ouml;ffnen ausgew&auml;hlt oder angegeben werden soll<\/li>\n<li>Leeren vorhandener Filter, Erstellen von Filtern und Auswahl eines der Filter als Standard<\/li>\n<\/ul>\n<p>Auf all diese Elemente gehen wir in den folgenden Abschnitten ein.<\/p>\n<h2>Der FileDialog-Wizard in Aktion<\/h2>\n<p>Den Wizard wollen wir auf zwei Arten aufrufen k&ouml;nnen. Die erste ist der Men&uuml;eintrag <b>AMV-Tools|amvFileDialogWizard <\/b>(siehe Bild 1).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_003.png\" alt=\"Aufruf per Men&uuml;eintrag\" width=\"624.6265\" height=\"183.928\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Aufruf per Men&uuml;eintrag<\/span><\/b><\/p>\n<p>Die zweite und bevorzugte M&ouml;glichkeit ist der Aufruf &uuml;ber das Kontextmen&uuml; des Codefensters (siehe Bild 2).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_004.png\" alt=\"Aufruf per Kontextmen&uuml;\" width=\"624.6265\" height=\"410.513\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Aufruf per Kontextmen&uuml;<\/span><\/b><\/p>\n<p>In Bild 3 zeigen wir, wie der fertige Wizard aussehen soll. Hier sehen wir Eingabefelder f&uuml;r den Titel, den Funktionsnamen, den Buttontext und den beim &Ouml;ffnen anzuzeigenden Pfad. F&uuml;r Dialogtyp haben wir ein Kombinationsfeld hinterlegt.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_002.png\" alt=\"Der FileDialog-Wizard in Aktion\" width=\"624.6265\" height=\"475.0156\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Der FileDialog-Wizard in Aktion<\/span><\/b><\/p>\n<p>Ob der Dialog die Mehrfachauswahl unterst&uuml;tzen soll, legen wir mit einer CheckBox fest. Schlie&szlig;lich kann der Benutzer die Beschreibung und die Dateiendung f&uuml;r Filter &uuml;ber zwei Textfelder eingeben, die mit der Plus-Schaltfl&auml;che zur Liste der anzuzeigenden Filter hinzugef&uuml;gt werden. Mit der Minus-Schaltfl&auml;che kann der Benutzer den markierten Filtereintrag wieder entfernen.<\/p>\n<p>Jede &Auml;nderung, die der Benutzer an einem der Steuerelemente vornimmt, wirkt sich direkt auf den angezeigten Code aus. Dadurch sieht der Benutzer jederzeit, welcher Code eingef&uuml;gt wird, wenn er auf die <b>OK<\/b>-Schaltfl&auml;che klickt.<\/p>\n<h2>Programmierung des COM-Add-Ins zur Bereitstellung des FileDialog-Wizards<\/h2>\n<p>Diesen Wizard programmieren wir in den folgenden Abschnitten.<\/p>\n<h2>Neues Projekt erstellen<\/h2>\n<p>Als Erstes erstellen wir ein neues Projekt auf Basis der Vorlage, die wir im Artikel <b>twinBASIC: COM-Add-In f&uuml;r den VBA-Editor <\/b>(<b>www.vbentwickler.de\/421<\/b>) beschrieben haben. In diesem Artikel haben wir auch gezeigt, an welchen Stellen &Auml;nderungen vorgenommen werden m&uuml;ssen, wenn wir ein neues COM-Add-In auf der Basis der Vorlage erstellen wollen. Der erste Schritt ist, dass wir die Hauptklasse in <b>FileDialogWizard <\/b>umbenennen und das auch f&uuml;r einige weitere Elemente im Projekt erledigen.<\/p>\n<p>Viele der Elemente k&ouml;nnen wir zun&auml;chst beibehalten. Dazu geh&ouml;rt auch die Prozedur <b>CreateToolbars<\/b>. Hier haben wir lediglich die beiden Konstanten ge&auml;ndert, welche f&uuml;r die Anzeige des Men&uuml;- und des Kontextmen&uuml;eintrags verantwortlich sind. Diese lauten nun wie folgt:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>Const cStrMenu<span style=\"color:blue;\"> As String<\/span> = \"AMV-Tools\"\r\n<span style=\"color:blue;\">Private <\/span>Const cStrCommandButton<span style=\"color:blue;\"> As String<\/span> = \"amvFileDialogWizard\"<\/pre>\n<p>F&uuml;r die beiden Ereignisprozeduren, die durch die beiden Schaltfl&auml;chen im Men&uuml; und im Kontextmen&uuml; ausgel&ouml;st werden, haben wir jeweils den Aufruf der Prozedur <b>LaunchFileDialogWizard <\/b>eingetragen:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cbbEvents_Click(...) Handles _\r\n         cbbEvents.Click\r\n     LaunchFileDialogWizard\r\n     ...\r\n<span style=\"color:blue;\">End Sub<\/span>\r\n<span style=\"color:blue;\">Private Sub <\/span>cbbEventsContext_Click(...) _\r\n         Handles cbbEventsContext.Click\r\n     LaunchFileDialogWizard\r\n     ...\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Damit sind die Arbeiten am Modul <b>FileDialogWizard <\/b>bereits erledigt.<\/p>\n<h2>Die Methode LaunchFileDialogWizard<\/h2>\n<p>Die durch die Men&uuml;befehle aufgerufene Methode <b>LaunchFileDialogWizard <\/b>finden wir in Listing 1. Sie ist daf&uuml;r verantwortlich, das Fenster unseres Assistenten anzuzeigen und den Code anzuhalten, bis dieses Fenster entweder geschlossen oder ausgeblendet wird. Das Fenster initialisieren wir auf Basis des <b>Form<\/b>-Objekts <b>frmFileDialogWizard<\/b>, das wir weiter unten erl&auml;utern. An dieser Stelle ist erst einmal wichtig, wie wir es aufrufen, wie wir reagieren, wenn es den Fokus verliert und was dann geschieht. Bevor wir es anzeigen, speichern wir sein Handle in der Variablen <b>lngHwnd<\/b>. Dann &ouml;ffnen wir es mit der <b>Show<\/b>-Methode und &uuml;bergeben dieser den Wert <b>vbModal <\/b>als Parameter, damit der aufrufende Code angehalten wird, bis der Benutzer die Arbeit mit dem Fenster abschlie&szlig;t.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>LaunchFileDialogWizard()\r\n     <span style=\"color:blue;\">Dim <\/span>frm<span style=\"color:blue;\"> As <\/span>frmFileDialogWizard\r\n     <span style=\"color:blue;\">Dim <\/span>strCode<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngStartLine<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngStartColumn<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngEndLine<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngEndColumn<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngCurrentLine<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objCodepane<span style=\"color:blue;\"> As <\/span>CodePane\r\n     <span style=\"color:blue;\">Dim <\/span>objCodemodule<span style=\"color:blue;\"> As <\/span>CodeModule\r\n     <span style=\"color:blue;\">Dim <\/span>lngHwnd<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> frm = <span style=\"color:blue;\">New<\/span> frmFileDialogWizard\r\n     lngHwnd = frm.hWnd\r\n     frm.Show vbModal\r\n     <span style=\"color:blue;\">If <\/span>IsWindow(lngHwnd) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         <span style=\"color:blue;\">Exit Sub<\/span>\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         strCode = frm.txtCode\r\n         UnloadObjectByHandle(lngHwnd)\r\n         <span style=\"color:blue;\">Set<\/span> objCodepane = objVBE.ActiveCodePane\r\n         <span style=\"color:blue;\">Set<\/span> objCodemodule = objCodepane.CodeModule\r\n         objCodepane.GetSelection(lngStartLine, lngStartColumn, _\r\n             lngEndLine, lngEndColumn)\r\n         objCodemodule.InsertLines(lngStartLine, strCode)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: Die Prozedur LaunchFileDialogWizard<\/span><\/b><\/p>\n<p>Dann pr&uuml;fen wir mit der Funktion <b>IsWindow<\/b>, ob das Fenster noch ge&ouml;ffnet ist oder nicht. Im ersten Fall hat der Benutzer das Fenster mit der <b>Abbrechen<\/b>-Schaltfl&auml;che oder der <b>Schlie&szlig;en<\/b>-Schaltfl&auml;che geschlossen. Dann gibt es nichts weiter zu tun, als die Prozedur zu beenden.<\/p>\n<p>Anderenfalls lesen wir den im Textfeld <b>txtCode <\/b>enthaltenen Code in die Variable <b>strCode <\/b>ein und schlie&szlig;en das Formular mit der Funktion <b>UnloadObjectByHandle<\/b>, wobei wir das Handle des Fensters &uuml;bergeben.<\/p>\n<p>Danach referenzieren wir das aktuelle <b>Codepane<\/b>&#8211; und dann das <b>CodeModule<\/b>-Objekt. &Uuml;ber die Methode <b>GetSelection <\/b>des <b>CodePane<\/b>-Elements lesen wir die aktuelle Position der Markierung aus. Hier wollen wir anschlie&szlig;end den im Wizard zusammengestellten Code einf&uuml;gen. Um die Markierung zu speichern, &uuml;bergeben wir dieser Methode die Parameter <b>lngStartLine<\/b>, <b>lngStartColumn<\/b>, <b>lngEndLine <\/b>und <b>lngEndColumn<\/b>, die von dieser Methode gef&uuml;llt werden sollen.<\/p>\n<p>Wir ben&ouml;tigen davon nur den Parameter <b>lngStartLine<\/b>, denn in dieser Zeile wollen wir den Code mit der <b>InsertLines<\/b>-Methode einf&uuml;gen.<\/p>\n<p>Das war einfach bisher &#8211; aber nun kommen wir zur eigentlichen Funktion.<\/p>\n<h2>Klasse zum Erstellen des FileDialog-Codes<\/h2>\n<p>Die Klasse <b>clsFileDialogWizard<\/b> ist das Arbeitstier des COM-Add-Ins. Die Klasse nimmt alle notwendigen Informationen entgegen und erstellt den Code der Funktion zum Anzeigen des Dateidialogs. Dazu deklarieren wir in der Klasse zun&auml;chst die folgenden lokalen Variablen:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>m_Title<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_Type<span style=\"color:blue;\"> As <\/span>MsoFileDialogType\r\n<span style=\"color:blue;\">Private <\/span>m_FunctionName<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_AllowMultiSelect<span style=\"color:blue;\"> As Boolean<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_Separator<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_ButtonName<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_InitialFileName<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Private <\/span>m_Filter<span style=\"color:blue;\"> As String<\/span><\/pre>\n<p>Diese f&uuml;llen wir &uuml;ber entsprechende <b>Property Let<\/b>-Prozeduren, die nach au&szlig;en als Eigenschaften pr&auml;sentiert werden. F&uuml;r die meisten Eigenschaften ist das recht einfach &#8211; sie nehmen einfach den Wert entgegen und speichern diesen in der jeweiligen Variablen:<\/p>\n<pre><span style=\"color:blue;\">Public Property Let <\/span>Title(str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_Title = str\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>Type(lng<span style=\"color:blue;\"> As <\/span>MsoFileDialogType)\r\n     m_Type = lng\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>FunctionName(str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_FunctionName = str\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>AllowMultiSelect(bol<span style=\"color:blue;\"> As Boolean<\/span>)\r\n     m_AllowMultiSelect = bol\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>Separator(str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_Separator = str\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>ButtonName(str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_ButtonName = str\r\n<span style=\"color:blue;\">End Property<\/span>\r\n<span style=\"color:blue;\">Public Property Let <\/span>InitialFilename(str<span style=\"color:blue;\"> As String<\/span>)\r\n     m_InitialFileName = str\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Nur f&uuml;r den Filter ist es etwas komplizierter. Hierf&uuml;r definieren wir die <b>Property Let<\/b>-Prozedur aus Listing 2. Sie nimmt eine Variable des Typs <b>Variant <\/b>entgegen. Der Inhalt ist ein Array mit zwei Dimensionen, wobei die Zeilen jeweils die Beschreibung und die Dateiendung f&uuml;r den Filter enthalten.<\/p>\n<pre><span style=\"color:blue;\">Public Property Let <\/span>Filter(var<span style=\"color:blue;\"> As Variant<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>strTemp<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">If <\/span>IsEmpty(var)<span style=\"color:blue;\"> Then<\/span>\r\n         m_Filter = \"\"\r\n         <span style=\"color:blue;\">Exit Property<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     strTemp = \"        .Filters.Clear\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     For i = <span style=\"color:blue;\">LBound<\/span>(var) To <span style=\"color:blue;\">UBound<\/span>(var)\r\n         strTemp += \"        .Filters.Add \"\"\" & var(i, 1) _\r\n             & \"\"\", \"\"\" & var(i, 2) & \"\"\"\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">Next<\/span>\r\n     m_Filter = strTemp\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Die Prozedur Filter<\/span><\/b><\/p>\n<p>Die Prozedur stellt die Anweisungen zum Einstellen des Filters zusammen und speichert diese in der Variablen <b>m_Filter<\/b>. Dabei schreibt sie in die erste Zeile die Anweisung zum Leeren des Filters (<b>.Filters.Clear<\/b>).<\/p>\n<p>Danach durchl&auml;uft sie die Zeilen des Arrays in einer <b>For&#8230;Next<\/b>-Schleife und f&uuml;gt f&uuml;r jede Zeile einen Eintrag wie den folgenden zum Filterausdruck hinzu &#8211; zum Beispiel:<\/p>\n<pre>.Filters.Clear\r\n.Filters.Add \"Alle Dateitypen\", \"*.*\"<\/pre>\n<h2>Zusammenstellen der FileDialog-Funktion<\/h2>\n<p>Die Hauptarbeit in der Klasse &uuml;bernimmt die Methode <b>CreateFileDialogCode<\/b>, die wir in Listing 3 zeigen. Sie deklariert eine Variable namens <b>strCode<\/b>, in der sie den kompletten Code zusammentr&auml;gt. Den Start macht die Kopfzeile der Prozedur, die aus <b>Public Function <\/b>und dem Namen der Funktion aus <b>m_FunktionName<\/b> besteht. Jede Zeile wird mit einem Zeilenumbruch abgeschlossen (<b>vbCrLf<\/b>).<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>CreateFileDialogCode()<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strCode<span style=\"color:blue;\"> As String<\/span>\r\n     strCode = \"Public Function \" & m_FunctionName & \"()\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    Dim objFileDialog<span style=\"color:blue;\"> As <\/span>Office.FileDialog\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    Dim strTemp<span style=\"color:blue;\"> As String<\/span>\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    <span style=\"color:blue;\">Set<\/span> objFileDialog = Application.FileDialog(\" & GetTypeConstant(m_Type) & \")\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    <span style=\"color:blue;\">With<\/span> objFileDialog\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(m_Title) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strCode += \"        .Title = \"\"\" & m_Title & \"\"\"\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(m_ButtonName) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strCode += \"        .ButtonName = \"\"\" & m_ButtonName & \"\"\"\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span>m_AllowMultiSelect = <span style=\"color:blue;\">True<\/span><span style=\"color:blue;\"> Then<\/span>\r\n         strCode += \"        .AllowMultiSelect = True\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(m_InitialFileName) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strCode += \"        .InitialFilename = \"\"\" & m_InitialFileName & \"\"\"\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     strCode += m_Filter\r\n     strCode += \"        If .Show = <span style=\"color:blue;\">True<\/span> Then\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">If <\/span>m_AllowMultiSelect<span style=\"color:blue;\"> Then<\/span>\r\n         strCode += \"            For Each varFilename In .SelectedItems \" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strCode += \"                strTemp = strTemp & \"\"\" & m_Separator & \"\"\" & varFilename\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strCode += \"            <span style=\"color:blue;\">Next<\/span> varFilename\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strCode += \"            If <span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(strTemp) = 0 Then \" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strCode += \"                strTemp = <span style=\"color:blue;\">Mid<\/span>(strTemp, 2)\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n         strCode += \"            End If\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">Else<\/span>\r\n         strCode += \"            strTemp = .SelectedItems(1) \" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     strCode += \"        End If\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    End With\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"    \" & m_FunctionName & \" = strTemp\" & <span style=\"color:blue;\">vbCrLf<\/span>\r\n     strCode += \"End Function\"\r\n     Return strCode\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Die Prozedur CreateFileDialog<\/span><\/b><\/p>\n<p>Die folgenden Zeilen f&uuml;gen die Deklaration der Variablen f&uuml;r das <b>FileDialog<\/b>-Objekt und einer tempor&auml;ren <b>String<\/b>-Variablen hinzu, bevor die Zeile zum Initialisieren des <b>FileDialog<\/b>-Objekts eingef&uuml;gt wird. In dieser haben wir einen weiteren variablen Anteil, n&auml;mlich den Typ des <b>FileDialog<\/b>-Elements. Dieser kann mit der Eigenschaft <b>Type <\/b>&uuml;bergeben werden und landet in <b>m_Type<\/b>.<\/p>\n<p>Um diese auszuwerten, verwenden wir eine Hilfsfunktion namens <b>GetTypeConstant<\/b>, der wir den <b>MsoFileDialogType <\/b>&uuml;bergeben und die eine entsprechende Zeichenkette zur&uuml;ckliefert:<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>GetTypeConstant( _\r\n         lngType<span style=\"color:blue;\"> As <\/span>MsoFileDialogType)<span style=\"color:blue;\"> As String<\/span>\r\n     Select Case lngType\r\n         <span style=\"color:blue;\">Case <\/span>MsoFileDialogType.msoFileDialogFilePicker\r\n             Return \"msoFileDialogFilePicker\"\r\n         <span style=\"color:blue;\">Case <\/span>MsoFileDialogType.msoFileDialogFolderPicker\r\n             Return \"msoFileDialogFolderPicker\"\r\n         <span style=\"color:blue;\">Case <\/span>MsoFileDialogType.msoFileDialogOpen\r\n             Return \"msoFileDialogOpen\"\r\n         <span style=\"color:blue;\">Case <\/span>MsoFileDialogType.msoFileDialogSaveAs\r\n             Return \"msoFileDialogSaveAs\"\r\n     <span style=\"color:blue;\">End Select<\/span>\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p>Danach folgt die Zuweisung der eigentlichen Eigenschaften. Wenn <b>m_Title <\/b>einen Wert enth&auml;lt, wird die Zeile <b>.Title = [Titel] <\/b>hinzuf&uuml;gt (der Teil <b>[Titel] <\/b>ist als Platzhalter zu verstehen).  Ist ein Wert f&uuml;r <b>ButtonName<\/b> &uuml;bergeben worden, resultiert dies in der Anweisung <b>.ButtonName = [ButtonName]<\/b>. Hat <b>m_AllowMultiSelect <\/b>den Wert <b>True<\/b>, wird dies mit <b>.AllowMultiSelect = True <\/b>vermerkt. Diese Einstellung wird gleich nochmals ber&uuml;cksichtigt. Wurde ein initial anzuzeigender Pfad angegeben, landet dieser in der Zeile <b>.InitialFilename = [Pfad]<\/b>. Anschlie&szlig;end wird der zuvor bereits zusammengestellte und in <b>m_Filter <\/b>gespeicherte Teil mit den Anweisungen f&uuml;r den Filter hinzugef&uuml;gt.<\/p>\n<p>Danach kommen wir zum zweiten Teil, der durch die Eigenschaft <b>AllowMultiSelect <\/b>beeinflusst wird. Diese Option legt fest, dass nicht nur eine, sondern gegebenenfalls auch mehrere Dateien ausgew&auml;hlt werden k&ouml;nnen. Dem m&uuml;ssen wir bei der Auswertung des Dateiauswahl-Dialogs Rechnung tragen &#8211; und somit auch in der Prozedur, die den Code f&uuml;r diese Funktion zusammenstellt.<\/p>\n<p>In diesem Fall f&uuml;gen wir, falls <b>m_AllowMultiSelect <\/b>den Wert True hat, eine <b>For Each<\/b>-Schleife hinzu, die alle Eintr&auml;ge aus <b>SelectedItems <\/b>durchl&auml;uft und sie in einer durch ein Trennzeichen separierten Liste zusammenstellt. Dabei f&uuml;gen wir das Trennzeichen f&uuml;r jeden Eintrag voran und entfernen diesen f&uuml;r das erste Element anschlie&szlig;end wieder.<\/p>\n<p>Wenn <b>m_AllowMultiSelect <\/b>den Wert <b>False <\/b>hat, f&uuml;gen wir einfach die Anweisung <b>strTemp = SelectedItems(1) <\/b>hinzu. Schlie&szlig;lich schlie&szlig;en wir die noch offenen <b>If<\/b>&#8211; und <b>With<\/b>-Konstrukte und f&uuml;gen die letzte Anweisung hinzu, welche das Ergebnis aus <b>strTemp <\/b>dem Namen Funktion zuweist. Das Ergebnis wird durch die <b>Return<\/b>-Anweisung an die aufrufende Funktion zur&uuml;ckgegeben.<\/p>\n<h2>Erstellen des Form-Elements<\/h2>\n<p>Damit kommen wir zur grafischen Benutzeroberfl&auml;che des COM-Add-Ins. Diese besteht aus einem <b>Form<\/b>-Element, das wir &uuml;ber den Men&uuml;eintrag <b>Project|Add|Add Form <\/b>hinzuf&uuml;gen. Dem <b>Form<\/b>-Element f&uuml;gen wir verschieden Steuerelemente hinzu (siehe Bild 4):<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_005.png\" alt=\"Anlegen eines Form-Elements in twinBASIC\" width=\"649.627\" height=\"438.3024\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Anlegen eines Form-Elements in twinBASIC<\/span><\/b><\/p>\n<ul>\n<li>Textfeld <b>txtTitle<\/b><\/li>\n<li>Textfeld <b>txtFunctionname<\/b><\/li>\n<li>ComboBox <b>cboType<\/b><\/li>\n<li>Textfeld <b>txtButtonName<\/b><\/li>\n<li>Kontrollk&auml;stchen <b>chkAllowMultiSelect<\/b><\/li>\n<li>Textfeld <b>txtInitialFilename<\/b><\/li>\n<li>Textfeld <b>txtDescription<\/b><\/li>\n<li>Textfeld <b>txtFileExtension<\/b><\/li>\n<li>ListView <b>lvwFilters<\/b><\/li>\n<li>Textbox <b>txtCode<\/b><\/li>\n<li>Schaltfl&auml;che <b>cmdOK<\/b><\/li>\n<li>Schaltfl&auml;che <b>cmdAbbrechen<\/b><\/li>\n<\/ul>\n<p>Das Klassenmodul zum <b>Form<\/b>-Element enth&auml;lt zun&auml;chst die Deklaration einer Objektvariablen auf Basis der oben vorgestellten Klasse <b>clsFileDialogWizard<\/b>. Danach folgt die Konstruktor-Methode <b>New <\/b>der <b>Form<\/b>-Klasse (siehe Listing 4). Diese deklariert zun&auml;chst einige Variablen, bevor sie eine neue Instanz der Klasse <b>clsFileDialogWizard <\/b>erstellt.<\/p>\n<pre><span style=\"color:blue;\">Sub <\/span>New()\r\n     <span style=\"color:blue;\">Dim <\/span>strFilter()<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFilterTemp<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFiltersTemp()<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strBeschreibung<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strDateiendung<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>itm<span style=\"color:blue;\"> As <\/span>ListItem\r\n     <span style=\"color:blue;\">Set<\/span> objFileDialogWizard = <span style=\"color:blue;\">New<\/span> clsFileDialogWizard\r\n     txtButtonName = GetAppSetting(\"ButtonName\", \"\")\r\n     txtFunctionName = GetAppSetting(\"FunctionName\", \"OpenFileDialog\")\r\n     txtInitialFilename = GetAppSetting(\"InitialFilename\")\r\n     txtTitle = GetAppSetting(\"Title\", \"\")\r\n     chkAllowMultiSelect = GetAppSetting(\"AllowMultiSelect\", 0)\r\n     <span style=\"color:blue;\">With<\/span> lvwFilters\r\n         .View = lvwReport\r\n         .ColumnHeaders.Add , , \"Beschreibung:\", lvwFilters.Width * 6 \/ 10\r\n         .ColumnHeaders.Add , , \"Filter:\", lvwFilters.Width * 4 \/ 10\r\n         .MultiSelect = <span style=\"color:blue;\">True<\/span>\r\n         .FullRowSelect = <span style=\"color:blue;\">True<\/span>\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     strFilterTemp = GetAppSetting(\"Filter\")\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(strFilterTemp) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strFiltersTemp = <span style=\"color:blue;\">Split<\/span>(strFilterTemp, \"|\")\r\n         For i = <span style=\"color:blue;\">LBound<\/span>(strFiltersTemp) To <span style=\"color:blue;\">UBound<\/span>(strFiltersTemp)\r\n             strBeschreibung = <span style=\"color:blue;\">Split<\/span>(strFiltersTemp(i), \"~\")(0)\r\n             strDateiendung = <span style=\"color:blue;\">Split<\/span>(strFiltersTemp(i), \"~\")(1)\r\n             <span style=\"color:blue;\">Set<\/span> itm = lvwFilters.ListItems.Add(, , strBeschreibung)\r\n             itm.SubItems(1) = strDateiendung\r\n         <span style=\"color:blue;\">Next<\/span>\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">With<\/span> cboType\r\n         .AddItem (\"FileDialogFilePicker\") ''4\r\n         .AddItem (\"FileDialogFolderPicker\") ''3\r\n         .AddItem (\"FileDialogOpen\") ''1\r\n         .AddItem (\"FileDialogSaveAs\") ''2\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     cboType = GetAppSetting(\"Type\", \"FileDialogFilePicker\")\r\n     txtDescription = \"Alle Dateitypen\"\r\n     txtFileExtension = \"*.*\"\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Die Prozedur New, die beim &Ouml;ffnen des Formulars ausgel&ouml;st wird<\/span><\/b><\/p>\n<p>Dann f&uuml;llt sie die Steuerelemente mit den durch den Benutzer vorzugebenden Werten aufgrund von in der Registry gespeicherten Werten von eventuellen vorherigen Aufrufen. <\/p>\n<p>Diese speichern wir in einem speziellen Bereich der Registry und rufen sie sp&auml;ter wieder dort ab. Die dazu notwendigen Funktionen <b>GetAppSetting <\/b>und <b>SaveAppSetting <\/b>stellen wir im Artikel <b>Anwendungsdaten in der Registry <\/b>(<b>www.vbentwickler.de\/411<\/b>) vor.<\/p>\n<p>Der Bereich der Registry, in dem wir unsere Anwendungseinstellungen speichern, sieht wie in Bild 5 aus. Damit die Einstellungen in diesem Bereich landen, definieren wir die beiden folgenden Konstanten im Modul <b>mdlRegistry.twin<\/b>:<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_006.png\" alt=\"Speichern von Anwendungseinstellungen in der Registry\" width=\"649.627\" height=\"342.8586\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Speichern von Anwendungseinstellungen in der Registry<\/span><\/b><\/p>\n<pre><span style=\"color:blue;\">Public <\/span>Const cStrAppName<span style=\"color:blue;\"> As String<\/span> = \"amvFileDialogWizard\"\r\n<span style=\"color:blue;\">Public <\/span>Const cStrSection<span style=\"color:blue;\"> As String<\/span> = \"Defaultvalues\"<\/pre>\n<p>Wir verwenden die Funktion <b>GetAppSetting<\/b>, um die Werte f&uuml;r <b>ButtonName<\/b>, <b>FunctionName<\/b>, <b>InitialFileName<\/b>, <b>Title <\/b>und <b>AllowMultiSelect <\/b>einzulesen und den entsprechenden Steuerelementen zuzuweisen.<\/p>\n<p>F&uuml;r das Textfeld <b>txtFunctionName <\/b>verwenden wir, falls noch kein entsprechender Wert in der Registry gespeichert wurde, den Wert <b>OpenFileDialog<\/b>. <b>chkAllowMultiSelect <\/b>belegen wir mit dem Wert <b>0 <\/b>vor, also <b>False<\/b>.<\/p>\n<p>Danach bereiten wir das <b>ListView<\/b>-Steuerelement <b>lvwFilters <\/b>vor &#8211; zun&auml;chst auf die Ansicht <b>lvwReport <\/b>(Listenansicht) und durch Hinzuf&uuml;gen der Spalten&uuml;berschriften <b>Beschreibung <\/b>und <b>Filter<\/b>. Au&szlig;erdem stellen wir die Spaltenbreiten auf 60% und 40% der gesamten Breite ein.<\/p>\n<p>Au&szlig;erdem sorgen wir mit <b>MultiSelect <\/b>und dem Wert <b>True <\/b>daf&uuml;r, dass der Benutzer mehrere Eintr&auml;ge markieren kann und mit <b>FullRowSelect <\/b>legen wir fest, dass die ganze Zeile markiert angezeigt wird.<\/p>\n<p>Dann ermitteln wir einen eventuell bereits zuvor ermittelten Filter aus der Registry. Hier werden die einzelnen Name-Wert-Paare durch das Pipe-Zeichen (<b>|<\/b>) und der Name und der Wert durch die Tilde getrennt (<b>~<\/b>). Wir lesen den Ausdruck wie <b>Alle Dateien~*.*|Access-Datenbanken~*.accdb <\/b>in die Variable <b>strFilterTemp <\/b>ein.<\/p>\n<p>Wenn die L&auml;nge dieses Ausdrucks gr&ouml;&szlig;er 0 ist, erstellen wir in <b>strFiltersTemp <\/b>ein Array, das die durch das Pipe-Zeichen getrennten Elemente enth&auml;lt. Dieses durchlaufen wir in einer <b>For&#8230;Next<\/b>-Schleife &uuml;ber alle Elemente und schreiben den Teil vor der Tilde in die Variable <b>strBeschreibung <\/b>und den hinteren Teil in <b>strDateiendung<\/b>. Schlie&szlig;lich weisen wir den Wert aus <b>strBeschreibung <\/b>als neues Element der <b>ListItems<\/b>-Auflistung hinzu und den Wert aus <b>strDateiendung<\/b> mit <b>SubItems(1) <\/b>in die zweite Spalte.<\/p>\n<p>Schlie&szlig;lich f&uuml;gen wir dem Kombinationsfeld <b>cboType<\/b> die vier Werte <b>FileDialogFilePicker<\/b>, <b>FileDialogFolderPicker<\/b>, <b>FileDialogOpen <\/b>und <b>FileDialogSaveAs <\/b>hinzu.<\/p>\n<p>F&uuml;r das Kombinationsfeld w&auml;hlen wir den Wert aus, den wir der Registry f&uuml;r den Eintrag <b>Type <\/b>entnehmen &#8211; und ersatzweise den Wert <b>FileDialogFilePicker<\/b>.<\/p>\n<p>Das Textfeld <b>txtDescription <\/b>belegen wir mit <b>Alle Dateitypen <\/b>vor und <b>txtFileExtension <\/b>mit <b>*.*<\/b>.<\/p>\n<p>Schlie&szlig;lich rufen wir die Prozedur <b>UpdateCode <\/b>auf, die den Code erstmalig zusammenstellt und im Textfeld <b>txtCode <\/b>eintr&auml;gt.<\/p>\n<h2>Anzeigen des Codes im Textfeld<\/h2>\n<p>Die Prozedur <b>UpdateCode<\/b> aus Listing 5 erstellt eine Instanz der Klasse <b>objFileDialogWizard <\/b>und weist ihren Eigenschaften die Werte der Steuerelemente des Forms zu.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>UpdateCode()\r\n     <span style=\"color:blue;\">With<\/span> objFileDialogWizard\r\n         .FunctionName = txtFunctionName\r\n         .Title = txtTitle\r\n         Select Case cboType\r\n             <span style=\"color:blue;\">Case <\/span>\"FileDialogFilePicker\"\r\n                 .Type = msoFileDialogFilePicker\r\n             <span style=\"color:blue;\">Case <\/span>\"FileDialogFolderPicker\"\r\n                 .Type = msoFileDialogFolderPicker\r\n             <span style=\"color:blue;\">Case <\/span>\"FileDialogOpen\"\r\n                 .Type = msoFileDialogOpen\r\n             <span style=\"color:blue;\">Case <\/span>\"FileDialogSaveAs\"\r\n                 .Type = msoFileDialogSaveAs\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n         .AllowMultiSelect = chkAllowMultiSelect\r\n         .Separator = \"|\"\r\n         .ButtonName = txtButtonName\r\n         .InitialFilename = txtInitialFilename\r\n         .Filter = GetFilter\r\n         txtCode = .CreateFileDialogCode\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Die Prozedur UpdateCode aktualisiert den Code<\/span><\/b><\/p>\n<p>Danach ruft sie die Methode <b>CreateFileDialogCode <\/b>auf, um das Ergebnis dem Textfeld <b>txtCode <\/b>zuzuweisen. Somit erhalten wir direkt nach dem &Ouml;ffnen des Assistenten die aktuelle Version des Codes. Diese kann der Benutzer nun durch Einstellen der verschiedenen Eigenschaften anpassen.<\/p>\n<h2>Filter zusammenstellen<\/h2>\n<p>Interessant ist dabei das Zusammenstellen des Filters. Dies erledigt die Prozedur <b>GetFilter<\/b> (siehe Listing 6). Diese Prozedur referenziert das <b>ListView<\/b>-Steuerelement <b>lvwFilters <\/b>und pr&uuml;ft zun&auml;chst, ob es &uuml;berhaupt ein Element enth&auml;lt. Falls nicht, beendet die Funktion sich selbst.<\/p>\n<pre><span style=\"color:blue;\">Private Function <\/span>GetFilter()<span style=\"color:blue;\"> As Variant<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>strFilter()<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">With<\/span> lvwFilters\r\n         <span style=\"color:blue;\">If <\/span>.ListItems.Count = 0<span style=\"color:blue;\"> Then<\/span>\r\n             <span style=\"color:blue;\">Exit Function<\/span>\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         ReDim strFilter(1 To .ListItems.Count, 1 To 2)\r\n         For i = 1 To .ListItems.Count\r\n             strFilter(i, 1) = .ListItems(i)\r\n             strFilter(i, 2) = .ListItems(i).SubItems(1)\r\n         <span style=\"color:blue;\">Next<\/span>\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     Return strFilter\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Die Funktion GetFilter stellt den Filterausdruck zusammen.<\/span><\/b><\/p>\n<p>Anderenfalls redimensioniert sie die <b>Array<\/b>-Variable <b>strFilter <\/b>so, dass dieses die Anzahl Zeilen entsprechend der Zeilen des <b>ListView<\/b>-Steuerelements enth&auml;lt und zwei Spalten.<\/p>\n<p>Danach durchlaufen wir in einer Schleife von <b>1 <\/b>bis zur Anzahl der <b>ListView<\/b>-Elemente alle enthaltenen Eintr&auml;ge und weisen die Werte der ersten und der zweiten Spalte dem jeweiligen Element des Arrays zu. Das Ergebnis geben wir an die aufrufende Routine zur&uuml;ck.<\/p>\n<h2>Filter hinzuf&uuml;gen<\/h2>\n<p>Damit wir die Filter &uuml;berhaupt auswerten k&ouml;nnen, m&uuml;ssen wir zun&auml;chst einmal einen zum ListView-Steuerelement hinzuf&uuml;gen. Das erledigen wir, indem wir die Beschreibung des Filters sowie den Filter in die beiden Textfelder <b>txtDescription <\/b>und <b>txtFileExtension <\/b>f&uuml;llen und auf die Schaltfl&auml;che <b>cmdAddFilter <\/b>klicken (siehe Bild 6).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2024_02\/pic_420_007.png\" alt=\"Hinzuf&uuml;gen eines Filters\" width=\"649.627\" height=\"240.0381\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Hinzuf&uuml;gen eines Filters<\/span><\/b><\/p>\n<p>Dies l&ouml;st die folgende Prozedur aus:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdAddFilter_Click()\r\n     <span style=\"color:blue;\">Dim <\/span>itm<span style=\"color:blue;\"> As <\/span>ListItem\r\n     <span style=\"color:blue;\">Set<\/span> itm = lvwFilters.ListItems.Add(, , txtDescription)\r\n     itm.SubItems(1) = txtFileExtension\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Die Prozedur f&uuml;gt ein neues Element des Typs <b>ListItem <\/b>zum <b>ListView<\/b>-Steuerelement hinzu und gibt den Inhalt von <b>txtDescription <\/b>als Text an. Die zweite Spalte f&uuml;llt es &uuml;ber die Eigenschaft <b>SubItems(1) <\/b>der neu hinzugef&uuml;gten Zeile, welche den Inhalt von <b>txtFileExtension <\/b>erh&auml;lt.<\/p>\n<p>Anschlie&szlig;end ruft auch diese Prozedur die Routine <b>UpdateCode <\/b>auf, um den angezeigten Code um den neu hinzugef&uuml;gten Filter zu erg&auml;nzen.<\/p>\n<h2>Filter aus der Liste entfernen<\/h2>\n<p>Gegebenenfalls m&ouml;chte man einen Filter wieder entfernen. Dazu markieren wir den oder die zu entfernenden Eintr&auml;ge und bet&auml;tigen die Schaltfl&auml;che <b>cmdRemoveFilter<\/b>, die den folgenden Code ausl&ouml;st:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdRemoveFilter_Click()\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">With<\/span> lvwFilters\r\n         For i = .ListItems.Count To 1 Step -1\r\n             <span style=\"color:blue;\">If <\/span>.ListItems(i).Selected<span style=\"color:blue;\"> Then<\/span>\r\n                 .ListItems.Remove i\r\n             <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">Next<\/span> i\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Die Prozedur durchl&auml;uft alle Elemente des <b>ListView<\/b>-Steuerelements vom letzten bis zum ersten. Wenn das aktuelle Element markiert ist, was wir mit der <b>Selected<\/b>-Eigenschaft pr&uuml;fen, entfernen wir das Element mit dem entsprechenden Index mit der <b>Remove<\/b>-Methode.<\/p>\n<p>Auch hier aktualisieren wir den zu erzeugenden Code mit der <b>UpdateCode<\/b>-Methode.<\/p>\n<h2>Aktualisieren des resultierenden Codes durch die &uuml;brigen Steuerelemente<\/h2>\n<p>Auch wenn der Benutzer eine der &uuml;brigen Eigenschaften aktualisiert, also beispielsweise den Funktionsnamen anpasst, soll sich dies direkt im zu erzeugenden Code widerspiegeln. Das erreichen wir durch verschiedene Ereignisprozeduren, die jeweils durch das Ereignis <b>Change <\/b>beziehungsweise bei der CheckBox durch das Ereignis <b>Click <\/b>ausgel&ouml;st werden:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>txtTitle_Change()\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode()\r\n<span style=\"color:blue;\">End Sub<\/span>\r\n<span style=\"color:blue;\">Private Sub <\/span>txtFunctionName_Change()\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode()\r\n<span style=\"color:blue;\">End Sub<\/span>\r\n<span style=\"color:blue;\">Private Sub <\/span>cboType_Change()\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode()\r\n<span style=\"color:blue;\">End Sub<\/span>\r\n<span style=\"color:blue;\">Private Sub <\/span>txtButtonName_Change()\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode()\r\n<span style=\"color:blue;\">End Sub<\/span>\r\n<span style=\"color:blue;\">Private Sub <\/span>chkAllowMultiSelect_Click()\r\n     <span style=\"color:blue;\">Call<\/span> UpdateCode()\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<h2>Ausblenden oder Schlie&szlig;en des Fensters<\/h2>\n<p>Es gibt verschiedene M&ouml;glichkeiten, die Bearbeitung abzuschlie&szlig;en. Wenn der Benutzer die <b>OK<\/b>-Schaltfl&auml;che bet&auml;tigt, soll das Formular nur ausgeblendet werden, damit die aufrufende Routine die aktuelle Version der zu erzeugenden Funktion einlesen und weiterverarbeiten kann. Dazu f&uuml;gen wir der Prozedur, die durch das Anklicken der Schaltfl&auml;che ausgel&ouml;st wird, die folgende Anweisung hinzu:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdOK_Click()\r\n     Me.Visible = <span style=\"color:blue;\">False<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Andere M&ouml;glichkeiten, die Bearbeitung zu beenden, sind das Bet&auml;tigen der Schlie&szlig;en-Schaltfl&auml;che (<b>X<\/b>) oder der <b>Abbrechen<\/b>-Schaltfl&auml;che. Diese ruft die Prozedur <b>UnloadObjectByHandle <\/b>auf und &uuml;bergibt dieser das Handle auf das <b>Form<\/b>-Element:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdCancel_Click()\r\n     UnloadObjectByHandle(Me.hWnd)\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Diese nutzt die API-Funktion <b>SendMessage<\/b>, um das <b>Form<\/b>-Element r&uuml;ckstandslos zu terminieren:<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>UnloadObjectByHandle(lHwnd<span style=\"color:blue;\"> As Long<\/span>)\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     SendMessage lHwnd, WM_CLOSE, ByVal 0&, ByVal 0&\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<h2>Beim Entladen des Formulars<\/h2>\n<p>Wenn das Formular entladen wird, sollen die vorgenommenen Einstellungen noch in der Registry gespeichert werden. An dieser Stelle m&uuml;ssen wir uns entscheiden, ob wir dies auf jedem Fall machen oder nur, wenn der Benutzer die <b>OK<\/b>-Schaltfl&auml;che gedr&uuml;ckt hat. Gegebenenfalls m&ouml;chte er durch das Bet&auml;tigen der Abbrechen-Schaltfl&auml;che ja auch ausdr&uuml;cken, dass er noch nicht einmal die &Auml;nderungen an den Einstellungen speichern m&ouml;chte. Wir entscheiden uns der Einfachheit halber daf&uuml;r, die &Auml;nderungen immer zu speichern.<\/p>\n<p>In diesem Fall rufen wir in der Prozedur aus Listing 7 f&uuml;r die meisten Einstellungen einfach die Funktion <b>SaveAppSetting <\/b>auf und speichern so die entsprechenden Werte aus den Steuerelementen in der Registry.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>Form_Unload(Cancel<span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>strFilter<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     SaveAppSetting (\"ButtonName\", txtButtonName)\r\n     SaveAppSetting(\"FunctionName\", txtFunctionName)\r\n     SaveAppSetting(\"InitialFilename\", txtInitialFilename)\r\n     SaveAppSetting(\"Title\", txtTitle)\r\n     SaveAppSetting(\"AllowMultiSelect\", chkAllowMultiSelect)\r\n     SaveAppSetting(\"Type\", cboType)\r\n     For i = 1 To lvwFilters.ListItems.Count\r\n         strFilter += \"|\" & lvwFilters.ListItems(i) & \"~\" & lvwFilters.ListItems(i).SubItems(1)\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n     <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> <span style=\"color:blue;\">Len<\/span>(strFilter) = 0<span style=\"color:blue;\"> Then<\/span>\r\n         strFilter = <span style=\"color:blue;\">Mid<\/span>(strFilter, 2)\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     SaveAppSetting(\"Filter\", strFilter)\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 7: Prozedur beim Endladen des Forms<\/span><\/b><\/p>\n<p>Beim Filter ist ein wenig mehr Aufwand erforderlich. Hier durchlaufen wir alle Eintr&auml;ge des <b>ListView<\/b>-Steuerelements. Dabei erstellen wir f&uuml;r jeden Eintrag aus dem Text der ersten und der zweiten Spalte eine Zeichenkette wie <b>|Alle Dateien~*.*<\/b>. Diese h&auml;ngen wir aneinander, sodass wir beispielsweise folgenden Ausdruck in der Variablen <b>strFilter <\/b>erhalten:<\/p>\n<pre>|Alle Dateien~*.*|Access-Datenbanken~*.accdb<\/pre>\n<p>Hier m&uuml;ssen wir nun noch das f&uuml;hrende Pipe-Zeichen entfernen und den Ausdruck in der Registry speichern.<\/p>\n<h2>Restarbeiten<\/h2>\n<p>Damit sind nur noch wenige Restarbeiten zu erledigen. Wir f&uuml;gen dem <b>Form<\/b>-Element noch ein <b>Icon <\/b>sowie eine Beschriftung hinzu (&uuml;ber die Eigenschaft <b>Caption<\/b>).<\/p>\n<p>Damit brauchen wir das COM-Add-In nur noch zu kompilieren und zu registrieren. Beides gelingt, wenn wir auf die <b>Build<\/b>-Schaltfl&auml;che klicken. Danach ist das COM-Add-In im VBA-Editor jeder Office-Anwendung verf&uuml;gbar.<\/p>\n<p>Im <b>Build<\/b>-Ordner finden wir, wenn wir das COM-Add-In f&uuml;r 32-Bit erstellt haben, die Datei <b>amvFileDialogWizard_win32.dll<\/b>. Diese k&ouml;nnen wir nun auch auf andere Rechner &uuml;bertragen. Damit sie auch dort registriert werden, k&ouml;nnen wir im gleichen Verzeichnis noch eine Datei namens <b>Register.bat <\/b>hinzuf&uuml;gen, die lediglich den folgenden Text enth&auml;lt:<\/p>\n<pre>RegSvr32.exe amvFileDialogWizard_win32.dll<\/pre>\n<p>Diese f&uuml;hrt die in der DLL enthaltene Prozedur <b>DllRegisterServer <\/b>aus und tr&auml;gt die DLL so in die Registry ein. Der folgende Befehl hebt die Registrierung wieder auf:<\/p>\n<pre>RegSvr32.exe amvFileDialogWizard_win32.dll \/u<\/pre>\n<p>Dies ruft die Funktion <b>DllUnregisterServer <\/b>auf.<\/p>\n<h2>Voraussetzung<\/h2>\n<p>Eine Voraussetzung f&uuml;r die korrekte Funktion der mit dieser DLL hinzugef&uuml;gten Funktionen sei noch genannt: Wir ben&ouml;tigen dazu noch einen Verweis auf die Bibliothek <b>Microsoft Office 16.0 Object Library<\/b>. Alternativ k&ouml;nnen wir <b>objFileDialog <\/b>mit Late Binding als Object deklarieren und m&uuml;ssen dann noch die Konstanten wie f&uuml;r den Typ des Dialogs durch Zahlenwerte ersetzen.<\/p>\n<h2>Zusammenfassung und Ausblick<\/h2>\n<p>Dieses COM-Add-In f&uuml;gt dem VBA-Editor eine Funktion hinzu, die es uns leicht macht, schnell einen Dateiauswahl-Dialog zu programmieren. Das gelingt f&uuml;r alle Arten dieses Dialogs, also auch f&uuml;r Verzeichnisse oder f&uuml;r zu speichernde Dateien.<\/p>\n<h2>Downloads zu diesem Beitrag<\/h2>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>amvFileDialogWizard.zip<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/AA30F8B1-E0D1-4FCC-9D5A-17528E0B406D\/vbe_420.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dateidialoge ben&ouml;tigt man immer wieder. Ob man nun Dateien zum &Ouml;ffnen oder Bearbeiten ausw&auml;hlen m&ouml;chte, ob man einen Pfad zum Speichern einer Datei braucht oder ob man ein Verzeichnis selektieren will &#8211; am einfachsten geht das mit den praktischen Dateidialogen. Die Office-Bibliothek bietet sogar alle ben&ouml;tigten Varianten &uuml;ber die FileDialog-Klasse an. Dumm ist nur, dass man nicht st&auml;ndig Filedialoge programmiert, sondern nur alle paar Wochen, Monate oder sogar Jahre. Dann muss man sich immer wieder einarbeiten, um die verschiedenen Parameter &#8211; Titel, Schaltfl&auml;chenbeschriftungen, Standardverzeichnis und -dateiname, Filter, Dateiendungen und so weiter zu definieren. Wie sch&ouml;n w&auml;re es doch, wenn wir solche Dateidialoge mit einem kleinen Assistenten zusammenstellen k&ouml;nnten. Also machen wir uns ans Werk und schaffen einen solchen Wizard!<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[66022024,662024,44000027,44000026,44000037],"tags":[],"yst_prominent_words":[],"class_list":["post-55000420","post","type-post","status-publish","format-standard","hentry","category-66022024","category-662024","category-Excel_programmieren","category-Outlook_programmieren","category-VBAEditor_programmieren"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000420","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/comments?post=55000420"}],"version-history":[{"count":0,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/posts\/55000420\/revisions"}],"wp:attachment":[{"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/media?parent=55000420"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/categories?post=55000420"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/tags?post=55000420"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/vbentwickler.de\/data\/wp\/v2\/yst_prominent_words?post=55000420"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}