Tausendsassa Datenblatt – Teil 2

15.02.2006 von André Minhorst
Die Datenblattansicht bietet standardmäßig viele Möglichkeiten zum Anpassen des Layouts. Manch ein Benutzer ändert hier aber mehr, als er soll, oder weiß anschließend nicht mehr, wie er den Ausgangszustand wieder herstellt. Erweitern Sie die Klasse aus dem ersten Teil dieser Beitragsreihe um weitere Funktionen, mit denen Sie die Einstellungen im Griff behalten.

Einer der größten Vorteile der Datenblattansicht gegenüber Alternativen wie der Endlosansicht oder Listenfeldern ist die hohe Flexibilität. So lässt sich nicht nur die Sortierung per Menüeintrag ändern, sondern Sie können auch Felder ein- und ausblenden oder deren Anordnung ändern.

Nun ist der gemeine Benutzer unberechenbar und hat blitzschnell Felder ausgeblendet und deren Reihenfolge vertauscht. Das allein wäre nicht schlimm, wenn er sich nicht sofort danach Hilfe suchend an den Entwickler wendete, um ihn um eine neue Version der Anwendung zu bitten, die wieder alle Felder wie zu Beginn anzeigt.

Im zweiten Teil dieser Beitragsreihe lernen Sie einige Routinen kennen, die Sie leicht in eigene Formulare integrieren können und mit denen Sie dafür sorgen, dass der Benutzer sicher immer wieder die zuletzt verwendeten Einstellungen bezüglich der angezeigten Spalten und deren Reihenfolge vorfindet.

Dass der Benutzer das Layout von Unterformularen in der Datenblattansicht überhaupt in Unordnung bringen kann, hat folgende mögliche Ursachen:

Die meisten dieser Einstellungen lassen sich über das Kontextmenü der Spaltenköpfe einstellen (Bild 1). Das Einblenden ausgeblendeter Spalten erfordert die Anzeige eines speziellen Dialogs, den man über den Menüeintrag Format/Spalten einblenden anzeigt (Bild 2).

Bild 1: Die Datenblattansicht bietet einige Möglichkeiten zum Anpassen des Layouts.
Bild 2: Das Einblenden von Spalten erfolgt in einem separaten Dialog.

Wenn Sie eine Anwendung ausliefern, stellen Sie die Spaltenbreiten und deren Anordnung vermutlich so ein, dass alle Informationen gut lesbar dargestellt werden. Wenn Sie dem Benutzer alle Freiheiten geben und dennoch gewährleisten möchten, dass sich der Ausgangszustand leicht wiederherstellen lässt, können Sie die Klasse aus dem ersten Teil dieser Beitragsreihe wie nachfolgend beschrieben erweitern.

Datenblatt wie neu

Die folgende Lösung sorgt dafür, dass die Datenbankanwendung die Spaltenreihenfolge, die Spaltenbreite und die Sichtbarkeit speichert und bei jedem erneuten Öffnen den bei der letzten Verwendung vorhandenen Zustand wieder herstellt. Außerdem können Sie einen Originalzustand festlegen, den der Benutzer per Kontextmenübefehl herbeiführen kann.

Konfiguration speichern

Da Sie ohnehin mit einem Datenbanksystem arbeiten, speichern Sie die Konfiguration des Datenblatts natürlich in einer Tabelle, die im Entwurf wie in Bild 3 aussieht. Die Felder Formular und Feldname speichern den Namen des Unterformulars und des jeweiligen Steuerelements, die übrigen Felder halten den Zustand des Datenblatts fest: Originalbreite und Originalreihenfolge speichern die Informationen bei Auslieferung, LetzteBreite und LetzteReihenfolge den Zustand beim Schließen des Formulars. Das Feld Versteckt legt fest, ob ein Feld ein- oder ausgeblendet ist. Da im Auslieferungszustand wohl immer alle vorhandenen Felder eingeblendet sind, gibt es dieses Feld nur einmal. Schließlich merkt sich das Feld Fixiert, welche der Spalten des Formulars fixiert sind.

Bild 3: Tabelle zum Speichern der Konfiguration des Datenblatts.
Bild 4: Beispiel für die Daten der Tabelle tblFelder für ein Formular, dessen Layout bereits geändert wurde.

Wichtig ist, dass die Felder Formular und Feldname mit einem zusammengesetzten eindeutigen Index versehen sind. Andernfalls könnte man jede Kombination aus Formular und Feldname mehrmals anlegen. Wie der Inhalt dieser Tabelle aussehen kann, zeigt Bild 4.

Speichern des Originalzustands

Um die Funktionalität zum Wiederherstellen des Originalzustandes zu gewährleisten, müssen Sie diesen Zustand zunächst einmal festhalten. Das Speichern der Konfiguration wird durch das Ereignis Beim Entladen des Unterformulars ausgelöst (Listing 1)

Private Sub mForm_Unload(Cancel As Integer)
SpeichereKonfiguration
End Sub

Die hier aufgerufene Routine SpeichereKonfiguration (Listing 2) erfasst zunächst, wie viele Spalten der Datenblattansicht fixiert sind. Der von der Eigenschaft FrozenColumns zurückgegebene Wert lautet beispielsweise 2, wenn nur das erste Feld fixiert ist. Der Wert 1 ist der Standardwert und steht stellvertretend für die Spalte, die den Datensatzmarkierer enthält. Diese Spalte ist selbstverständlich immer fixiert. Um die tatsächliche Anzahl der fixierten Spalten zu erhalten, wird der Wert noch um 1 vermindert.

Private Sub SpeichereKonfiguration()
Dim db As DAO.Database
Dim ctl As Control
Dim strFormular As String
Dim strFeldname As String
Dim intOriginalbreite As Integer
Dim intLetzteBreite As Integer
Dim intOriginalreihenfolge As Integer
Dim intLetzteReihenfolge As Integer
Dim bolVersteckt As Boolean
Dim bolFixiert As Boolean
Dim intFixiert As Integer
Set db = CurrentDb
intFixiert = mForm.FrozenColumns - 1
For Each ctl In mForm
If ctl.ControlType = acTextBox Or ctl.ControlType = acComboBox Or ctl.ControlType
= acCheckBox Then
strFormular = mForm.Name
strFeldname = ctl.Name
intOriginalbreite = ctl.ColumnWidth
intLetzteBreite = ctl.ColumnWidth
intOriginalreihenfolge = ctl.ColumnOrder
intLetzteReihenfolge = ctl.ColumnOrder
bolVersteckt = ctl.ColumnHidden
If ctl.ColumnOrder <= intFixiert Then
bolFixiert = True
Else
bolFixiert = False
End If
On Error Resume Next
db.Execute "INSERT INTO tblFelder(Formular, Feldname, Originalbreite,
LetzteBreite, OriginalReihenfolge, LetzteReihenfolge, Versteckt, Fixiert) VALUES('"
& strFormular & "','" & strFeldname & "'," & intOriginalbreite & "," & intLetzteBreite
& ", " & intOriginalreihenfolge & " ," & intLetzteReihenfolge & "," &
BoolToString(bolVersteckt) & ", " & BoolToString(bolFixiert) & ")", dbFailOnError
If Err.Number = 3022 Then
db.Execute "UPDATE tblFelder SET LetzteBreite = " & intLetzteBreite
& ", LetzteReihenfolge = " & intLetzteReihenfolge & ", Versteckt = " &
BoolToString(bolVersteckt) & ", Fixiert = " & BoolToString(bolFixiert) & " WHERE
Formular = '" & strFormular & "' AND Feldname = '" & strFeldname & "'"
End If
On Error GoTo 0
End If
Next ctl
Set db = Nothing
End Sub

Anschließend durchläuft die Routine alle Steuerelemente des Unterformulars, das hier durch die Variable mForm gekennzeichnet ist. Wie diese Variable belegt wird, erfahren Sie weiter unten.

Ermitteln der Werte für tblFelder

Da nur Textfelder, Kombinationsfelder oder Kontrollkästchen in der Datenblattansicht angezeigt werden sollen, wird die nachfolgende If-Bedingung nur für diese drei Steuerelementtypen ausgeführt. Dann werden die Werte ermittelt, die im darauf folgenden Schritt in der Tabelle tblFelder gespeichert werden sollen: der Formularname, der Steuerelementname, die Breiten und Reihenfolgen sowie die Informationen über die Sichtbarkeit und die Fixierung. Für jedes der betroffenen Steuerelemente wird anschließend ein Datensatz mit dem ermittelten Wert angelegt.

Die Antwort darauf, warum etwa die Variablen Originalbreite und LetzteBreite mit dem gleichen Wert, nämlich der Eigenschaft ColumnWidth des Steuerelements, gefüllt werden, findet sich in den anschließenden Anweisungen zum Anlegen des Datensatzes: Die Routine versucht zunächst, einen neuen Datensatz anzulegen. Dies gelingt natürlich nur, wenn für die Kombination aus Feld und Formular noch kein Datensatz vorhanden ist, also wenn die Daten zum ersten Mal angelegt werden. In dem Fall sind Originalbreite und letzte Breite natürlich identisch.

Ist schon ein Datensatz vorhanden, deutet das darauf hin, dass das Formular bereits einmal geöffnet wurde. Das heißt, dass auch die Originalbreiten und die Originalreihenfolge schon gespeichert sind; es müssen also nur noch die zuletzt verwendeten Einstellungen gespeichert werden.

Das Vorhandensein des Datensatzes wird nicht gesondert überprüft, sondern es wird einfach versucht, den Datensatz neu zu schreiben – ist der Datensatz schon vorhanden, löst dies einen entsprechenden Fehler aus. Nur in diesem Fall kommt die zweite SQL-Anweisung zum Aktualisieren der Informationen für das aktuelle Steuerelements zum Zuge. Diese wiederum schreibt nur die zuletzt gültigen Werte zurück.

Zusammengefasst bedeutet dies, dass die erste SQL-Anweisung einmal, nämlich vor der Auslieferung des Formulars, ausgeführt wird, und die zweite SQL-Anweisung den Regelfall darstellt – in dem die aktuellen Parameter der Steuerelemente gespeichert werden.

Einlesen der Konfiguration und Anpassen derDatenblattansicht

Im ersten Teil Artikelserie haben Sie ja bereits erfahren, wie Sie die Funktionen eines Formulars und seiner Steuerelemente in einer separaten Klasse kapseln. Die dazu notwendigen Routinen wurden für diesen Artikel ein wenig angepasst.

Der einzige Code, der im eigentlichen Formular untergebracht werden muss, ist der aus Listing 3. Nach dem Instanzieren des Objekts obj- Form weist die Routine lediglich noch der Eigenschaft Unterformular den Namen des Unterformulars mit der Datenblattansicht zu.

Dim objForm As clsForm
Private Sub Form_Open(Cancel As Integer)
Set objForm = New clsForm
Set objForm.Unterformular = Me.sfmArtikelNachKategorie
End Sub

Beim Zuweisen des Unterformulars wird die entsprechende Property-Prozedur ausgelöst. Den ersten Teil und die Deklaration der Membervariablen kennen Sie bereits; interessant sind die beiden Prozeduraufrufe KontextmenueErweitern und LadeKonfiguration am Ende der Routine.

Dim mSubform As SubForm
Dim WithEvents mForm As Form
Dim mColControls As Collection
Public Property Set Unterformular(sfmSubform As SubForm)
Dim ctl As Control
Dim objControl As clsControl
Set mSubform = sfmSubform
Set mForm = mSubform.Form
Set mColControls = New Collection
mSubform.Form.OnUnload = "[Event Procedure]"
For Each ctl In mSubform.Controls
If ctl.ControlType = acTextBox Or ctl.ControlType = acComboBox Or _
ctl.ControlType = acCheckBox Then
Set objControl = New clsControl
Set objControl.FormControl = ctl
mColControls.Add objControl
End If
Next ctl
KontextmenueErweitern
LadeKonfiguration
End Property

Schauen Sie sich zunächst die Routine LadeKonfiguration an (Listing 5). Sie öffnet eine Datensatzgruppe, die alle Datensätze der Tabelle tblFel der zu Steuerelementen des aktuellen Unterformulars enthält.

Private Sub LadeKonfiguration()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim strFormular As String
strFormular = mSubform.Name
Set db = CurrentDb
Set rst = db.OpenRecordset("SELECT * FROM tblFelder WHERE Formular = '" & _
strFormular & "' ORDER BY LetzteReihenfolge")
mSubform.SetFocus
DoCmd.RunCommand acCmdUnfreezeAllColumns
Do While Not rst.EOF
mSubform.Controls(rst!Feldname).ColumnOrder = rst!LetzteReihenfolge
mSubform.Controls(rst!Feldname).ColumnWidth = rst!LetzteBreite
mSubform.Controls(rst!Feldname).ColumnHidden = rst!Versteckt
If rst!Fixiert = True Then
mSubform.Controls(rst!Feldname).SetFocus
DoCmd.RunCommand acCmdFreezeColumn
End If
rst.MoveNext
Loop
Set rst = Nothing
Set db = Nothing
End Sub

Bevor die Routine die Datensätze für die einzelnen Steuerelemente durchläuft, muss sie eine eventuell vorhandene Fixierung entfernen. Dazu setzt sie den Fokus auf das Unterformular und ruft die RunCommand-Anweisung mit dem passenden Parameter auf. Der Grund für diese Vorgehensweise ist, dass bei bestehender Fixierung keine Änderungen an den fixierten Feldern vorgenommen werden können.

Dann geht es endlich los: In einer Do-While-Schleife durchläuft die Routine alle Steuerelemente, passt Breite, Reihenfolge und Sichtbarkeit an und fixiert gegebenenfalls die angegebenen Felder.

Ausgangszustand wieder herstellen

Wie oben versprochen, soll die Klasse auch dafür sorgen, dass der Ausgangszustand der Datenblattansicht wieder herstellt wird; das heißt, dass die Eigenschaften wieder auf die in den Feldern Originalbreite und Originalreihenfolge gespeicherten Werte zurückgesetzt werden. Außerdem werden alle Felder wieder eingeblendet und eine eventuell vorhandene Fixierung entfernt.

Diese Aktion muss natürlich irgendwie gestartet werden. Da die betroffenen Eigenschaften am schnellsten per Kontextmenü verändert werden können, ist dies natürlich ein geeigneter Platz für einen Befehl zum Initialisieren der Datenblattansicht. Im Detail soll dies wie in Bild 5 aussehen.

Bild 5: Kontextmenüeintrag zum Initialisieren der Datenblattansicht.

Da die Klasse zum Kapseln der Formularfunktionalität jeweils dynamisch zugewiesen wird, muss auch der passende Menüeintrag per VBA erzeugt werden. Dies geschieht in den Routinen KontextmenueErzeugen und KontextmenuepunktErzeugen (Listing 6 und 7). Der Grund, warum die gleiche Anweisung direkt in mehreren Kontextmenüs eingetragen wird, ist ganz einfach: Dieser Befehl soll möglichst im kompletten Datenblatt verfügbar sein, und dieses besteht nun einmal aus verschiedenen Bereichen.

Private Sub KontextmenueErweitern()
KontextmenuepunktAnlegen "Form Datasheet Column"
KontextmenuepunktAnlegen "Form Datasheet Subcolumn"
KontextmenuepunktAnlegen "Form Datasheet Row"
KontextmenuepunktAnlegen "Form Datasheet Cell"
End Sub

Private Sub KontextmenuepunktAnlegen(strMenue As String)
Dim cbr As Object
Dim cbc As Object
Set cbr = CommandBars(strMenue)
On Error Resume Next
cbr.Controls("Datenblatt zurücksetzen").Delete
On Error GoTo 0
Set cbc = cbr.Controls.Add(1)
cbc.BeginGroup = True
cbc.Caption = "Datenblatt zurücksetzen"
cbc.TooltipText = "Stellt die ursprüngliche Reihenfolge und Spaltenbreite _
wieder her."
cbc.OnAction = "=KonfigurationWiederherstellen()"
Set cbc = Nothing
Set cbr = Nothing
End Sub

Die Aufrufe der Routine aus Listing 7 sorgen dafür, dass alle betroffenen Kontextmenüs einen Eintrag namens Datenblatt zurücksetzen erhalten, der eine Funktion namens Konfiguration- Wiederherstellen aufruft. Diese Funktion muss öffentlich sein, daher legen Sie sie in einem Standardmodul an. Den Code der Funktion können Sie Listing 8 entnehmen.

Public Function KonfigurationWiederherstellen()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim sfmDatenblatt As SubForm
Dim strFormular As String
Dim strUnterformular As String
Dim strHauptformular As String
Set db = CurrentDb
strUnterformular = Screen.ActiveControl.Parent.Name
strHauptformular = Screen.ActiveControl.Parent.Parent.Name
Set sfmDatenblatt = Forms(strHauptformular).Controls(strUnterformular)
Set rst = db.OpenRecordset("SELECT * FROM tblFelder WHERE Formular = '" &
strUnterformular & "' ORDER BY LetzteReihenfolge")
sfmDatenblatt.Controls(rst!Feldname).SetFocus
DoCmd.RunCommand acCmdUnfreezeAllColumns
Do While Not rst.EOF
sfmDatenblatt.Controls(rst!Feldname).ColumnOrder = rst!Originalreihenfolge
sfmDatenblatt.Controls(rst!Feldname).ColumnWidth = rst!Originalbreite
sfmDatenblatt.Controls(rst!Feldname).ColumnHidden = rst!Versteckt
rst.MoveNext
Loop
Set rst = Nothing
Set db = Nothing
End Function

Die Funktion ermittelt zunächst über den Ausdruck Screen.ActiveControl.Parent das Unterformular in der Detailansicht und öffnet dann eine Datensatzgruppe mit den entsprechenden Datensätzen der Tabelle tblFelder. Nach dem eventuell notwendigen Aufheben der Fixierung stellt die Routine die Eigenschaften der Spalten auf die in der Tabelle gespeicherten Originalwerte ein.

Zusammenfassung

Sie wissen nun, welche Möglichkeiten die Datenblattansicht von Unterformularen bietet und wie Sie diese erweitern können. Diese Erweiterungen haben Sie allerdings nicht im Modul des Formulars, sondern in eine separate Klasse integriert. Die in dieser Klasse enthaltenen Funktionen werden dynamisch durch Instanzierung eines Objekts dieser Klasse bereitgestellt. Nach der Instanzierung muss lediglich ein Verweis auf das Unterformular erfolgen, das notwendigerweise in der Datenblattansicht angezeigt wird. Die so erzeugte Klasse ist insofern sehr nützlich, als man sie mit wenigen Zeilen in anderen Formularen mit Unterformularen in der Datenblattansicht verfügbar machen kann. Diese Technik hält noch weitere Möglichkeiten bereit. In loser Folge erfahren Sie in inside Access, wie sich noch weitere Aufgaben in externen Klassen kapseln lassen und man sich damit eine Menge Tipparbeit sparen kann.