Zur Ein- oder Ausgabe eines Datums macht sich ein geeignetes Kalendersteuerelement im Formular besser als ein schnödes Textfeld. Das kann fest im Formular integriert sein, oder als Popup zur Auswahl erscheinen. In Buchungssystemen im Web sind solche Kalenderelemente allgegenwärtig. Auch Access wurde mit der Version 2007 ein solches Popup-Element spendiert, welches sich aber leider in keiner Weise steuern lässt. Grund genug also, um sich nach Alternativen umzuschauen.
Existierende Kalenderelemente
Einst war ein Kalendersteuerelement in Form der ActiveX-Datei mscal.ocx optionaler Bestandteil der Office-Installation. Das hat sich seit einiger Zeit geändert. Man ist nunmehr allein auf das Popup angewiesen, das bei Datumsfeldern zur Auswahl erscheint, wenn in das zugehörige Textfeld geklickt wird. Eine dauerhafte Ansicht des Kalenders ist somit verwehrt.
Bild 1 zeigt das alte Kalender-ActiveX-Steuerelement links oben im Formular. Aus irgendeinem Grund befand es sich, möglicherweise aus älteren Office-Installationen, noch bei uns in englischer Version im System. Seine Darstellung lässt sich im Formularentwurf oder auch per VBA steuern. Das betrifft die Schriftarten und die Farbgebung. Es löst bei einigen Aktionen ein Ereignis aus und spiegelt in der Eigenschaft Value das markierte Datum wieder. Sollte auf ihrem System die ActiveX-Datei nicht installiert sein, so meldet Access beim Aufruf des Formulars der Beispieldatenbank den etwas eigenartigen Fehler In diesem Formular befindet sich kein Steuerelement. Entfernen Sie in diesem Fall im Entwurf des Formulars frmCalendarTest einfach den Platzhalter des Steuerelements.
Bild 1: Demo einiger Datums- und Kalendersteuerelemente im Testformular
Da von diesem Fall auszugehen ist, fragt sich, wie mit anderen Mitteln eine ähnliche Darstellung und Funktion zu erreichen wäre. Testweise haben wir ein Listenfeld (rechts unten in der Abbildung) und ein Microsoft Listview Control (links unten) als Kalender zu gestalten versucht. Mit einigen Zeilen Code und den entsprechenden Einstellungen für die Steuerelemente gelingt dies auch. Der inakzeptable Nachteil der Lösungen besteht darin, dass sich in diesen Steuerelementen nur ganze Zeilen markieren lassen, nicht aber einzelne Zellen. Eine Auswahl per Maus scheidet deshalb aus. Derlei ließe sich also lediglich zur Anzeige der Tage eines Monats verwenden. Doch das brauchen Sie wohl höchst selten. Bleiben noch zwei Alternativen: das Microsoft DateTime Picker Control und die Access-Textbox mit Datumsformatierung. Ersteres zeigt sich im Formular rechts oben. Es entspringt der ActiveX-Datei mscomct2.ocx, die früher ebenfalls mit Office installiert wurde (s. Bild 2).
Bild 2: Das Microsoft DateTime-Picker- ActiveX-Steuerelement
Wir erwähnen es nur der Vollständigkeit halber, denn außer einer abweichenden Gestalt bietet es gegenüber der dem Popup von Access (s. Bild 3) keine sonderlichen Vorteile. Nur die Schriftarten und Farben können hier zusätzlich eingestellt werden. Die Funktionalität ist bei beiden jedoch gleich.
Bild 3: Das Popup-Element zur Datumsauswahl bei Access-Textboxen
Normalerweise öffnet sich das Kalenderelement einer Textbox mit Datumsformatierung dann, wenn Sie auf das Symbol rechts neben dem Textfeld klicken. Es gibt aber eine Anweisung, die die Anzeige des Kalender-Popups erzwingt. Möchten Sie etwa, dass es sofort beim Eintritt in das Feld erscheint, oder auch beim Klicken in das Textfeld, so schreiben Sie die folgende Zeile in die entsprechenden Ereignisprozeduren:
Private Sub txtDate_Click() RunCommand acCmdShowDatePicker End Sub Private Sub txtDate_Enter() RunCommand acCmdShowDatePicker End Sub
Diese RunCommand-Anweisung ermöglicht das öffnen des Popups per Code, falls die Datums-Textbox gerade den Fokus besitzt. Andernfalls ereignet sich eine Fehlermeldung. Verwenden Sie sie deshalb besser nur in den Ereignissen der Textbox selbst.
Sicher finden Sie auch noch weitere Fremdsteuerelemente in Form von ActiveX-Dateien bei Drittanbietern. Es gilt jedoch die Maxime, wegen der möglichen Registrierungsprobleme solche ActiveX-Komponenten nur dann einzusetzen, wenn es absolut notwendig ist. Und das ist hier nicht gegeben, denn ein Kalendersteuerelement lässt sich gut auch mit Access-Bordmitteln selbst erstellen!
Kalender im Eigenbau
Natürlich könnte man einfach 31 Steuerelemente, etwa Buttons oder Textboxen, in einem Formular so anordnen, dass diese einen Kalender ergäben. Um auf eine Datumsauswahl zu reagieren, bräuchte es dafür dann eben auch 31 Ereignisprozeduren. Das wäre zwar recht unkompliziert, ist aber wenig elegant.
Dabei reicht es im Prinzip ja für die Tage der Woche nur sieben Steuerelemente im Detailbereich zu platzieren und diese Wochen untereinander zu wiederholen. Damit sind wir allerdings auf eine Tabelle angewiesen, die die Datensätze für alle Wochen eines Monats bereitstellt. Nur eine Tabelle oder Abfrage als Datensatzherkunft kann bewirken, dass sich der Detailbereich wiederholt.
Bevor es Schritt für Schritt an die Entwicklung des Kalenderformulars geht, sollte noch definiert werden, was der Ausdruck Steuerelement in unserem Zusammenhang bedeutet. Ein wirklich neues Steuerelement kann nun mal mit Access nicht erzeugt werden. Sie können lediglich aus bestehenden Elementen eine neue Funktionsgruppe erstellen, die den Anschein eines einzelnen Steuerelements erweckt. Damit das Ganze wiederverwendbar ist, sollte diese Funktionsgruppe möglichst in einem Formular ohne weitere Abhängigkeiten untergebracht werden, welches Sie später als Unterformular in andere einsetzen. Dieses Unterformular stellt dann quasi ein Pseudo-Steuerelement dar. In der Beispieldatenbank nennt es sich sfrmCalendar. Das Präfix s steht für Subform.
Wochentabelle für einen Monat
Die Basistabelle tblCalendar für das Kalenderformular in Bild 4 weist lediglich die sieben Datenfelder D1 bis D7 von Typ Long für die einzelnen Tage der Woche auf. Für die Namen der Felder kann man eher nicht Kürzel für Wochentage, wie Mo bis So, verwenden, da der Erste eines Monats eben selten ein Montag ist.
Bild 4: Die Tabelle tblCalendar als Basis für den Kalender im Eigenbau
Die Nummerierung in den Namen der Felder ermöglicht später den gezielten Zugriff auf die Spalten der Tabelle. Ein Blick auf Bild 5 zeigt, was dahinter steckt.
Bild 5: Die gefüllte Tabelle tblCalendar im Datenblatt
Die Datensätze füllt nämlich eine VBA-Routine des Formulars zur Laufzeit unter Angabe des gewünschten Jahrs und Monats. Sie ordnet die Zahlen für die Tage so an, dass sich links immer die Montage befinden. Der Erste des Monats wiederum soll in der ersten Zeile auftauchen. Ist das kein Montag, so sind links von diesem Feld die Tage des Vormonats einzusetzen. Hier sind das der 28. bis 31.. ähnliches gilt für den Letzten des Monats, welcher sich im letzten Datensatz befinden soll, aber in der Regel kein Sonntag ist. Folglich ergeben sich in der letzten Zeile rechts vom Monatsletzten meist noch weitere Tage des Folgemonats. Je nach Monat und Jahr sind in der Tabelle mindestens vier, maximal sechs, Datensätze zu erzeugen.
Meist sind die Tage, welche nicht zum angezeigten Monat gehören, also jene des Vor- und Folgemonats, in Kalendern ausgegraut dargestellt. Um dieses Feature auch unserem Kalender zu spendieren, verwenden wir für die Textfelder im Formular eine Bedingte Formatierung. Denn per VBA-Code kann zwar die Hintergrundfarbe einer Textbox über die Eigenschaft BackgroundColor gesteuert werden, doch das betrifft dann sämtliche Zellen einer Spalte, die sich von diesem Datenfeld ableiten, nicht jedoch einzelne Zellen.
Um Bedingte Formatierung kommt man hier also nicht herum. Da diese einen Vergleichsausdruck für die Steuerung des Formats verwendet, benötigen wir irgendein Indiz, welches die nicht zum Monat gehörigen Tage definiert. Und das ist hier das Minuszeichen. Alle auszugrauenden Tage haben einen negativen Wert. Damit ist auch klar, weshalb hier keine Datumstypen für die Felder der Tabelle zum Einsatz kommen, denn deren Werte können nicht negativ sein. Ein Long– oder ein Integer-Wert reichen zur Kennzeichnung aus.
Der direkte Bezug des Kalenders zu einer Tabelle bringt allerdings einen Nachteil mit sich. Benötigen Sie etwa zwei Kalendersteuerelemente in Ihrem Hauptformular, so wären diese ohne weiteres Zutun beide an dieselbe Tabelle gebunden und zeigten in der Folge auch die gleichen Daten an. In diesem Fall erstellen Sie eine Kopie der Tabelle und bezeichnen diese etwa mit tblCalendar2. Die folgend beschriebene Routine zum Füllen der Tabelle kann unterschiedliche Tabellen berücksichtigen.
Füllen der Tabelle per VBA
Die dafür verantwortliche Routine nennt sich FillCalendar und ist in Listing 1 abgebildet, wobei hier einige Teile entfernt wurden, die nicht unmittelbar zum Erzeugen der Datensätze gehören.
Private m_Table As String Property Get Table() As String Table = m_Table End Property Property Let Table(ByVal Value As String) m_Table = Value FillCalendar End Property Private Sub FillCalendar() Dim rs As DAO.Recordset Dim i As Long, j As Long Dim n As Long Dim StartDate As Date Me.RecordSource = m_Table n = Weekday(DateSerial(m_Year, m_Month, 1), vbMonday) StartDate = DateSerial(m_Year, m_Month, 1) - n CurrentDb.Execute "DELETE FROM " & m_Table Set rs = CurrentDb.OpenRecordset("SELECT * FROM " & m_Table, dbOpenDynaset) For j = 0 To 5 rs.AddNew For i = 0 To 6 StartDate = StartDate + 1 n = VBA.Month(StartDate) rs.Fields("D" & CStr(1 + i)).Value = Day(StartDate) * IIf(n <> m_Month, -1, 1) Next i rs.Update If n <> m_Month Then Exit For Next j rs.Close Me.Requery End Sub
Listing 1: Füllen einer Kalendertabelle über VBA-Code
Der Name der zu füllenden Tabelle steht im Kopf in der Eigenschaftsvariablen m_Table des Formularmoduls. Dieser Variablen muss also erst ein Wert zugewiesen werden, was die Property-Let-Prozedur Table übernimmt:
frm.Table = "tblCalendar"
Erst dann kann die eigentliche Routine aufgerufen werden. Sie weist dem Formular selbst zunächst als Datensatzherkunft (RecordSource) die nun in m_Table stehende Tabelle zu. Das Formular ist im Entwurf also noch nicht zwingend an eine Tabelle gebunden, sondern das geschieht hier zu Laufzeit.
Nun benötigen wir jenes Datum, welches in der linken oberen Ecke des Kalenders steht. über Property-Prozeduren (hier nicht im Listing), wurde in den Variablen m_Month und m_Year der gewünschte Monat und das Jahr für den Kalender abgespeichert. Den Ersten dieses Monats erhalten Sie über die VBA-Funktion DateSerial:
DateSerial(m_Year, m_Month, 1)
Das ist indessen noch nicht das Datum, welches links oben steht. Um zu ermitteln, wie viele Tage des Vormonats zu berücksichtigen sind, kommt die VBA-Funktion Weekday zum Einsatz. Sie gibt eine Zahl zurück, die den Wochentag symbolisiert. Die 1 entspricht dabei Montag, die 7 dem Sonntag. Nun muss vom Ersten des Monats lediglich diese Zahl subtrahiert werden, und schon steht das Datum links oben fest. Es wird der Variablen StartDate vom Type Date zugewiesen. Nach diesen Vorarbeiten kann die Tabelle m_Table über zwei verschachtelte Schleifen mit Daten versehen werden.
Doch vorher muss die Tabelle noch über die Execute-Anweisung und den SQL-DELETE-Ausdruck geleert werden. Anschließend öffnet ein Recordset rs die Datensätze zum Beschreiben.
Die Zählervariable für die Schleife zum Hinzufügen von Datensätzen ist j. Da maximal sechs Zeilen im Kalender stehen können, ist ihr Bereich auf 0 bis 5 eingestellt. Nach jedem Durchlauf wird per AddNew ein neuer Datensatz erzeugt. Für den Zugriff auf die Feldwerte eines Datensatzes gibt es dann die folgende Schleife auf den Zähler i, deren Bereich sich für die einzelnen Wochentage von 0 bis 6 erstreckt. In dieser wird fortlaufend der Wert des Kalenderdatums in StartDate um eins erhöht. Das ist statthaft, weil ein Date-Type imgrunde ein Double-Wert ist, wobei die Nachkommastellen die Tageszeit angeben, die Vorkommastellen die Tage. Also führt Addition von 1 zum nächsten Tag.
Der Zugriff auf die Datenfelder geschieht namentlich über das Präfix D und den Zahler i, zu dem noch 1 addiert werden muss, weil die Felder von 1 bis 7 nummeriert sind, nicht von 0 bis 6. Der Wert eines Felds errechnet sich aus dem Tagesanteil des Datums, welchen die VBA-Funktion Day zurückgibt.