Access macht Installationsarbeit

15.09.2006 von André Minhorst
Manche Access-Anwendungen benötigen zusätzliche Tools oder Dateien für ihre Arbeit. Die liefert man normalerweise entweder separat mit oder integriert sie in ein Setup. Es gibt aber auch noch einen pfiffigeren Weg: Speichern Sie die notwendigen Dateien doch einfach in der Datenbank und lassen Sie Access die Installation übernehmen!

Die Weitergabe von Access-Anwendungen verläuft so lange unproblematisch, wie keine Installation weiterer Software oder Dateien erforderlich wird. Wenn die Umstände dies dennoch erfordern, baut man entweder auf den Anwender und trägt diesem auf, wo und wie er die fehlenden Elemente unterbringt, oder man investiert ein wenig mehr Arbeit und erzeugt ein Setup-Programm, dass alle Dateien automatisch an den passenden Stellen installiert.

In manchen Fällen lässt sich dies jedoch noch wesentlich eleganter erledigen, zum Beispiel wenn es nur um das Speichern zusätzlicher Dateien geht oder wenn man den Installationsvorgang benötigter Anwendungen per VBA bewerkstelligen kann.

Datei im OLE-Feld

Und so einfach ist es: Man speichert die benötigten Dateien in der Datenbank selbst– und zwar in einem OLE-Feld einer eigens dafür angelegten Tabelle. Diese Tabelle hat einen recht einfachen Aufbau: Sie besteht aus den drei Feldern DateiID, Dateibezeichnung und Datei (Bild 1).

Bild 1: Notwendige Verweise.
Bild 2: Die UserForm zur Auswahl von Datenbank und Tabelle.

Das Feld Datei hat den Typ OLE-Objekt und speichert die jeweilige Datei, das Feld Dateibezeichnung enthält einen Text, der die Datei identifiziert. Dabei kann es sich um den Dateinamen oder einen anderen Begriff handeln – das können Sie selbst entscheiden. Wichtig ist nur, dass dieses Feld einen eindeutigen Wert enthält. Um sicherzugehen, legen Sie für dieses Feld in der Entwurfsansicht der Tabelle über Ansicht/Indizes einen eindeutigen Index für dieses Feld an (Bild 2).

Rein in die Tabelle

Fehlen noch zwei Funktionen, mit denen man eine Datei aus dem Dateisystem in der Tabelle und aus der Tabelle wieder im Dateisystem speichern kann.

Das Importieren in die Datenbank übernimmt die Funktion DateiInTabelle (Listing 1).

Const TABELLE As String = "tblDateien"
Public Function DateiInTabelle(strDateibezeichnung As String, strDatei As String)
As Long
Dim cnn As ADODB.Connection
Dim rst As New ADODB.Recordset
Dim lngImportdateiID As Long
Dim Buffer() As Byte
Dim lngDateigroesse As Long
On Error GoTo DateiInTabelle_Err
Set cnn = CurrentProject.Connection
rst.Open "SELECT Datei, Dateibezeichnung FROM " & TABELLE & " WHERE Dateibezeichnung
= '" & strDateibezeichnung & "'", cnn, adOpenDynamic, adLockOptimistic
If rst.EOF Then
rst.AddNew
End If
lngImportdateiID = FreeFile
If Dir(strDatei) = "" Then
MsgBox "Die Datei '" & strDatei & "' existiert nicht."
Exit Function
End If
Open strDatei For Binary Access Read As lngImportdateiID
lngDateigroesse = FileLen(strDatei)
ReDim Buffer(lngDateigroesse)
Get lngImportdateiID, , Buffer
rst!Datei = Null
rst!Dateibezeichnung = strDateibezeichnung
rst!Datei.AppendChunk Buffer
rst.Update
Close lngImportdateiID
DateiInTabelle = True
DateiInTabelle_Exit:
rst.Close
Set rst = Nothing
Set cnn = Nothing
Exit Function
DateiInTabelle_Err:
DateiInTabelle = Err.Number
Debug.Print Err.Description
Resume DateiInTabelle_Exit
End Function

Die dort enthaltene Konstante TABELLE speichert den Namen der Zieltabelle, die in beiden Funktionen benötigt wird:

Const TABELLE As String = "tblDateien"

Die Funktion hat zwei Eingangsparameter:

Datensatz anlegen

Nach dem Deklarationsteil öffnet die Funktion zunächst eine Datensatzgruppe auf Basis der in der Konstanten TABELLE gespeicherten Tabelle, die entweder keinen Datensatz (falls die Datei das erste Mal importiert wird) oder den Datensatz mit der angegebenen Dateibezeichnung enthält (in einer Zeile):

rst.Open "SELECT Datei, Dateibezeichnung FROM " & TABELLE
& " WHERE Dateibezeichnung = '" & strDateibezeichnung
& "'", cnn, adOpenDynamic, adLockOptimistic

Wenn der Datensatz noch nicht vorhanden ist, legt die Funktionen einen passenden Datensatz an:

If rst.EOF Then
rst.AddNew
End If

Anschließend liest die Funktion die Datei zunächst in ein Byte-Array namens Buffer ein. Dazu weist sie der Variablen lngImportdateiID mit der Funktion FreeFile zunächst eine freie Nummer für den Dateizugriff zu. Nach der Prüfung, ob die angegebene Datei vorhanden ist, öffnet die Funktion sie für lesenden Zugriff:

Open strDatei For Binary Access Read As lngImportdateiID

Dann bereitet die Funktion das Byte-Array für das Aufnehmen der Datei vor, indem sie die Größe der Datei ermittelt und das Byte-Array entsprechend dimensioniert:

lngDateigroesse = FileLen(strDatei)
ReDim Buffer(lngDateigroesse)

Das Einlesen der Datei in das Byte-Array erfolgt schließlich mit der Get-Anweisung:

GetlngImportdateiID, , Buffer

Fehlt noch das Speichern in der Tabelle. Dazu leert die Funktion zunächst das Feld Datei, fügt die Dateibezeichnung in das passende Feld ein und verwendet die AppendChunk-Methode, um den Inhalt des Byte-Arrays Buffer in das OLE-Feld Datei der Tabelle zu schreiben. Die Update-Methode schließlich speichert die Änderungen:

rst!Datei = Null
rst!Dateibezeichnung = strDateibezeichnung
rst!Datei.AppendChunk Buffer
rst.Update

Der folgende Aufruf der Funktion speichert die Datei c:\Test.txt in der Tabelle tblDateien und schreibt gleichzeitig den Wert Testdatei in das Feld Dateibezeichnung:

Debug.Print DateiInTabelle("Testdatei",
"c:\test.txt")

Die Debug.Print-Anweisung sorgt dafür, dass der Rückgabewert der Funktion im Direktfenster ausgegeben wird. Dieser ist entweder -1, wenn keine Fehler auftreten, oder eine entsprechende Fehlernummer.

Bild 3: Die Tabelle tblDateien mit einer ersten Testdatei.

Die Tabelle tblDateien sieht nach dem Einfügen einer Datei wie in Bild 3 aus.

Raus aus der Tabelle

Um zu prüfen, ob der Import der Datei in die Tabelle auch funktioniert hat, gibt es wohl nur einen einzigen Weg: Man exportiert die Datei und prüft, ob sie mit der Originaldatei übereinstimmt. Da trifft es sich gut, dass Sie ohnehin eine Funktion zum Exportieren von Dateien aus einem OLE-Feld in das Dateisystem benötigen. Listing 2 zeigt die komplette Funktion TabelleIn- Datei im Überblick.

Public Function TabelleInDatei(strDateibezeichnung As String, strDatei As String)
As Long
Dim cnn As ADODB.Connection
Dim rst As New ADODB.Recordset
Dim lngExportdateiID As Long
Dim Buffer() As Byte
Dim lngDateigroesse As Long
On Error GoTo TabelleInDatei_Err
Set cnn = CurrentProject.Connection
rst.Open "SELECT Datei FROM " & TABELLE & " WHERE Dateibezeichnung = '"
& strDateibezeichnung & "'", cnn, adOpenDynamic, adLockOptimistic
lngExportdateiID = FreeFile
lngDateigroesse = Nz(LenB(rst!Datei), 0)
If lngDateigroesse > 0 Then
ReDim Buffer(lngDateigroesse)
Open strDatei For Binary Access Write As lngExportdateiID
Buffer = rst!Datei.GetChunk(lngDateigroesse)
Put lngExportdateiID, , Buffer
Close lngExportdateiID
End If
TabelleInDatei = True
TabelleInDatei_Exit:
rst.Close
Set rst = Nothing
Set cnn = Nothing
Exit Function
TabelleInDatei_Err:
TabelleInDatei = Err.Number
Debug.Print Err.Description
Resume TabelleInDatei_Exit
End Function

Die Funktion hat genau die gleichen Parameter wie die Funktion DateiInTabelle, allerdings dienen sie einem anderen Zweck: Der Parameter strDateibezeichnung legt nicht fest, unter welcher Dateibezeichnung die Datei gespeichert werden soll, sondern ermittelt den Datensatz mit der zu exportierenden Datei. Und der Parameter strDatei gibt an, unter welchem Namen die in dem OLE-Feld der Tabelle enthaltene Datei im Dateisystem gespeichert werden soll.

Der Ablauf der Funktion ähnelt ebenfalls dem der Funktion zum Importieren der Datei – die Daten gehen lediglich den umgekehrten Weg. Daher an dieser Stelle nur eine Kurzbeschreibung: Die Routine öffnet eine Datensatzgruppe mit der angegebenen Dateibezeichnung und erzeugt eine Datei, die anschließend mit dem Inhalt des OLE-Feldes gefüllt werden soll. Das Byte- Array Buffer wird in diesem Fall mit dem Inhalt des OLE-Feldes gefüttert. Die Put-Anweisung schreibt den Inhalt von Buffer in die zuvor geöffnete Datei – fertig!

Mit folgendem Aufruf schreiben Sie die zuvor in der Tabelle gespeicherte Datei in eine neue Datei namens TestExport.txt:

Debug.Print TabelleInDatei("Testdatei", "c:\TestExport. txt")

Customizing mit „einfachen“ Dateien

Angenommen, die Datenbankanwendung benötigt zum Betrieb irgendwelche Dateien wie Bilddateien oder Konfigurationsdateien, die in einem bestimmten Verzeichnis auf der Festplatte gespeichert sind. Dann haben Sie mit den beiden obigen Funktionen bereits gute Voraussetzungen geschaffen, diese auch bei der Weitergabe an andere Benutzer automatisch bereitzustellen. Sie müssen nur noch dafür sorgen, dass die Datenbank beim Start prüft, ob die benötigten Dateien schon im Dateisystem vorhanden sind, und sie gegebenenfalls dort speichert.

Als Verzeichnis für solche Dateien bietet sich dasjenige an, in dem sich auch die Datenbankdatei (also die .mdb-Datei) befindet. Erstens kann man diesen Pfad per CurrentProject.Path dynamisch ermitteln, und zweitens hat die Datenbank in der Regel Schreibrechte für dieses Verzeichnis.

Die Funktion, die das Vorhandensein der benötigten Dateien prüft, muss beim Start der Datenbank aufgerufen werden. Dafür hat man mehrere Möglichkeiten:

Die meisten Anwendungen dürften dem Anwender zum Start irgendein Formular präsentieren, daher folgt nun die Beschreibung der passenden Vorgehensweise – der Einfachheit halber mit einem leeren Formular namens frmStart.

Damit dieses Formular beim Öffnen der Datenbankanwendung ebenfalls geöffnet wird, stellen Sie den Namen des Formulars über Extras/Start in der Option Formular/Seite anzeigen ein (Bild 4).

Bild 4: Einstellen eines beim Start der Anwendung zu öffnenden Formulars.

Damit die passenden Dateien beim Öffnen der Datenbank und damit des Formulars frmStart in das Datenbankverzeichnis geschrieben werden, fügen Sie zu dem Formular eine Prozedur hinzu, die durch das Ereignis Beim Öffnen ausgelöst wird (Listing 3). Wenn die Datei nicht im Datenbankverzeichnis vorhanden ist, ruft die Ereignisprozedur die Funktion TabelleInDatei auf, die für das Speichern der in der Tabelle tblDateien enthaltenen Datei sorgt.

Private Sub Form_Open(Cancel As Integer)
Dim strDatenbankpfad As String
Dim strDatei As String
strDatenbankpfad = CurrentProject.Path
strDatei = "Test.txt"
If Dir(strDatenbankpfad & "\" & strDatei) = "" Then
TabelleInDatei "Testdatei", strDatenbankpfad & "\" & strDatei
End If
End Sub

Customizing mit Tücken

Leider reicht nicht bei allen Dateien das einfache Speichern im Dateisystem aus. Wenn Sie beispielsweise ein ActiveX-Steuerelement auf diese Weise weitergeben möchten, müssen Sie zusätzlich für dessen Registrierung sorgen.

Ein gutes Beispiel ist das RTF2-Steuerelement von Stephen Lebans, das Sie in anderen Artikeln bereits kennen gelernt haben. Man muss die passende DLL oder .ocx-Datei zunächst mit der Anwendung RegSvr32.exe registrieren. Das ist allerdings auch nicht besonders schwierig: Wenn Sie eine Prozedur wie aus Listing 3 verwenden, um die benötigten Dateien im Dateisystem zu speichern, können Sie dort mit wenigen weiteren Befehlen den für DLLs notwendigen Registriervorgang durchführen.

Ganz so trivial ist das allerdings nicht, denn die Weitergabe, Registrierung und Verwendung von ActiveX-Steuerelementen ist selten ohne Probleme möglich. So führten Experimente beispielsweise dazu, dass beim Speichern der RTF2.ocx-Datei im gleichen Verzeichnis wie die Datenbank und anschließendes Registrieren nicht zur erwarteten Funktion: Die Formulare mit dem RTF2-Steuerelement versagten mit der Meldung „In diesem Steuerelement befindet sich kein Objekt“ den Dienst

Behoben werden konnte dieses Problem durch Installation der RTF2.ocx-Datei genau in dem gleichen Verzeichnis, in dem es sich auch bei der Erstellung der Originaldatenbank und damit beim Hinzufügen des RTF2-Steuerelements zu den Formularen befand (in diesem Fall c:\Programmeebans holdings 1999 ltd\rtf2\ rtf2.ocx).

Die Routine aus Listing 4 versucht, die gleiche Verzeichnisstruktur auf laufwerk c:\ herzustellen und die Datei rtf2.ocx dort zu speichern. Gelingt dies, funktionieren die Formulare mit dem Steuerelement; andernfalls erhält man eine Fehlermeldung beim öffnen der Formulare.

private sub form_open(cancel as integer)
dim strdatenbankpfad as string
dim strdatei as string
dim strdateibezeichnung as string
strdatenbankpfad = "c:\programmeebans holdings 1999 ltd\rtf2"
strdatei = "rtf2.ocx"
strdateibezeichnung = "rtf2.ocx"
mkdir "c:\programmeebans holdings 1999 ltd"
mkdir "c:\programmeebans holdings 1999 ltd\rtf2"
if dir(strdatenbankpfad & "\" & strdatei) = "" then
tabelleindatei strdateibezeichnung, strdatenbankpfad & "\" & strdatei
shell "regsvr32.exe /s " & """" & strdatenbankpfad & "\" & strdatei & """"
end if
end sub

Probleme könnte es etwa geben, wenn der Zugriff auf laufwerk c:\ und die enthaltenen Verzeichnisse versagt wird. Hundertprozentig sicher ist dieses Verfahren also nicht; man sollte die Routine zur Sicherheit noch mit einer umfassenden Fehlerbehandlung ausstatten und dem Benutzer gegebenenfalls mitteilen, wie er das Steuerelement installieren muss.

Die genannten Probleme beschränken sich übrigens nicht auf das RTF2-Steuerelement, bei anderen ActiveX-Steuerelementen dürften sie ebenfalls auftreten.

Zusammenfassung

Die beschriebenen Funktionen arbeiten zumindest dann einwandfrei, wenn die von der Datenbank benötigten Dateien nicht registriert werden müssen. ist eine Registrierung notwendig, können Schwierigkeiten auftreten; man sollte solche Anwendungen also vor der Weitergabe so gut wie möglich testen und den Benutzer auf eventuelle Probleme hinweisen.

Funktioniert die Weitergabe des .ocx-Steuerelements wie beschrieben, kann man dem Benutzer jedoch eine menge Arbeit ersparen.