Home > Artikel > Ausgabe 2/2015 > Zippen mit Bordmitteln

Zippen mit Bordmitteln

  PDF ansehen

  Download PDF und Beispieldatenbank

Das VBA-gesteuerte Packen und Archivieren von Dateien ist eine Aufgabe, der auch Access-Entwickler immer wieder begegnen. Dass man dabei keineswegs auf externe Komponenten oder Hilfsmittel angewiesen ist und mit Windows-Bordmittel auskommen kann, soll dieser Beitrag zeigen.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1502_Zippen.mdb.

Zip-Format

Es gibt zweifellos Alternativen zum altehrwürdigen Zip-Format, denn RAR oder 7z etwa bieten erheblich bessere Kompressionsraten an. Doch diese Formate setzen voraus, dass auf dem System ein entsprechendes Archivierungsprogramm installiert ist, und bei Weitergabe einer solchen Archivdatei können Sie nicht einfach davon ausgehen, dass dies beim Adressaten der Fall ist. Mit dem Zip-Format sind Sie auf der sicheren Seite: Windows hat seit einigen Generationen einen entsprechenden Support eingebaut. Über den Explorer lassen sich Zip-Dateien wie Ordner behandeln, einsehen und bearbeiten. Und was sich manuell erledigen lässt, sollte sich über irgendeinen Weg auch programmtechnisch realisieren lassen.

Shell-Funktionen

Die übliche Herangehensweise, wenn Funktionen von Windows gefragt sind, ist die Suche im API-Katalog. In unserem Fall kann darauf verzichtet werden, da Windows mit der Shell-Bibliothek eine COM-Schnittstelle zur Verfügung stellt, die alles Nötige mitbringt. Es handelt sich um die shell32.dll im Systemverzeichnis, welche sich in der Liste der Verweise von VBA namentlich als Microsoft Shell Controls And Automation ausgibt. Nachdem Sie die Bibliothek ihrem Projekt über den Verweisedialog hinzugefügt haben, finden Sie deren Klassen im Objektkatalog, wenn Sie links oben den Eintrag Shell32 auswählen (Bild 1). An den Klassenbezeichnungen, wie Folder, FolderItem und ShellFolderView, lässt sich bereits ablesen, dass es hier zuvörderst um Verzeichnisse und deren Inhalte geht. Da die Shell, wie erwähnt, eine Zip-Datei als Verzeichnis ansieht, wäre nun zu erläutern, wie man so ein Verzeichnis anspricht. Dazu gehen Sie vom Hauptobjekt der Bibliothek aus, der Shell-Klasse, welche in der Abbildung bereits markiert ist.

Die Shell32-Bibliothek, im VBA-Objektkatalog dargestellt

Bild 1: Die Shell32-Bibliothek, im VBA-Objektkatalog dargestellt

Eine Instanz dieser Klasse erhalten Sie einfach über die New-Zuweisung an eine Objektvariable:

Dim objShell As New Shell32.Shell

Hier sollte nicht unerwähnt bleiben, dass die ganze Angelegenheit auch komplett ohne einen Bibliotheksverweis auskäme. Folgender Ausdruck erzeugt nämlich ebenfalls das Shell-Objekt:

Dim objShell As Object

Set objShell = _

CreateObject("Shell.Application")

Haben Sie nun das Hauptobjekt, so können Sie sogleich allerlei Methoden desselben ansprechen. So öffnet etwa die Methode objShell.FileRun den Ausführen-Dialog von Windows, oder TrayProperties den Dialog zu Einstellungen der Taskleiste. Warum Microsoft ausgerechnet solche Funktionen in die COM-Schnittstelle verbaute, ist nicht recht nachvollziehbar, da man sie programmgesteuert wohl höchst selten aufrufen wird. Sie interessieren uns auch nicht weiter. Lediglich die Funktion NameSpace ist für unsere Belange wichtig: sie allein gibt ein Folder-Objekt, also ein Verzeichnis, zurück. Dazu übergibt man ihr als Parameter den Verzeichnisnamen:

Dim objFolder As Shell32.Folder

Set objFolder = objShell.NameSpace ( _

"c:\windows")

Auf den Methodennamen NameSpace würde man wahrscheinlich nicht kommen und eher so etwas wie GetFolder erwarten. Grund für die Bezeichnung ist, dass sie eben nicht nur physische Verzeichnisse ermittelt, sondern auch virtuelle, wie dies beim Zip-Archiv vorliegt. Statt des Verzeichnispfads nimmt sie etwa auch Zahlen entgegen. Versuchen Sie dies:

objShell.NameSpace(0).Title

Ergebnis wäre der String Desktop. (Title ist die Methode zum Auslesen der Bezeichnung eines Folder-Objekts.) Der komplette Pfadname ist etwas umständlicher zu ermitteln:

objShell.NameSpace(0).Self.Path

Das ergibt etwa C:\Users\André\Desktop. Sie können nun mit den Zahlenparametern experimentieren. Übergeben Sie die 3, so erhalten Sie als Bezeichnung Systemsteuerung. Tatsächlich lässt sich die Systemsteuerung ja im Explorer-Baum an-zeigen. Was aber ist deren Pfad? Sie erhalten den Ausdruck

::{21EC2020-3AEA-1069-A2DD-08002B30309D}

Diese GUID symbolisiert einen virtuellen Ordner. Machen Sie die Probe aufs Exempel und setzen diese GUID als String für den Pfadnamen ein:

objShell.NameSpace("::{21EC2020-3AEA-1069-A2DD-08002B30309D}").Title

Auch das geht und liefert uns abermals die Bezeichnung Systemsteuerung.

Zusammengefasst also nimmt NameSpace als Parameter sowohl Verzeichnis-Strings entgegen, wie auch Zahlenwerte (die sogenannten KnownFolder-IDs) und schließlich GUIDs (KnownFolder-CLSIDs) als Strings.

Wenden wir uns nach diesem Ausflug in den Shell-Namespace wieder den Zip-Archiven zu. Sie können sicher erraten, wie eine Zip-Datei als Folder-Objekt zu erhalten ist: Sie übergeben einfach deren Dateinamen. Ein Beispiel:

Set objFolder = objShell.NameSpace ( _

"d:\beispiele\test.zip")

Als nächstes interessiert uns natürlich der Inhalt eines Ordners – in diesem Fall die enthaltenen Dateien. Die Folder-Klasse sieht dafür die Auflistungs-Eigenschaft Items vor, welche sich über eine For-Next-Schleife durchlaufen lässt. Jedes Element der Items-Auflistung ist vom Typ FolderItem. Möchten Sie die Namen der Dateien im Ordner ausgeben, so käme etwa diese Routine infrage:

Dim objItm As FolderItem

For Each objItm in objFolder.Items

     Debug.Print objItm.Name

Next objItm

Auf diese Weise können Sie die in einem Zip-Archiv befindlichen Dateien inspizieren. Dabei sollte noch erwähnt werden, dass das Archiv wiederum Ordner enthalten kann. Nur dann weist die Eigenschaft IsFolder des entsprechenden FolderItems den Wert True auf, und die Eigenschaft Type gibt einen Text aus, der genau der Bezeichnung im Explorer entspricht. Je nach System kann diese Bezeichnung allerdings unterschiedlich sein. Ist etwa ein Packprogramm installiert, so könnte die Bezeichnung WinRAR ZIP-Archiv lauten.

FolderItems extrahieren

Wie jedoch lassen sich die Dateien oder Ordner des Archivs nun entpacken? Im Objektkatalog findet man weder zum FolderItem, noch zum Folder-Objekt Funktionsnamen, wie Extract oder Uncompress, die dafür dienlich sein könnten. Das aber ist einleuchtend, da die Shell das Archiv als normalen Ordner ansieht und somit lediglich jene Methoden anbietet, die auch für Dateien eines Verzeichnisses gälten. Im Explorer können Sie eine Datei aus dem Archiv extrahieren, indem Sie sie entweder per Drag&Drop verschieben, oder indem Sie sie kopieren und in einen anderen Ordner einfügen. Genau das geht auch hier über die CopyHere-Funktion eines Folder-Objekts. Diese Methode erwartet als Parameter ein Folder- oder FolderItem-Objekt. Der Vorgang hat folgendermaßen auszusehen:

Sie setzen zuerst ein Folder-Objekt auf das Zielverzeichnis. Dann benutzen Sie dessen CopyHere-Methode, um in dieses Verzeichnis Dateien oder Ordner zu kopieren, wobei diese in Form von zuvor erzeugten FolderItems daher kommen müssen.

Machen wir den Test und versuchen das Archiv test.zip in einen Ordner d:\testordner zu extrahieren:

Dim objZiel As Folder

Set objZiel = objShell.NameSpace( _

     "d:\testordner")

objZiel.CopyHere objZipFolder

Leider wird das Archiv damit nicht entpackt, sondern nur eine Kopie der Zip-Datei angelegt – eigentlich klar, da das Objekt objZipFolder ja das Archiv selbst kennzeichnet, nicht dessen Inhalt. Folglich muss hier die oben angeführte Routine zum Auslesen der FolderItems modifiziert werden:

Dim objItm As FolderItem

For Each objItm in objFolder.Items

     objZiel.CopyHere objItm

     DoEvents

Next objItm

Und tatsächlich: Im Zielordner laden damit die Dateien des Archivs! Sollte das Archiv Unterordner mit Dateien enthalten, so werden auch diese alle anstandslos entsprechend der Struktur angelegt. Es ist also nicht etwa nötig, diese Unterordner und Dateien in der Routine alle einzeln anzusprechen.

Wichtig ist auch die Zeile mit der DoEvents-Anweisung. Da der Shell-Prozess asynchron arbeitet, ist er unter Umständen noch mit dem Kopieren eines Items beschäftigt, während VBA bereits mit dem Code fortfährt – die CopyHere-Methode wartet nicht auf den Vollzug! Es hat sich gezeigt, dass ohne ein DoEvents manchmal Dateien unterschlagen werden.

Bei umfangreicheren Archiven werden Sie feststellen, dass sich beim Ausführen der Routine automatisch der Windows-Fortschrittsdialog öffnet. Das Shell-Objekt verhält sich also exakt gleich, wie beim manuellen Kopieren von Dateien im Explorer. In Maßen lässt sich das Verhalten jedoch über einen zweiten optionalen Parameter von CopyHere steuern, den wir bisher unterschlagen haben. Mit Options kann man eine Kombination von Steuerkonstanten übergeben, die Sie im Modul mdlZipShell der Beispieldatenbank kommentiert als Enumeration eFileOp finden. So bewirkt etwa die Angabe von eOpNoProgress, dass sich eben kein Fortschrittsdialog öffnet. Oder eOpAllowUndo weist an, dass die Operation wieder rückgängig gemacht werden kann. In unserem Fall hieße das, dass Sie im Explorer dann das Rückgängig-Symbol betätigen könnten, wodurch sich das Zielverzeichnis wieder leeren würde. Interessant ist auch die Konstante eOpNewName. Ohne deren Angabe würde eine existierende Datei mein.txt im Zielverzeichnis nicht überschrieben, sondern automatisch mit einem neuen Namen kopiert; also etwa in Kopie von mein.txt oder mein(1).txt.

Die komplette Routine zum Entpacken eines Zip-Archivs finden Sie in der Beispieldatenbank unter der Funktion UnZipFile. Sie übergeben ihr einfach den Pfadnamen der Zip-Datei und das Zielverzeichnis. Bei erfolgtem Entpacken gibt die Funktion ein True zurück. Wie Sie sehen, kommt man über die Shell-Bibliothek mit nur wenigen Code-Zeilen zum Ergebnis, und externe Zusatzkomponenten sind überflüssig.

Zip-Archive erzeugen

Zwar gibt uns Windows die Möglichkeit, Zip-Dateien zu entpacken, aber das Anlegen solcher scheint nicht vorgesehen zu sein. Ein Rechtsklick auf den freien Bereich eines Ordners im Explorer zeigt im Kontextmenü unter Neu... keinen Eintrag für das Erstellen eines Zip-komprimierten Ordners. Tatsächlich gibt es keinerlei Methoden in der Shell, um das zu bewerkstelligen.

Zum Glück ist eine Zip-Datei prinzipiell eine ziemlich einfache Sache, die sich auch über einen Workaround anlegen lässt. Nehmen Sie eine be-liebige Zip-Datei und löschen aus ihr sämtliche Dateien. Das Archiv existiert dann immer noch, hat nun aber eine Größe von nur 23 Byte. Dieses Grundgerüst kann auch über VBA angelegt werden:

Dim arrZip(22) As Byte

arrZip(0) = 80 'P

arrZip(1) = 75 'K

arrZip(2) = 5

arrZip(3) = 6

Sie legen ein Byte-Array an und füllen die ersten vier Elemente mit den Vorgabewerten für eine Zip-Datei. Die restlichen Bytes enthalten Nullen. Das Array muss nun nur noch in eine Datei gespeichert werden:

Open "d:\test\xy.zip" For Binary As #1

Put F, , arrZip

Close #1

Im Testordner wird im Explorer jetzt für die Datei xy.zip die korrekte Bezeichnung Zip-Archiv angezeigt.

Das weitere Vorgehen können Sie sich denken. Die angelegte Zip-Datei wird über das Shell-Objekt als Folder behandelt und über dessen CopyHere-Methode Dateien hinzugefügt. Eine rudimentäre Routine könnte so aussehen:

Dim objZip As Shell32.Folder

Dim objSource As Folder

Set objZip = objShell.NameSpace( _

     "d:\test\xy.zip")

Set objSource = objShell.NameSpace( _

     "c:\windows\")

objZip.CopyHere objSource

Führen Sie diesen Code besser nicht aus! Er kopiert den gesamten Inhalt des Windows-Ordners in das Zip-Archiv xy.zip. Deutlich machen soll er nur, dass die Shell sich hier genauso verhält, wie beim Entpacken. Komplette Ordnerstrukturen können mit nur einer Zeile in ein Archiv verfrachtet werden.

Möchten Sie nur einzelne Dateien in das Archiv übernehmen, so benötigen Sie statt des Forder-Objekts ein FolderItem-Pendant. Eine Erweiterung der Routine käme etwa wie in Listing 1 daher (Ausschnitt). Dort würde die Datei mein.txt des Quellverzeichnisses d:\test\ über die Variable objItem in das Archiv wandern.

Dim objZip As Shell32.Folder

Dim objSource As Folder

Dim objItem As FolderItem

Set objZip = objShell.NameSpace("d:\test\xy.zip")

Set objSource = objShell.NameSpace("d:\test\")

Set objItem = objSource.Items.Item("mein.txt")

objZip.CopyHere objItem

Listing 1: Hinzufügen von Dateien zum Zip-Archiv

Sollten Sie in einer Schleife mehrere Dateien in das Archiv übernehmen, so sollten Sie auch hier ein DoEvents einfügen. Sonst kommt es vor, dass im Archiv anschließend einige Dateien fehlen. Auch sonst gilt, etwa für die Optionen der CopyHere-Methode, dasselbe, wie für die Routine zum Entpacken des Archivs.

Ausführlichere Routinen zum Erzeugen von Zip-Archiven finden Sie in der Beispieldatenbank in Gestalt der beiden Prozeduren CreateZip und CreateZipFromFolder. Der ersten übergeben Sie als Parameter den Pfadnamen der zu erzeugenden Zip-Datei und alle hinzuzufügenden Dateien in einem String-Array, der zweiten stattdessen nur den Pfadnamen eines Ordners, den Sie im Archiv haben wollen. Beide Prozeduren werden vom Formular frmZipper verwendet, das eine Oberfläche für sie bereitstellt.

Zippen über Formular

Bild 2 zeigt Sie das Formular frmZipper der Beispieldatenbank nach dem Öffnen. Im obersten Textfeld geben Sie den Pfadnamen der Zip-Datei an, die Sie erzeugen wollen. Dafür können Sie auch die Schaltfläche rechts daneben verwenden. Sie öffnet einen Datei speichern-Dialog, in dem der Name AB_Test.zip auf das aktuelle Datenbankverzeichnis voreingestellt ist, wie in Bild 3. Diesen Dialog erzeugen Sie über das FileDialog-Objekt von Office. Der hinter der Schaltfläche liegende Code steht in Listing 2. Falls Sie nicht den Abbrechen-Button betätigen, wird der gewählte Dateiname in das Textfeld txtZip übertragen.

Die Datei speichern-Oberfläche des Office-FileDialog-Objekts

Bild 2: Die Datei speichern-Oberfläche des Office-FileDialog-Objekts

Das Formular frmZipper für die Verwaltung von Zip-Archiven

Bild 3: Das Formular frmZipper für die Verwaltung von Zip-Archiven

Private Sub cmdSelect_Click()

     Dim oDlg As Office.FileDialog

     Set oDlg = Application.FileDialog(msoFileDialogSaveAs)

     With oDlg

         .Title = "Dateiname für ZIP-Archiv"

         .InitialView = msoFileDialogViewDetails

         .AllowMultiSelect = False

         .InitialFileName = CurrentProject.Path & "\AB_Test.zip"

         If .Show Then

             Me!txtZip = .SelectedItems(1)

         End If

     End With

End Sub

Listing 2: Routine zum Anzeigen des Speichern unter-Dialogs von Office

Darunter befindet sich ein Listenfeld, welches die dem Zip-Archiv hinzuzufügenden Dateien oder Ordner aufnimmt. Diese öffnen Sie über die beiden Schaltflächen rechts. Der mit D... bezeichnete Button lässt die Auswahl von Dateien zu, der mit O... beschriftete öffnet einen Ordner-auswahldialog (siehe Bild 4). Während für die Dateiauswahl wieder der Office-FileDialog in ähnlicher Form wie im Listing zum Einsatz kommt, wird zur Ordnerauswahl eine Methode BrowseForFolder des Shell-Objekts verwendet – denn die Office-Bibliothek kennt keinen solchen Auswahldialog. Wenn Sie eventuell den häufig benutzten und umfangreichen API-Code zum Anzeigen dieses Dialogs kennen, werden Sie sich vielleicht über den überschaubaren Code in Listing 3 freuen. Der BrowseForFolder-Methode des Shell-Objekts übergibt man ein Fenster-Handle oder 0, den Titel des Dialogs, eine Konstante, die das Erscheinungsbild bestimmt, und schließlich ein Folder-Objekt, das die oberste Ebene für den Verzeichnisbaum einstellt. Hier wird der Desktop angegeben, während in der Abbildung als Root der Computer steht, was durch Angabe des Werts 17, statt "Desktop", für den Shell-NameSpace gelingt.

Shell-Verzeichnisauswahldialog

Bild 4: Shell-Verzeichnisauswahldialog

Wenn Sie eine Übersicht über die Zahlenwerte haben möchten, die für den NameSpace möglich sind, so rufen Sie die Prozedur EnumNameSpaces im Modul mdlZipShell aus dem VBA-Direktfenster heraus auf. Sie gibt alle Werte aus, die auf Ihrem System angegeben werden können, sowie die Bezeichnung und den Pfad des Ordners, die, wie bereits erwähnt, auch virtueller Natur sein können, was an GUIDs für den Pfad deutlich wird.

Bis hierhin wurden nur benötigte Informationen für die Erstellung der Zip-Datei gesammelt. Ein Klick auf Zip-Archiv erzeugen im Formular erst stößt die Prozeduren CreateZip oder CreateZipFromFolder an, je nachdem, ob im Listenfeld ein Ordnerpfad steht, oder eine Liste von Dateien. Im letzteren Fall durchläuft die Prozedur cmdCreateZip_Click alle Einträge des Listenfelds und speichert sie in einem String-Array, welches anschließend der CreateZip-Routine übergeben wird.

Mit dieser hat es eine Besonderheit auf sich. Sie erstellt nicht grundsätzlich eine Zip-Datei neu, sondern nur dann, wenn für den Parameter Overwrite ein True angegeben wird. Ansonsten nutzt Sie ein eventuell bereits vorhandenes Archiv gleichen Namens im Zielverzeichnis. Das bedeutet, dass Sie über diese Routine einem Archiv auch weitere Dateien hinzufügen können. Und weiterhin, dass es somit auch zu Kollisionen kommen kann, wenn im Archiv schon eine gleichnamige Datei existiert. Um diesen Konflikt brauchen Sie sich indessen nicht zu kümmern. Die Shell erkennt das selbst und konfrontiert Sie mit dem Dialog aus Bild 5, in dem Sie aufgefordert werden zu entscheiden, wie weiter verfahren werden soll. Das Auftauchen des Dialogs können Sie übrigens über keinen Optionsparameter der CopyHere-Methode unterbinden.

Das Shell-Objekt fragt vor dem Überschreiben einer Datei im Zip-Archiv sicherheitshalber nach

Bild 5: Das Shell-Objekt fragt vor dem Überschreiben einer Datei im Zip-Archiv sicherheitshalber nach

Das Formular stellt für CreateZip und Overwrite allerdings True ein, wodurch ein eventuell existierendes Archiv ohne Nachfrage gelöscht und neu erstellt wird. Demnach kann es auch nicht zur Anzeige des Konfliktdialogs kommen. Denn dieser hat einen Pferdefuß: Obwohl er noch angezeigt wird, hält die Shell den CopyHere-Vorgang nicht an und VBA fährt asynchron mit der Ausführung des Codes fort. So könnte es dazu kommen, dass die Routine bereits fertig und das Archiv damit angelegt ist, obwohl die Entscheidung über das Ersetzen noch gar nicht gefallen ist. Zwar modifiziert der Shell-Prozess das Archiv dann noch nachträglich, aber eine Kontrolle des Vorgangs über VBA ist nicht möglich.

Nach dem Erzeugen der Zip-Datei öffnet das Formular diese noch zur Kontrolle über die ShellExecute-Methode des Shell-Objekts. Was hier passiert hängt von Ihrem System ab. Die Methode tut das Gleiche, wie ein Doppelklick auf die Datei im Explorer. Haben Sie etwa WinRar oder 7-Zip installiert, so werden diese wohl mit der Aufgabe des Öffnens betraut. Wenn Sie die Module der Beispieldatenbank in eigene Anwendungen einbauen möchten, so können Sie selbstverständlich die Zeilen mit dem ShellExecute auch auskommentieren.

Ist das Archiv erstellt, so ändert sich der Inhalt der Textfelder im Formular wie in Bild 6. In die unteren beiden ist nun gleich der Name der erstellten Zip-Datei eingetragen und zusätzlich noch ein Ordner angegeben, in den Sie die Datei sogleich wieder entpacken könnten. Dabei handelt sich um den Ordner, in dem sich auch die Zip-Datei befindet. Ein Klick auf Zip-Archiv entpacken extrahiert die Dateien im Archiv dann in dieses Verzeichnis. Natürlich kann es auch dabei wieder zur Anzeige des Konfliktdialogs kommen.

Das Formular frmZipper nach dem Erstellen der Archivdatei

Bild 6: Das Formular frmZipper nach dem Erstellen der Archivdatei

Der der Schaltfläche hinterlegte Code ruft lediglich die schon angesprochene Prozedur UnzipFile auf.

Nach dem Entpacken wird allerdings zur Kontrolle noch der Zielordner im Explorer geöffnet. Das lässt sich abermals mit einer unscheinbaren Methode Explore des Shell-Objekts erledigen. Ihr übergibt man einfach als String den Verzeichnispfad:

objShell.Explore CStr(Me!txtOutDir)

Die ausdrückliche Konvertierung des Textfeldinhalts in einen OLE-konformen String über CStr ist übrigens notwendig. Ohne diese kann die Shell mit dem Ausdruck nichts anfangen.

Vielleicht fällt Ihnen bei der Durchsicht der beiden Routinen zum Zippen noch auf, dass diese am Ende die Hilfsfunktion WaitForFileReady aufrufen. Mit ihr soll sichergestellt werden, dass sich die Routinen erst dann beenden, wenn das Archiv wirklich fertiggestellt ist. Da ja CopyHere asynchron arbeitet, kann etwa ein Archivierprogramm die Zip-Datei noch nicht öffnen, solange die Shell es im Zugriff hat. In Listing 4 finden Sie diese Hilfsfunktion dargestellt. Ihr Kern ist die Zeile für den Zugriff auf die Datei per Open-Anweisung. Diese ist mit den Parametern Access Read Write Lock Read Write ausgestattet. Solange die Shell die Datei noch bearbeitet, wird dieser Zugriffsmodus wegen Dateisperrung fehlschlagen, was durch die Fehlerbehandlung abgefangen wird. Die Do-Loop-Schleife wird erst dann verlassen, wenn dieser Fehler nicht mehr auftritt. Damit die Schleife unter Umständen nicht endlos durchläuft, ist zusätzlich eine Timer-Funktion eingebaut, die sie nach 30 Sekunden (wählbar) auf jeden Fall unterbricht.

Function WaitForFileReady(ByVal FileName As String, Optional TimeOut As Single = 30)

     Dim F As Integer

     Dim T As Single

     Dim bOK As Boolean

     

     F = FreeFile

     T = VBA.Timer

     On Error Resume Next

     Do

         Sleep 50

         DoEvents

         Err.Clear

         Open FileName For Binary Access Read Write Lock Read Write As F

         If Err.Number = 0 Then bOK = True

         Close F

     Loop Until ((VBA.Timer - T) >= TimeOut) Or bOK

     WaitForFileReady = (VBA.Timer - T) < TimeOut

End Function

Listing 4: Diese Funktion wird erst beendet, wenn die übergebene Datei frei ist

Bliebe noch die Schaltfläche mit der Aufschrift Zip erzeugen und in Anlage speichern des Formulars. Im Prinzip löst diese nichts anderes aus, als die darüber liegende. Die Zip-Datei wird auf gleiche Weise erstellt. Nur wird diese anschließend als Anlagedatensatz in die Tabelle MSysResources aufgenommen, wenn Sie mindestens Access 2010 verwenden, und wieder gelöscht. Diese Systemtabelle ist in jeder unter Access 2010 ff. erstellten Datenbank mit am Start und an sich für die Aufnahme von Bildern für Formulare gedacht. Da sie nicht schreibgeschützt ist, lässt sie sich genauso gut für andere Zwecke missbrauchen.

Der Code für das Speichern der Zip-Datei im Anlagefeld über DAO soll hier nicht weiter interessieren. Sie finden ähnlichen Code auch an anderer Stelle. Statt der MSysResources können Sie natürlich auch eine eigene Tabelle mit einem Anlagefeld verwenden. Ändern Sie dann die Routine cmdZipAttachment_Click entsprechend ab.

Datensicherung zippen

In der letzten Ausgabe 01/2015 von Access [basics] ging es im Beitrag Datenbanken und Objekte sichern unter anderem darum, die einzelnen Objekte einer Datenbank, also Formulare, Berichte, Module, et cetera, über die versteckte Access-Methode SaveAsText in einzelne Textdateien zu sichern, aus denen sie sich später wiederherstellen lassen. Bei umfangreichen Datenbanken haben Sie dann nach Durchlaufen der Sicherungsroutine einen Haufen Textdateien erzeugt. Die dort angestellte Überlegung, diese Dateien stattdessen platzsparend in einem Zip-Archiv unterbringen, kann nun mit dem Rüstzeug dieses Beitrags realisiert werden.

Das Modul mdlSaveObjects ist in dieser Beispieldatenbank fast identisch zu der des erwähnten Beitrags. Nur heißt die Routine zum Sichern der Objekte hier ObjektSicherungZip, die zum Einlesen SicherungWiederherstellenZip.

In der Sicherungsroutine werden die gefragten Datenbankobjekte als Textdateien exportiert, aber deren Dateinamen zusätzlich in ein String-Array arrFiles aufgenommen. Das ist der einzige Unterschied. Das Array wird am Schluss für die schon bekannte Prozedur CreateZip benötigt, die aus den Dateien ein Archiv braut. Ist dieses erstellt, werden die Textdateien in einer Schleife per Kill-Anweisung wieder gelöscht.

Beim Test der Routine kam es allerdings zu Fehlern. Ein oder mehrere der Fehlerdialoge aus Bild 7 poppten auf. Das sind Meldungen der Shell auf die Anweisung CopyHere hin, die von VBA leider nicht abgefangen werden können. Wieder einmal ist das der asynchronen Verarbeitung geschuldet. Das Verhalten konnte erst korrigiert werden, nachdem in die Schleife zum Hinzufügen von Dateien zum Archiv in der Funktion CreateZip eine Verzögerung von 100 ms mit der API-Anweisung Sleep eingebaut wurde. Es hängt von der Performance Ihres Systems ab, ob Sie diese Zeile benötigen und wie groß die Verzögerung sein muss, damit die Fehlermeldungen nicht mehr auftreten.

Fehlermeldung der Shell beim Hinzufügen von Dateien und dabei verzögerter Aktualisierung des Zip-Archivs

Bild 7: Fehlermeldung der Shell beim Hinzufügen von Dateien und dabei verzögerter Aktualisierung des Zip-Archivs

Umgekehrt geht die Wiederherstellen-Routine vor. Zunächst entpackt sie das als Parameter übergebene Sicherungsarchiv ZipFile mit UnzipFile in ein neu angelegtes temporäres Verzeichnis \temp unterhalb des Anwendungsverzeichnisses. Aus dem Archiv werden dann die einzelnen Textdateien mit LoadFromText gegebenenfalls wieder in Datenbank-objekte umgewandelt, wobei die Routine für jedes einzelne Objekt gesondert nachfragt. Ob wiederhergestellt, oder nicht, die Textdatei wird im Anschluss sogleich gelöscht. Am Ende der Schleife ist das temporäre Verzeichnis damit leer und kann mit der RmDir-Anweisung selbst gelöscht werden – sie funktioniert nur, wenn das angegebene Verzeichnis keinen Inhalt hat.

Gegenüber der ursprünglichen Version hat sich dieses Sicherungsmodul nur um wenige Zeilen vergrößert, da hauptsächlich die Zip-Funktionen des Moduls mdlZipShell angesprochen werden.