So, jetzt habe ich endgültig die Nase voll. Schon wieder ist es passiert: Mit letzter Kraft eine wichtige Routine zu Ende programmiert und jetzt nur noch Access schließen, den Rechner runterfahren und ab ins Bett. Und am nächsten Morgen die Ernüchterung: Die änderungen sind nicht mehr da! Da habe ich wohl mal wieder mit “Nein” auf die Frage geantwortet, ob ich die geänderten Objekte speichern möchte … Aber damit ist jetzt Schluss: Ich baue mir ein Tool, das regelmäßig meine Formulare, Berichte und Module speichert – und dabei auch noch alte Versionen aufbewahrt.
Es ist ein Kreuz: Es braucht unter Access nur ein wenig Ungeschicklichkeit, um eine Menge Arbeit zu vernichten. Wenn Sie mal ein umfangreiches Modul programmiert und dann die Datenbank oder das Modul geschlossen haben, ohne den neuen Stand zu speichern, wissen Sie, wovon ich spreche. Zwischenspeichern
Wenn man gerade im Programmierrausch ist und eine Zeile nach der anderen schreibt, denkt man nicht unbedingt an das Zwischenspeichern, auch wenn es doch so einfach wäre, zwischendurch einfach mal Strg + S zu drücken.
Wie kommt man dem bei Termine in Outlook setzen, die einen alle 15 Minuten daran erinnern, mal durchzupusten und den aktuellen Stand zu speichern
So weit kommt es noch – wir sind schließlich Programmierer und die helfen sich selbst.
Wenn man sich aber schon an die Programmierung eines Tools macht, das regelmäßig für das Speichern geänderter Objekte sorgt, was sollte dieses Tool praktischerweise noch erledigen
Das automatische Speichern ist nicht unbedingt immer eine perfekte Lösung: Immerhin zerstört man damit ja auch die Möglichkeit, zum Stand der letzten Speicherung zurückzukehren – und damit zur möglicherweise letzten funktionstüchtigen Version.
Unter Access und speziell im VBA-Editor ist das praktisch eine Lebensversicherung, wenn man mal größere änderungen durchführt und diese dann doch nicht übernehmen möchte – man speichert sie dann einfach nicht.
Der VBA-Editor bietet nämlich auch in der aktuellen Version nur die Möglichkeit, 20 Schritte rückgängig zu machen – insgesamt, nicht pro Modul!
Es scheint also eine gute Idee, die gespeicherten Zwischenstände zu sichern. Aber wohin damit – und wie
Bevor wir uns diesen technischen Feinheiten zuwenden, halten wir fest: Access soll den Stand der Entwicklung in regelmäßigen Abständen, zumindest aber beim Schließen der Anwendung, sichern.
Auf diese Weise sorgen Sie nicht nur dafür, dass änderungen nicht verloren gehen, sondern können auch noch auf frühere Stände zurückgreifen.
Anwendung
Schauen wir uns an, wie das Ganze funktioniert: Nachdem Sie die Elemente der Anwendung wie im Kasten “Im Soforteinsatz” zu einer Datenbank hinzugefügt und dafür gesorgt haben, dass das Formular frmAccVersion angezeigt wird, können Sie schon loslegen.
Das Formular sehen Sie in Bild 1. Es enthält ein Listenfeld zur Anzeige der Module und ein weiteres zur Auflistung der Versionen zum aktuell im ersten Listenfeld markierten Modul. Zu den Modulen gehören in dem Fall Formulare, Berichte, Standardmodule und Klassenmodule, aber auch Abfragen.
Bild 1: Das Formular zur Verwaltung der Versionen
Beim ersten Öffnen des Formulars sind diese Listenfelder natürlich noch leer, was sich aber nach einem Klick auf die Schaltfläche Neue Version aller geänderten Objekte speichern ändert. Die Versionsverwaltung durchläuft dann alle Objekte der genannten Typen und legt zunächst das Modul im ersten Listenfeld und dann die Version im zweiten Listenfeld an.
Damit hat man schon einmal einen Zwischenstand, mit dem man sich ganz unbeschwert an den Code heranwagen kann, denn mit der Schaltfläche Bestehendes Objekt mit dieser Version überschreiben können Sie sich ganz leicht eine ältere Version zurückholen.
Falls Sie sich nicht sicher sind, welche Version die richtige ist, stellen Sie einfach die Version wieder her, hinter der Sie die richtige vermuten – und zwar mit der Schaltfläche Objekt wiederherstellen als <Name>_yyyymmdd_hhnnss, die bestehende Objekte nicht überschreibt, sondern unter einem Namen anlegt, der den Zeitpunkt der Speicherung enthält.
Die Schaltfläche Version löschen dient schließlich dazu, eine oder mehrere Versionen zu entsorgen – wenn man einen Versionsstand erreicht hat, bei dem alles funktioniert, braucht man selbstverständlich keine Altlasten mehr mit sich herumzutragen.
Schließlich gibt es im unteren Bereich noch die Steuerelemente zum Festlegen der regelmäßigen Sicherung von Versionsständen. Mit dem Kontrollkästchen Geänderte Objekte alle x Minuten als neue Version sichern aktivieren Sie diese Funktion, in das Textfeld Intervall [min] tragen Sie ein, in welchem Abstand die Sicherung erfolgen soll.
Schließen ohne zu speichern
Wenn der Benutzer die Datenbankanwendung beendet, schließt er auch das Formular frmAccVersion. Dieses sorgt dann mit seiner letzten Aktion noch einmal dafür, dass nicht gespeicherte Module in den Versionierungstabellen gespeichert werden.
Wenn Sie also ein Modul namens mdlTest bearbeiten, die änderungen nicht speichern und dann die Anwendung schließen, speichert frmAccVersion trotzdem die aktuelle Version des Moduls.
Beim nächsten Öffnen der Datenbank erscheint eine Meldung, die den Benutzer darauf hinweist, dass es Module oder Objekte gibt, die vor dem letzten Schließen geändert, aber nicht gespeichert wurden (siehe Bild 2).
Bild 2: Diese Meldung macht den Benutzer auf ungespeicherte Elemente aufmerksam.
Im Anschluss erscheint das Formular frmAccVersion und zeigt das nicht gespeicherte Objekt unter dem Originalobjekt an – versehen mit einer GUID. Mit einem Klick auf die Schaltfläche Objekte wiederherstellen als <Name>_yyyymmdd_hhnnss erzeugen Sie dann eine Kopie des Objekts mit dem ungespeicherten Stand beim letzten Schließen der Anwendung (siehe Bild 3).
Bild 3: Nicht gespeicherte änderungen lassen sich mit AccVersion zurückholen.
Nebenwirkungen
Für manche Objekte ist es wichtig, diese vor dem Versionieren zu speichern. Dies gilt für Abfragen, Berichte und Formulare. Standard- und Klassenmodule stehen ohne weitere Schritte zum Versionieren bereit.
Das ist allerdings ein Problem, denn dieses Tool soll ja gerade auch dann, wenn der Benutzer es versäumt, ein Objekt vor dem Beenden der Datenbank zu speichern, geradestehen und die seit dem letzten Speichern getätigten änderungen sichern.
Ganz einfach ist das allerdings nicht, ohne den Benutzer nicht doch noch zu einem Klick auf die Ja-Schaltfläche der Speichern-Frage zu nötigen – warum, erfahren Sie weiter unten.
Datenmodell
Die Anwendung braucht genau zwei Tabellen: Eine zum Speichern der Informationen zu den Modulen selbst und eine für die einzelnen Versionen der Module. Bild 4 zeigt die Tabellen und ihre Beziehung.
Bild 4: Datenmodell für die Verwaltung von Modulen und deren Versionen
Die Tabelle tblModule enthält neben dem Feld Modulname mit der Bezeichnung des Moduls nur noch ein Feld namens Modultyp. Dieses enthält einen Zahlenwert, der einer der Konstanten der VBA-Enumeration acObjectType entspricht. In diesem Zusammenhang kommen die folgenden Typen vor (Zahlenwert in Klammern):
- acQuery (1)
- acForm (2)
- acReport (3)
- acModule (5)
Unter acModule fallen dabei sowohl Klassen- als auch Standardmodule.
Die zweite Tabelle heißt tblVersionen und ist über ein Fremdschlüsselfeld namens ModulID mit der Tabelle tblModule verknüpft. Auf diese Weise können die Datensätze dieser Tabelle den Modulen aus der Tabelle tblModule zugeordnet werden.
Die Tabelle enthält weitere Felder zum Speichern des eigentlichen Inhalts des Moduls (Modulinhalt) und des Speicherdatums (Speicherdatum).
Das Feld Modulinhalt ist dabei als Memofeld ausgelegt, weil es sehr lange Texte erfassen können muss.
Datenmodell automatisch erstellen
Der Benutzer soll so wenige Objekte wie möglich in seine Anwendung importieren müssen, um mit der Versionsverwaltung arbeiten zu können. Daher soll das Hauptformular von AccVersion – so soll das Tool heißen – beim Laden prüfen, ob die benötigten Tabellen schon vorhanden sind und diese anderenfalls anlegen. Ist das der Fall, kommt die Prozedur aus Listing 1 zum Einsatz.
Listing 1: Anlegen der für die Versionsverwaltung benötigten Tabellen per SQL
Public Sub TabellenAnlegen() Dim cnn As Object Set cnn = CurrentProject.Connection cnn.Execute "CREATE TABLE tblModule(ModulID COUNTER CONSTRAINT PK PRIMARY KEY, " _ & "Modulname VARCHAR(255), Modultyp INT)", dbFailOnError cnn.Execute "CREATE TABLE tblVersionen(VersionID COUNTER CONSTRAINT PK PRIMARY KEY, " _ & "Modulinhalt LONGTEXT, Speicherdatum DATETIME, ModulID INT)", dbFailOnError cnn.Execute "ALTER TABLE tblVersionen ADD CONSTRAINT FKModulID FOREIGN KEY (ModulID) " _ & "REFERENCES tblModule ON DELETE CASCADE ON UPDATE CASCADE", dbFailOnError Application.RefreshDatabaseWindow End Sub
Diese Routine erzeugt zunächst die Tabelle tblModule, dann tblVersionen und schließlich das Fremdschlüsselfeld mit referentieller Integrität sowie Lösch- und Aktualisierungsweitergabe.
Tabelleninhalt
Der Inhalt der meisten Felder der Tabellen des Datenmodells bedarf keiner Erklärung, das Feld Modulinhalt aber sehr wohl. Wer sich ein Modul (sei es ein einfaches Klassenmodul, ein Standardmodul oder auch das Klassenmodul eines Formulars oder Berichts) einmal angesehen hat, sieht zunächst nur den darin enthaltenen Code.
Nur diesen zu speichern, würde zwar schon weiterhelfen, denn somit könnten Sie zumindest den Verlust umfangreicher Codeänderungen verhindern.
Wer aber einmal ein Formular oder einen Bericht mit einer Menge Steuerelemente und entsprechenden Anpassungen der Eigenschaften gebaut hat und später alles noch einmal neu bauen musste, weil er im falschen Moment die falsche Schaltfläche angeklickt hat, wird sich über Folgendes freuen: Auch die Definition sämtlicher Eigenschaften von Formularen und Berichten und den darin enthaltenen Steuerelementen liegt in Textform vor und Sie können sogar darauf zugreifen!
Nun ist es nicht so, als ob diese Informationen offen herumliegen, aber über einen Zwischenschritt können Sie die komplette Definition von Objekten wie Formularen oder Berichten im Feld Modulinhalt der Tabelle tblVersionen speichern.
Dies macht die verborgene Methode SaveAsText von Access.Application möglich, die drei Parameter erwartet:
- ObjectType: Typ des Objekts, entspricht einem Wert der weiter oben schon erwähnten VBA-Enumeration acObjectType
- ObjectName: Name des Objekts, also beispielsweise frmBeispielformular
- Filename: Speicherort- und Name, also etwa c:\frmBeispielformular.txt
Wenn Sie mit dieser Methode ein noch leeres Formular als Textdatei speichern und es anschließend in einem Texteditor ansehen, sieht das beispielsweise wie in Bild 5 aus.
Bild 5: Die Definition eines leeren Formulars als Textdatei
Versionieren von Access-Objekten
Der schwierigste Teil von AccVersion ist das eigentliche Versionieren der Module.
Dabei durchläuft eine Routine alle Elemente der Anwendung, die einem der möglichen Typen entsprechen und unterzieht diese einer ganzen Reihe von Methoden.
Wie aufwendig das ist, verdeutlicht ein Blick auf das Flussdiagramm aus Bild 6, dessen einzelne Schritte wir in den nächsten Abschnitten genau beleuchten werden.
Bild 6: Ablauf beim Versionieren der Objekte einer Access-Anwendung
Der Hauptteil dieses Flussdiagramms läuft in der Routine ModuleSpeichern ab (s. Listing 2). Von dort aus werden noch eine Reihe weiterer Routinen aufgerufen. Den Rahmen bildet eine Do While-Schleife, die alle Datensätze der Tabelle MSysObjects durchläuft, die speziellen Anforderungen genügen.
Listing 2: Diese Routine steuert das Versionieren der Module.
Public Function ModuleSpeichern(bolZwischenspeichern As Boolean) As Boolean ... Set db = CurrentDb strSQL = "SELECT Name, Type FROM MSysObjects WHERE Type=1 Or Type=5 Or Type=8 Or Type=-32761 " _ & "Or Type=-32764 Or Type=-32768;" Set rst = db.OpenRecordset(strSQL, dbOpenDynaset) FortschrittAnzeigen Do While Not rst.EOF If Not (Left(rst!Name, 2) = "__" Or Left(rst!Name, 1) = "~") Then strModulname = rst!Name Select Case rst!Type Case -32761 'Modul lngObjecttype = acModule Case -32764 'Report lngObjecttype = acReport Case -32768 'Form lngObjecttype = acForm Case 5 lngObjecttype = acQuery Case Else 'andere Objekte strModulname = "" End Select If Not Len(strModulname) = 0 Then FortschrittAktualisieren "Versioniere " & rst!Name, _ 100 * rst.AbsolutePosition / rst.RecordCount strModulname_Original = strModulname If SaveModule(strModulname, lngObjecttype, bolZwischenspeichern) = True Then strModulinhalt = LoadModule If MustSaveObject(strModulname) Then SaveObject strModulname, lngObjecttype End If lngModulID = DLookup("ModulID", "tblModule", "Modulname='" & strModulname & "'") If MustSaveVersion(lngModulID, strModulinhalt) Then MakeNewVersion lngModulID, strModulinhalt End If TemporaereObjekteLoeschen End If End If End If rst.MoveNext Loop FortschrittBeenden End Function
Diese Tabelle enthält Informationen über alle Objekte der Datenbank – Tabellen, Abfragen, Formulare, Berichte, Module, Indizes, in Formulare, Berichte und Steuerelemente eingebettete Abfragen und mehr. Diese Tabelle interessiert uns, weil sie zuverlässig den Namen und den Typ der gewünschten Access-Objekte liefert. Die für strSQL angegebenen Kriterien entsprechen je einer Objektart, allerdings sind die Zahlenwerte nicht mit denen der VBA-Enumeration acObjectType identisch.
Nachfolgend wird durch Zahlen in Klammern auf die einzelnen Stationen des Flussdiagramms verwiesen.
Der obere Teil der Routine aus Listing 2 beschäftigt sich mit dem Ermitteln der betroffenen Objekte und enthält außerdem den Start der Do While-Schleife, die alle Datensätze der oben erwähnten Abfrage durchläuft (1). Direkt im Anschluss prüft eine If…Then-Bedingung einige weitere Eigenschaften, so etwa, ob der Objektname mit zwei Unterstrichen (__) oder mit der Tilde (~) beginnt.
Ersteres ist eine Kennzeichnung für temporäre und zum Betrachten gedachte Objekte (mehr dazu weiter unten), Letzteres das erste Zeichen spezieller und hier nicht berücksichtigter Access-Objekte wie etwa die in Daten- und Datensatzherkünften gespeicherten Abfragen (2).
Der Objektname wird dann in der Variablen strModulname und der Objekttyp in lngObjectType gespeichert, wobei Letzteres in einer Select Case-Anweisung geschieht, welche die Objekt-IDs aus der Tabelle MSysObjects in die der Enumeration acObjectType übersetzt.
Den Modulnamen speichert die Routine dann sicherheitshalber in einer weiteren Variablen namens strModulname_Original zwischen, dieser Wert wird weiter unten noch benötigt.
Dann folgt die erste externe Funktion, nämlich SaveModule (s. Listing 3). Im ersten Schritt (3) versucht diese Routine, den Inhalt des aktuellen Objekts als Textdatei zu speichern. Dies gelingt gerade bei Formularen und Berichten nicht unbedingt: Wenn diese sich in einem ungespeicherten Zustand befinden, kann man sie nicht so einfach mit SaveAsText auf die Festplatte bannen. Im schlechtesten Fall löst dies einen Fehler aus, der aber in der folgenden Select Case-Anweisung behandelt wird.
Listing 3: Diese Routine prüft, ob ein Modul aktuell gespeichert ist.
Public Function SaveModule(strModulname As String, lngObjecttype As AcObjectType, _ bolZwischenspeichern As Boolean) As Boolean On Error Resume Next Dim strGuid As String strGuid = CreateGUID SaveAsText lngObjecttype, strModulname, CurrentProject.Path & "\temp.txt" Select Case Err.Number Case 32584, 2001 If Not bolZwischenspeichern Then On Error GoTo 0 DoCmd.SelectObject lngObjecttype, strModulname, True On Error Resume Next DoCmd.CopyObject , strModulname & "_" & strGuid, lngObjecttype, strModulname On Error GoTo 0 SaveAsText lngObjecttype, strModulname & "_" & strGuid, _ CurrentProject.Path & "\temp.txt" strModulname = strModulname & "_" & strGuid SaveModule = True Else SaveModule = False End If Case 0 SaveModule = True Case Else SaveModule = False MsgBox "Fehler " & Err.Number & " " & Err.Description End Select If lngObjecttype = acModule Then If VBE.ActiveVBProject.VBComponents.Item(strModulname).Saved = False Then strModulname = strModulname & "_" & strGuid End If End If End Function
Ungespeicherter Zustand bedeutet, dass ein Objekt zwar geändert (egal, ob diese änderung sich auf den Code oder auch auf Steuerelemente et cetera bezieht), aber seit dieser änderung noch nicht gespeichert wurde.
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