API-Funktion GetSaveFileDialog (32-Bit und 64-Bit)

Das Öffnen eines Dialogs zum Auswählen des Namens einer zu speichernden Datei erledigen Sie beispielsweise mit der API-Funktion “GetSaveFileDialog”. Diese stellen wir im vorliegenden Beitrag für 32-Bit- und 64-Bit-Office vor. Dabei weisen wir auch auf die Änderungen hin, die für das Update einer eventuell bereits bestehenden 32-Bit-Version auf die 64-Bit-Version notwendig sind.

Die API-Funktion GetSaveFileDialog ist eine Funktion der Bibliothek comdlg32.dll. Sie erwartet alle Parameter, die das Aussehen des Speichern-Dialogs beeinflussen, in Form eines Typs namens OPENFILENAME. Er heißt OPENFILENAME, weil er in gleicher Form auch für die API-Funktion zum Anzeigen eines Datei öffnen-Dialogs zum Einsatz kommt. Sie liefert den Pfad der zu speichernden Datei zurück oder eine leere Zeichenkette, falls kein Name ausgewählt wurde – zum Beispiel, weil der Benutzer den Dialog mit der Abbrechen-Schaltfläche geschlossen hat.

Die Wrapper-Funktion GetSaveFile

Vor dem Aufruf der API-Funktion GetSaveFileDialog füllt man also den Typ OPENFILENAME mit den gewünschten Einstellungen.

Da Sie das nicht jedes Mal erledigen sollen, wenn Sie diese Funktion zu einer Ihrer Anwendungen hinzufügen wollen, liefern wir eine Wrapperfunktion namens GetSaveFile. Diese sieht wie in Listing 1 aus.

Public Function GetSaveFile(Optional strStartDir As String, _
         Optional strDefFileName As String, _
         Optional strFilter As String = "Alle Dateien (*.*)", _
         Optional strTitle As String) As String
     Dim udtOpenFileName As OPENFILENAME
     Dim strExt As String
     On Error GoTo Fehler
     If Len(strStartDir) = 0 Then
         strStartDir = CurrentProject.Path
     End If
     With udtOpenFileName
         .nStructSize = LenB(udtOpenFileName)
         .hwndOwner = Application.hWndAccessApp
         strFilter = strFilter & vbNullChar & vbNullChar
         .sFilter = strFilter
         .nFilterIndex = 1
         .sInitDir = strStartDir & vbNullChar
         .sDlgTitle = strTitle
         .sFile = Space$(256) & vbNullChar
         .nFileSize = Len(.sFile)
         If Len(strDefFileName) <> 0 Then 
             Mid(.sFile, 1) = strDefFileName
         End If
         .sFileTitle = Space$(256) & vbNullChar
         .nTitleSize = Len(.sFileTitle)
         If GetSaveFileName(udtOpenFileName) Then
             GetSaveFile = Left(udtOpenFileName.sFile, InStr(.sFile, vbNullChar) - 1)
         Else
             GetSaveFile = ""
         End If
     End With
Ende:
     Exit Function
Fehler:
     MsgBox Err.Description, vbCritical, "GetSaveFileName"
     Resume Ende
End Function

Listing 1: Wrapperfunktion für die API-Funktion GetSaveFileName

Dies sind die Parameter:

  • strStartDir: Hier geben Sie das Verzeichnis an, das beim Öffnen des Dialogs vorausgewählt werden soll.
  • strDefFileName: Vordefinierter Dateiname, der beim Öffnen des Dialogs im Feld Dateiname eingetragen werden soll.
  • strFilter: Ein Ausdruck, der angibt, welche Dateitypen als Speichername verwendet werden dürfen.
  • strTitle: Titel des Dialogs

Ablauf von GetSaveFile

Der Wrapperfunktion für die API-Funktion GetSaveFileName haben wir einen etwas verkürzten Namen gegeben – eben GetSaveFile. Sie definiert als Erstes eine Variable namens udtOpenFileName des Typs OPENFILENAME. Wie dieser genau aussieht und deklariert wird, zeigen wir weiter unten.

Dieser Typ arbeitet wie eine Klasse und hat verschiedene Eigenschaften, die wir mit der Funktion GetSaveFile füllen. nStructSize nimmt beispielsweise die Größe des Typs udtOpenFileName entgegen, die wir mit der Funktion LenB ermitteln. Das ist übrigens auch eine der Änderungen der 64-Bit-Version gegenüber der 32-Bit-Version – früher wurde hier die Funktion Len verwendet. Len ermittelt die Anzahl der Zeichen einer Zeichenkette, LenB die Anzahl der Bytes dieser Zeichenkette. hwndOwner füllen wir mit dem Handle des aktuellen Fensters. Den Filter aus dem Parameter strFilter erweitern wir noch um zwei vbNullChar, bevor wir ihn der Eigenschaft sFilter zuweisen. Gültige Werte dazu schauen wir uns gleich im Anschluss an. Welcher der Filter voreingestellt wird, legt die Funktion für die Eigenschaft nFilterIndex fest, in diesem Fall mit dem Wert 1.

Das mit dem Parameter strStartDir übergebene Startverzeichnis ergänzt die Funktion ebenfalls um vbNullChar und schreibt sie dann in die Eigenschaft sInitDir. Der Titel für den Dialog landet unbehandelt in sDlgTitle.

In sFile trägt die Funktion eine leere Zeichenkette mit einer Länge von 256 Zeichen und einem abschließenden vbNullChar ein. nFileSize erhält die Länge von sFile.

Wenn der Aufruf einen Wert für den Parameter strDef-FileName enthält, also den voreinzustellenden Speichernamen, wird dieser vorn in der Eigenschaft sFile eingesetzt. Damit die Länge der darin enthaltenen Zeichenkette nicht verändert wird, verwenden wir dazu die Mid-Funktion in einer eher unbekannten Art. Der Aufruf Mid(.sFile, 1) = strDefFileName sorgt dafür, dass der Inhalt von strDefFileName die entsprechenden Zeichen von .sFile ab der ersten Position überschreibt.

sFileTitle soll den ermittelten Namen ohne Verzeichnis aufnehmen und wird mit 256 Leerzeichen plus abschließendem vbNullChar gefüllt. Auch die Länge diese Zeichenkette landet in udtOpenFileName, und zwar in der Eigenschaft nTitleSize.

Nachdem alle relevanten Eigenschaften von udtOpenFileName gefüllt sind, ruft die Funktion die API-Funktion GetSaveFileName mit udtOpenFileName als Parameter auf. Liefert diese Funktion den Wert True zurück, was der Fall ist, wenn der Benutzer die Schaltfläche OK betätigt, schreibt die Funktion den Inhalt von sFile aus udtOpenFileName in den Rückgabewert der Funktion. Anderenfalls liefert die Funktion eine leere Zeichenkette zurück.

Deklaration der API-Funktion GetSaveFileName

Wichtig für die Einsatzbereitschaft der Funktion GetSaveFileName unter 32-Bit- und 64-Bit-Office ist, dass Sie für beide Varianten die richtige Deklaration bereitstellen. Genau genommen ist es sogar so, dass Sie nur prüfen müssen, ob Sie VBA 6.x oder VBA 7.x verwenden. VBA 6.0 kam mit älteren Office-Versionen bis 2007. VBA 7 wurde eingeführt, um die Kompatibilität mit 64-Bit zu sichern. Grundsätzlich müssen wir also prüfen, welche Access-Version auf dem Zielrechner vorliegt und nicht, ob diese mit 32-Bit oder 64-Bit kompatibel ist (mehr dazu im Beitrag 32-Bit, 64-Bit, VBA-Version und Co. (www.access-im-unternehmen.de/1322).

Ausgangspunkt für diesen Beitrag war eine Version der Deklaration des Typen OPENFILENAME und der Funktion GetSaveFileName, die unter VBA 6.x erstellt wurde und dementsprechend mit dem Datentyp Long auch für Zeiger und Handles arbeitet und die in der Deklaration der API-Funktion nicht das Schlüsselwort PtrSafe verwendet.

Diese funktioniert auch noch unter VBA 7.x, allerdings nur, wenn Office in der 32-Bit-Version vorliegt. Wir wollen nun eine Version der Deklaration erschaffen, die noch kompatibel mit VBA 6.x ist, aber auch mit VBA 7.x und somit auch mit 64-Bit. Dazu benötigen wir zwei Varianten, die wir in einer If…Then-Bedingung unterscheiden. Weil wir in der Bedingung die Kompilierkonstante VBA7 nutzen, müssen wir den Zeilen der Bedingung das Raute-Zeichen (#) voranstellen. Damit werden die innerhalb der Bedingung aufgeführten Zeilen nur im Code berücksichtigt, wenn die jeweilige Bedingung wahr ist.

In Listing 2 prüfen wir in der If…Then-Bedingung den Wert der Kompilierkonstanten VBA7 im If-Teil der Bedingung. Dieser Teil enthält also den Code, der ausgeführt werden soll, wenn wir VBA 7.x nutzen. Der Else-Teil führt den Code aus, der mit VBA 6.x kompatibel ist. Den Code aus dem Else-Teil kennen Sie vermutlich aus älteren Beiträgen. Im If-Teil für VBA 7.x haben wir kleine Änderungen vorgenommen:

#If VBA7 Then
Private Type OPENFILENAME
     nStructSize     As Long
     hwndOwner       As LongPtr
     hInstance       As LongPtr
     sFilter         As String
     sCustomFilter   As String
     nCustFilterSize As Long
     nFilterIndex    As Long
     sFile           As String
     nFileSize       As Long
     sFileTitle      As String
     nTitleSize      As Long
     sInitDir        As String
     sDlgTitle       As String
     Flags           As Long
     nFileOffset     As Integer
     nFileExt        As Integer
     sDefFileExt     As String
     nCustData       As Long
     fnHook          As LongPtr
     sTemplateName   As String
End Type
Private Declare PtrSafe Function GetSaveFileName Lib "comdlg32.dll" Alias _
     "GetSaveFileNameA" (pOpenfilename As OPENFILENAME) As Long
#Else
Private Type OPENFILENAME
     nStructSize     As Long
     hwndOwner       As Long
     hInstance       As Long
     sFilter         As String
     sCustomFilter   As String
     nCustFilterSize As Long
     nFilterIndex    As Long
     sFile           As String
     nFileSize       As Long
     sFileTitle      As String
     nTitleSize      As Long
     sInitDir        As String
     sDlgTitle       As String
     Flags           As Long
     nFileOffset     As Integer
     nFileExt        As Integer
     sDefFileExt     As String
     nCustData       As Long
     fnHook          As Long
     sTemplateName   As String
End Type
Private Declare Function GetSaveFileName Lib "comdlg32.dll" _
     Alias "GetSaveFileNameA" (pOpenfilename As OPENFILENAME) As Long
#End If

Listing 2: Die Deklaration der API-Funktion GetSaveFileName plus dem Typ OPENFILENAME

  • In der Deklaration des Typs OPENFILENAME haben wir für die drei Eigenschaften hwndOwner, hInstance und fnHook den Datentyp Long durch LongPtr ersetzt. Diese drei Eigenschaften enthalten Zeiger, Handle und Hook – nur diese müssen geändert werden, die übrigen Long-Eigenschaften, die reine Zahlenwerte enthalten, bleiben erhalten.
  • In der Deklaration der API-Funktion GetSaveFileName stellen wir dem Schlüsselwort Function das Wort PtrSafe voran.
  • Die letzte Änderung haben wir bereits besprochen – das Ändern der Funktion Len zur Berechnung der Größe von OPENFILENAME in LenB.

Wenn Sie sicher sind, dass Ihre Anwendung nur unter Access ab Version 2010 eingesetzt wird, können Sie natürlich auch nur den Teil im If-Teil der Bedingung abbilden und die Bedingung weglassen. Das heißt, Sie müssen nur eventuell vorhandenen API-Code für VBA 6.x nach VBA 7.x migrieren.

Die Wrapper-Funktion GetSaveFile verwenden

Nun schauen wir uns noch einige Beispiele für den Aufruf von GetSaveFile an, die Sie jeweils im Direktbereich absetzen können.

Die einfachste Variante lautet:

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