{"id":55000906,"date":"2013-10-01T00:00:00","date_gmt":"2020-05-22T21:30:59","guid":{"rendered":"http:\/\/access-im-unternehmen.aix-dev.de\/aiu\/?p=906"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-30T00:00:00","slug":"Schichtplaner","status":"publish","type":"post","link":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/","title":{"rendered":"Schichtplaner"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62\" width=\"1\" height=\"1\" alt=\"\"><\/p>\n<p><b>Wenn ein Arbeitsplatz nicht nur zu gew&ouml;hnlichen Arbeitszeiten besetzt sein muss, sondern im Extremfall den kompletten Tag, m&uuml;ssen mehrere Mitarbeiter bereitstehen. Diese werden dann in mehreren Schichten &uuml;ber den Tag verteilt. Dabei ist es gar nicht so einfach, nicht den &uuml;berblick zu verlieren &#8211; immerhin sollen alle Mitarbeiter im Laufe eines Zeitraums m&ouml;glichst die angestrebte Anzahl Schichten durchf&uuml;hren. Gleichzeitig sollen zu jeder Zeit ausreichend Mitarbeiter arbeiten. Was liegt da n&auml;her, als dieses Problem mit einer geeigneten Access-Datenbank anzugehen <\/b><\/p>\n<p>Die in diesem Beitrag beschriebene Schichtplanung soll einige Ein- und Ausgabem&ouml;glichkeiten f&uuml;r die Erstellung von Schichtpl&auml;nen ber&uuml;cksichtigen:<\/p>\n<ul>\n<li>Eingabe\/Markierung der Schichten in einer &uuml;bersicht mit allen Mitarbeitern und f&uuml;r einen bestimmten Zeitraum wie etwa 28 Tage<\/li>\n<li>Ber&uuml;cksichtigung verschiedener Schichten, in der Regel Fr&uuml;h-, Mittag- und Sp&auml;tschicht<\/li>\n<li>&uuml;bersicht f&uuml;r einen bestimmten Zeitraum wie etwa einen Monat als Bericht<\/li>\n<li>Ausgabe des Schichtplans f&uuml;r einzelne Mitarbeiter per Bericht, dort unterschiedliche Markierung der verschiedenen Schichten<\/li>\n<\/ul>\n<p><b>Tabellen der L&ouml;sung<\/b><\/p>\n<p>Die L&ouml;sung ben&ouml;tigt zun&auml;chst eine Mitarbeitertabelle. Diese hei&szlig;t <b>tblMitarbeiter <\/b>und soll so einfach wie m&ouml;glich ausgelegt werden. Das hei&szlig;t, dass es zun&auml;chst nur ein Feld f&uuml;r die Bezeichnung des Mitarbeiters (<b>Mitarbeiter<\/b>) sowie das Prim&auml;rschl&uuml;sselfeld <b>MitarbeiterID<\/b> gibt.<\/p>\n<p><b>Schichtarten<\/b><\/p>\n<p>Die Anwendung soll bez&uuml;glich der verschiedenen Schichten m&ouml;glichst flexibel sein. Das hei&szlig;t, dass es nicht nur die &uuml;blichen Schichten wie Fr&uuml;h-, Sp&auml;t- und Nachtschicht geben soll. Der Benutzer kann den Tag nach Gutd&uuml;nken in die gew&uuml;nschten Schichten einteilen.<\/p>\n<p>Gegebenenfalls werden nur zwei Schichten pro Tag ben&ouml;tigt &#8211; etwa von 8 Uhr bis 14 Uhr und von 14 Uhr bis 22 Uhr.<\/p>\n<p>Oder zwei Schichten &uuml;berlappen sich, weil es w&auml;hrend der &uuml;berlappung besonders viel zu tun gibt (etwa von 8 Uhr bis 16 Uhr und von 10 Uhr bis 20 Uhr).<\/p>\n<p>Deshalb sehen wir eine eigene Tabelle namens <b>tblSchichtarten<\/b> zur Erfassung der verschiedenen Schichten vor (s. Bild 1). Diese Tabelle enth&auml;lt die folgenden Felder:<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_001.png\" alt=\"Verwaltung von Arbeitstagen per Tabelle\" width=\"270\" height=\"266,5473\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 1: Verwaltung von Arbeitstagen per Tabelle<\/span><\/b><\/p>\n<ul>\n<li><b>SchichtartID<\/b>: Prim&auml;rschl&uuml;sselfeld der Tabelle<\/li>\n<li><b>Schichtart<\/b>: Bezeichnung der Schichtart, etwa Fr&uuml;hschicht oder Sp&auml;tschicht<\/li>\n<li><b>Schichtbeginn<\/b>: Uhrzeit, zu der die Schicht beginnt<\/li>\n<li><b>Schichtende<\/b>: Uhrzeit, zu der die Schicht endet<\/li>\n<li><b>Farbe<\/b>: Legt eine Farbe f&uuml;r die Schichtart fest.<\/li>\n<li><b>Kuerzel<\/b>: Legt einen Buchstaben zur einfachen Anzeige in &uuml;bersichten fest.<\/li>\n<\/ul>\n<p><b>Ber&uuml;cksichtigung von Fehltagen<\/b><\/p>\n<p>Die Mitarbeiter stehen nicht immer zur Verf&uuml;gung, da Sie wegen Urlaub, Krankheit oder Weiterbildung fehlen. Man neigt dazu, schnell eine eigene Tabelle zur Erfassung der Fehlzeiten zu erstellen.<\/p>\n<p>In der Tat ist es aber viel sinnvoller, einfach einen oder mehrere weitere Eintr&auml;ge in der Tabelle <b>tblSchichtarten <\/b>zu erfassen &#8211; beispielsweise mit den Werten <b>Krankheit<\/b>, <b>Urlaub <\/b>oder <b>Fortbildung <\/b>im Feld <b>Schichtart<\/b>.<\/p>\n<p>Auf diese Weise ist es sogar m&ouml;glich, die Fehlzeit auf bestimmte Schichten einzuschr&auml;nken. Wenn ein Mitarbeiter beispielsweise mittags einen wichtigen Termin hat und dort keine Schicht wahrnehmen kann, tr&auml;gt man f&uuml;r diese Schicht einfach als Schichtart die entsprechende Fehlzeitschichtart ein.<\/p>\n<p>Damit diese Schichtarten deutlich von den &uuml;brigen, regul&auml;ren Schichtarten unterschieden werden k&ouml;nnen, erh&auml;lt die Tabelle <b>tblSchichtarten <\/b>ein weiteres Feld namens <b>Fehlschicht<\/b>. Dieses verwendet den Felddatentyp <b>Ja\/Nein<\/b>.<\/p>\n<p><b>Arbeitstage<\/b><\/p>\n<p>F&uuml;r einige Zwecke ist es sehr hilfreich, wenn es eine Tabelle mit allen ben&ouml;tigten Datumsangaben gibt. Den genauen Zweck erl&auml;utern wir sp&auml;ter, aktuell reicht Folgendes: Die Tabelle hei&szlig;t <b>tblArbeitstage<\/b>, enth&auml;lt die beiden Felder <b>ArbeitstagID<\/b> und <b>Arbeitstag <\/b>(<b>Datum\/Uhrzeit<\/b>) und wird mit der Prozedur aus Listing 1 gef&uuml;llt.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>ArbeitstageEintragen(<span style=\"color:blue;\">Optional<\/span> datStart<span style=\"color:blue;\"> As Date<\/span>, <span style=\"color:blue;\">Optional<\/span> datEnde<span style=\"color:blue;\"> As Date<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>datAktuell<span style=\"color:blue;\"> As Date<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">If <\/span>datStart = 0<span style=\"color:blue;\"> Then<\/span>\r\n         datStart = Date\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     <span style=\"color:blue;\">If <\/span>datEnde = 0<span style=\"color:blue;\"> Then<\/span>\r\n         datEnde = datStart + 1000\r\n     <span style=\"color:blue;\">End If<\/span>\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     For datAktuell = datStart To datEnde\r\n         db.Execute \"INSERT INTO tblArbeitstage(Arbeitstag) VALUES(\" & ISODatum(datAktuell) & \")\", dbFailOnError\r\n     <span style=\"color:blue;\">Next<\/span> datAktuell\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 1: F&uuml;llen der Tabelle tblArbeitstage<\/span><\/b><\/p>\n<p>Die Prozedur erwartet den ersten und den letzten anzulegenden Tag als optionale Parameter. Wird der Starttag nicht angegeben, stellt die Prozedur diesen auf das aktuelle Datum ein.<\/p>\n<p>Fehlt das Enddatum, erh&auml;lt die Variable <b>datEnde <\/b>den Inhalt von <b>datStart <\/b>plus 1.000 Tage. Anschlie&szlig;end durchl&auml;uft die Prozedur in einer Schleife den angegebenen Datumsbereich und tr&auml;gt die entsprechenden Datumsangaben in das Feld <b>Arbeitstag <\/b>der Tabelle <b>tblArbeitstage <\/b>ein.<\/p>\n<p><b>Schichten speichern<\/b><\/p>\n<p>Schlie&szlig;lich ben&ouml;tigen wir noch eine Tabelle, in der wir die jeweiligen Schichten f&uuml;r die Arbeitstage und Mitarbeiter speichern. Dies hei&szlig;t <b>tblSchichten <\/b>und ist wie in Bild 2 aufgebaut.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_003.png\" alt=\"Die Tabelle tblSchichten enth&auml;lt einen zusammengesetzten, eindeutigen Index f&uuml;r die beiden Felder MitarbeiterID und ArbeitstagID, damit f&uuml;r jeden Mitarbeiter nur eine Schicht je Arbeitstag festgelegt werden kann.\" width=\"570\" height=\"338,25\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 2: Die Tabelle tblSchichten enth&auml;lt einen zusammengesetzten, eindeutigen Index f&uuml;r die beiden Felder MitarbeiterID und ArbeitstagID, damit f&uuml;r jeden Mitarbeiter nur eine Schicht je Arbeitstag festgelegt werden kann.<\/span><\/b><\/p>\n<p><b>Schichtplan-Tabelle f&uuml;llen<\/b><\/p>\n<p>Die Tabelle <b>tblSchichten <\/b>muss nun noch vorausgef&uuml;llt werden. Warum das &#8211; k&ouml;nnen wir die Kombinationen aus Mitarbeiter, Arbeitstag und Schichtart nicht zusammenstellen, wenn die Schicht geplant wird Grunds&auml;tzlich ginge dies schon &#8211; aber wir m&ouml;chten ja bereits f&uuml;r das Festlegen der Schichten eine komfortable und &uuml;bersichtliche Benutzeroberfl&auml;che liefern. Daf&uuml;r ist es n&ouml;tig, dass die entsprechenden Datens&auml;tze der Tabelle <b>tblSchichten <\/b>bereits existieren &#8211; auch wenn die Schichtarten selbst dort noch nicht zugewiesen sind. Die gef&uuml;llte Tabelle soll schlie&szlig;lich wie in Bild 3 aussehen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_004.png\" alt=\"Die Tabelle tblSchichten mit einigen vorab angelegten Datens&auml;tzen\" width=\"420\" height=\"250,5882\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 3: Die Tabelle tblSchichten mit einigen vorab angelegten Datens&auml;tzen<\/span><\/b><\/p>\n<p>Das F&uuml;llen dieser Tabelle &uuml;bernimmt die Prozedur <b>SchichtenEintragen <\/b>aus Listing 2. Die Prozedur erzeugt zwei <b>Recordset<\/b>-Objekte auf Basis der Tabellen <b>tblMitarbeiter <\/b>und <b>tblArbeitstage<\/b>. In einer &auml;u&szlig;eren <b>Do While<\/b>-Schleife durchl&auml;uft die Prozedur die Datens&auml;tze der Tabelle <b>tblMitarbeiter<\/b>, in einer inneren Schleife die Datens&auml;tze aus <b>tblArbeitstage<\/b>.<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>SchichtenEintragen()\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Dim <\/span>rstMitarbeiter<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Dim <\/span>rstArbeitstage<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">Set<\/span> rstMitarbeiter = db.OpenRecordset(\"SELECT * FROM tblMitarbeiter\", dbOpenDynaset)\r\n     <span style=\"color:blue;\">Set<\/span> rstArbeitstage = db.OpenRecordset(\"SELECT * FROM tblArbeitstage\", dbOpenDynaset)\r\n     <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rstMitarbeiter.EOF\r\n         <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rstArbeitstage.EOF\r\n             On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n             db.Execute \"INSERT INTO tblSchichten(MitarbeiterID, ArbeitstagID) VALUES(\" & rstMitarbeiter!MitarbeiterID _\r\n                 & \", \" & rstArbeitstage!ArbeitstagID & \")\", dbFailOnError\r\n             <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n             rstArbeitstage.Move<span style=\"color:blue;\">Next<\/span>\r\n         <span style=\"color:blue;\">Loop<\/span>\r\n         rstArbeitstage.MoveFirst\r\n         rstMitarbeiter.Move<span style=\"color:blue;\">Next<\/span>\r\n     <span style=\"color:blue;\">Loop<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 2: Eintragen von Datens&auml;tzen in die Tabelle tblSchichten<\/span><\/b><\/p>\n<p>Nach dem Durchlaufen aller Datens&auml;tze der inneren Schleife wird der Datensatzmarkierer f&uuml;r das Recordset <b>rstArbeitstage <\/b>mit der <b>MoveFirst<\/b>-Methode wieder auf den ersten Datensatz zur&uuml;ckgesetzt.<\/p>\n<p>Innerhalb der beiden Schleifen befindet sich eine Anweisung, die der Tabelle <b>tblSchichten <\/b>jeweils einen Datensatz hinzuf&uuml;gt. Diese stellt die Werte der Fremdschl&uuml;sselfelder <b>MitarbeiterID <\/b>und <b>ArbeitstagID <\/b>der Tabelle auf den jeweils aktuellen Wert des Prim&auml;rschl&uuml;sselfelds der beiden <b>Recordset<\/b>-Objekte ein.<\/p>\n<p>Wenn ein neuer Mitarbeiter zur Tabelle <b>tblMitarbeiterID <\/b>hinzugef&uuml;gt wird oder wenn die Liste der Arbeitstage erweitert werden soll, braucht die Prozedur <b>SchichtenEintragen <\/b>einfach nur erneut aufgerufen zu werden. Durch die deaktivierte Fehlerbehandlung werden bereits vorhandene Kombinationen aus Mitarbeiter und Arbeitstag weder &uuml;berschrieben noch wird ein Fehler ausgel&ouml;st, wenn die Prozedur versucht, einen bereits vorhandenen und mit einem eindeutigen Index versehenen Datensatz erneut hinzuzuf&uuml;gen.<\/p>\n<p><b>Schichtplan anzeigen<\/b><\/p>\n<p>Nachdem wir die grundlegenden Daten f&uuml;r den Schichtplan in der Tabelle <b>tblSchichten <\/b>vorliegen haben, ben&ouml;tigen wir noch ein Formular, mit dem wir diese Daten komfortabel bearbeiten k&ouml;nnen. Dazu gibt es eine Reihe M&ouml;glichkeiten: Wir k&ouml;nnen beispielsweise alle Arbeitstage f&uuml;r einen einzigen Mitarbeiter untereinander anzeigen und anbieten, die Schichten f&uuml;r jeden Arbeitstag auszuw&auml;hlen.<\/p>\n<p>Oder wir zeigen alle Mitarbeiter und ihre Schichten f&uuml;r einen ausgew&auml;hlten Arbeitstag untereinander an und erlauben dem Benutzer, die Schichten f&uuml;r den aktuellen Arbeitstag zu bearbeiten.<\/p>\n<p>Dies erlaubt nat&uuml;rlich nicht die &uuml;bersicht, die wir uns erhoffen &#8211; n&auml;mlich die Darstellung in einer Matrix, bei der die Spaltenk&ouml;pfe die Arbeitstage anzeigen und die Zeilenk&ouml;pfe die Mitarbeiter.<\/p>\n<p>Die Zellen dazwischen sollen das Eintragen der jeweiligen Schichtart f&uuml;r die Kombination aus Arbeitstag und Mitarbeiter erlauben.<\/p>\n<p><b>Schichten per Kreuztabelle<\/b><\/p>\n<p>Diese Ansicht l&auml;sst sich mit Access-Bordmitteln nur auf einem Wege erreichen &#8211; n&auml;mlich mithilfe einer Kreuztabellenabfrage. Diese Kreuztabelle soll ein Ergebnis &auml;hnlich wie in Bild 4 liefern.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_005.png\" alt=\"Ergebnis der Kreuztabellenabfrage, welche die Daten f&uuml;r die Anzeige der Schicht&uuml;bersicht im Formular liefern soll\" width=\"700\" height=\"166,6667\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 4: Ergebnis der Kreuztabellenabfrage, welche die Daten f&uuml;r die Anzeige der Schicht&uuml;bersicht im Formular liefern soll<\/span><\/b><\/p>\n<p>Wie entwerfen wir die Kreuztabellenabfrage, die ein solches Ergebnis liefert Dazu m&uuml;ssen wir zun&auml;chst einmal ermitteln, welche Daten wir dazu ben&ouml;tigen:<\/p>\n<ul>\n<li>Mitarbeitername f&uuml;r die Anzeige in den Zeilen&uuml;berschriften<\/li>\n<li>Datum der Arbeitstage f&uuml;r die Anzeige in den Spalten&uuml;berschriften<\/li>\n<li>Schichtart oder Schichtartk&uuml;rzel f&uuml;r die Anzeige als Wert der Kreuztabelle<\/li>\n<\/ul>\n<p>Legen Sie also eine neue Abfrage an und  stellen Sie den Abfragetyp dann etwa &uuml;ber den entsprechenden Ribbon-Eintrag auf <b>Kreuztabelle <\/b>ein. F&uuml;gen Sie die vier Tabellen <b>tblSchichten<\/b>, <b>tblSchichtarten<\/b>, <b>tblMitarbeiter <\/b>und <b>tblArbeitstage <\/b>hinzu.<\/p>\n<p>Die Tabelle <b>tblSchichten <\/b>sorgt f&uuml;r die Verkn&uuml;pfung zwischen den drei &uuml;brigen Tabellen, welche die Daten f&uuml;r die Anzeige in der Kreuztabelle liefern.<\/p>\n<p>Das hei&szlig;t, dass Sie das Feld <b>Mitarbeiter <\/b>als <b>Zeilen&uuml;berschrift<\/b>, das Feld <b>Arbeitstag <\/b>als <b>Spalten&uuml;berschrift <\/b>und das Feld <b>Schichtart <\/b>als <b>Wert <\/b>zum Abfrageentwurf hinzuf&uuml;gen.<\/p>\n<p>Au&szlig;erdem ben&ouml;tigen wir noch ein Kriterium, da eine Kreuztabelle nur eine begrenzte Anzahl von Spalten anzeigen kann. In diesem Fall f&uuml;gen wir das Feld Arbeitstag zum Entwurf hinzu und legen in der Zeile <b>Funktion <\/b>den Wert <b>Bedingung <\/b>fest. Dann stellen wir f&uuml;r Testzwecke zun&auml;chst das folgende Filterkriterium ein (s. Bild 5):<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_002.png\" alt=\"Entwurf der Abfrage qryCTSchichten\" width=\"570\" height=\"342,6837\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 5: Entwurf der Abfrage qryCTSchichten<\/span><\/b><\/p>\n<pre>&gt;=#01.08.2013# Und &lt;#01.09.2013#<\/pre>\n<p>Ein Wechsel zur Datenblattansicht liefert relative wenige Daten &#8211; zumindest, wenn Sie noch keine Werte im Feld <b>SchichtartID <\/b>der Tabelle <b>tblSchichten <\/b>eingetragen haben.<\/p>\n<p>Der Grund ist, dass die Abfrage nur solche Datens&auml;tze liefert, f&uuml;r die alle Fremdschl&uuml;sselfelder der Tabelle <b>tblSchichten <\/b>gef&uuml;llt sind. Dies ist nat&uuml;rlich beim Feld <b>Schichtart-ID <\/b>nicht der Fall, da wir ja die Schichtarten f&uuml;r die einzelnen Arbeitstage erst noch festlegen wollen.<\/p>\n<p>Also stellen Sie die Verkn&uuml;pfungseigenschaften f&uuml;r die Beziehung zwischen den Tabellen <b>tblSchichten <\/b>und <b>tblSchichtarten <\/b>so ein, dass alle Datens&auml;tze der Tabelle <b>tblSchichten <\/b>angezeigt werden &#8211; unabh&auml;ngig davon, ob es bereits eine Verkn&uuml;pfung zu einem Datensatz der Tabelle <b>tblSchichtarten <\/b>gibt (s. Bild 6).<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_007.png\" alt=\"Einstellen der Verkn&uuml;pfungseigenschaften so, dass alle Datens&auml;tze der Tabelle tblSchichten angezeigt werden\" width=\"370\" height=\"231,6991\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 6: Einstellen der Verkn&uuml;pfungseigenschaften so, dass alle Datens&auml;tze der Tabelle tblSchichten angezeigt werden<\/span><\/b><\/p>\n<p>Die Kreuztabellenabfrage speichern wir nun noch unter dem Namen <b>qryCTSchichten<\/b>.<\/p>\n<p><b>Schichten im Formular anzeigen<\/b><\/p>\n<p>Das Anzeigen der Kreuztabelle im Formular ist prinzipiell ganz einfach: Sie m&uuml;ssen einfach nur ein Hauptformular namens <b>frmSchichten<\/b> erstellen und diesem ein Unterformular namens <b>sfmSchichten <\/b>in der Datenblattansicht hinzuf&uuml;gen, das die Abfrage <b>qryCTSchichten <\/b>als Datenherkunft verwendet.<\/p>\n<p>Wenn Sie dann die Feldliste einblenden, erkennen Sie allerdings das erste Problem: Die Felder der Datenherkunft sind nat&uuml;rlich alle nach den Spalten&uuml;berschriften der Kreuztabellenabfrage benannt, die ja dynamisch aus der Tabelle <b>tblArbeitstage<\/b> ermittelt werden &#8211; und das auch noch in einem Format, in dem die Punkte im Datum durch Unterstriche ersetzt wurden.<\/p>\n<p>Dennoch ziehen Sie zun&auml;chst alle Felder der Datenherkunft aus der Feldliste in den Detailbereich &#8211; irgendwie m&uuml;ssen wir ja beginnen. Das Ergebnis sieht dann wie in Bild 7 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_008.png\" alt=\"Der erste Entwurf des Unterformulars zur Anzeige des Ergebnisses der Kreuztabellenabfrage\" width=\"570\" height=\"454,9211\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 7: Der erste Entwurf des Unterformulars zur Anzeige des Ergebnisses der Kreuztabellenabfrage<\/span><\/b><\/p>\n<p>Nun wurden die Felder nicht nur nach dem Wert der Spalten&uuml;berschriften f&uuml;r die aktuellen Kriterien (also den gew&auml;hlten Zeitraum) benannt, sondern auch die Eigenschaft <b>Steuerelementinhalt <\/b>enth&auml;lt den entsprechenden Feldnamen der Abfrage als Wert.<\/p>\n<p>Nun m&ouml;chten wir aber nicht immer den gleichen Datumsbereich anzeigen, sondern diesen dynamisch einstellen &#8211; beispielsweise &uuml;ber ein Textfeld, das den ersten Tag eines Datumsbereichs etwa von 28 Tagen entgegennimmt. Dazu sind einige Schritte n&ouml;tig:<\/p>\n<ul>\n<li>Wir f&uuml;gen ein Textfeld namens <b>txtStartdatum <\/b>zum Formular hinzu, das zur Eingabe des ersten angezeigten Tages dient.<\/li>\n<li>Wir werfen ein paar der Textfelder aus dem Formularentwurf heraus, sodass neben dem Feld <b>Mitarbeiter <\/b>noch 28 Bezeichnungs- und Textfelder verbleiben &#8211; die Darstellung eines Zeitraums von vier Wochen sollte f&uuml;r normale Anwendungsf&auml;lle ausreichen.<\/li>\n<li>Die Bezeichnungsfelder, die aktuell Namen wie <b>Bezeichnungsfeld1<\/b>, <b>Bezeichnungsfeld2 <\/b>et cetera enthalten, werden durchlaufend umbenannt in <b>lbl01<\/b>, <b>lbl02 <\/b>und so weiter. Wichtig ist, dass die Zahlen zweistellig angegeben werden, also im Zweifel mit f&uuml;hrender <b>0<\/b>.<\/li>\n<li>Die Textfelder, deren Namen aktuell noch <b>01_08_2013<\/b>, <b>02_08_2013 <\/b>et cetera lauten, benennen wir in <b>txt01<\/b>, <b>txt02 <\/b>und so weiter um. Auch hier gilt: Zweistellige Zahlen angeben!<\/li>\n<li>Die Eigenschaft <b>Steuerelementinhalt <\/b>der Textfelder enth&auml;lt die gleichen Werte wie zuvor die Eigenschaft <b>Name<\/b>. Wenn wir die Kreuztabelle sp&auml;ter dynamisch gestalten, wird diese nat&uuml;rlich Felder mit anderen Namen liefern, die als <b>Steuerelementinhalt <\/b>verwendet werden sollen. Das ist aktuell aber kein Problem; den <b>Steuerelementinhalt <\/b>stellen wir sp&auml;ter per VBA ein.<\/li>\n<\/ul>\n<p><b>Kreuztabelle dynamisch<\/b><\/p>\n<p>Wie erw&auml;hnt, soll die Kreuztabelle Ihre Daten in Abh&auml;ngigkeit von einem bestimmten Startdatum liefern. Dazu muss diese mit einem Parameter ausgestattet werden. Bei herk&ouml;mmlichen Abfragen geben Sie Parameter im Entwurf der Abfrage einfach als Bezeichnung des Parameters in eckigen Klammern an. Bei Kreuztabellenabfragen gelingt dies jedoch nicht &#8211; wir haben verschiedenste Varianten ausprobiert. Das soll uns jedoch nicht davon abhalten, die Kreuztabelle dynamisch zu gestalten. Wir statten diese dennoch einfach mit den ben&ouml;tigten Parametern aus:<\/p>\n<pre>&gt;=[Startdatum] Und &lt;[Enddatum]<\/pre>\n<p>Nun ben&ouml;tigen wir eine Prozedur, welche das Startdatum entgegennimmt und dieses in die Platzhalter der Kreuztabellenabfrage einbaut. Diese Prozedur hei&szlig;t <b>KreuztabelleFuellen<\/b> und ist in Listing 3 zu finden.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>KreuztabelleFuellen(dat<span style=\"color:blue;\"> As Date<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Dim <\/span>qdf<span style=\"color:blue;\"> As <\/span>DAO.QueryDef\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     strSQL = db.QueryDefs(\"qryCTSchichten\").SQL\r\n     strSQL = <span style=\"color:blue;\">Replace<\/span>(strSQL, \"[Startdatum]\", ISODatum(dat))\r\n     strSQL = <span style=\"color:blue;\">Replace<\/span>(strSQL, \"[Enddatum]\", ISODatum(DateAdd(\"d\", 28, dat)))\r\n     Me!sfmSchichten.Form.RecordSource = strSQL\r\n     For i = 0 To 27\r\n         Me!sfmSchichten.Form(\"lbl\" & Format(i + 1, \"00\")).Caption = Format(DateAdd(\"d\", i, dat), \"d.m\")\r\n         Me!sfmSchichten.Form(\"txt\" & Format(i + 1, \"00\")).ControlSource = Format(DateAdd(\"d\", i, dat), \"dd_mm_yyyy\")\r\n         Me!sfmSchichten.Form(\"txt\" & Format(i + 1, \"00\")).Tag = DateAdd(\"d\", i, dat)\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n     SpaltenbreitenAnpassen\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 3: Kreuztabellenabfrage mit Parametern ausstatten<\/span><\/b><\/p>\n<p>Die Prozedur liest zun&auml;chst den in der Abfrage <b>qryCTSchichten<\/b> enthaltenen SQL-Code aus und schreibt diesen in die Variable <b>strSQL<\/b>. Theoretisch k&ouml;nnte man diesen SQL-Code auch gleich fest im Code verdrahten, aber in einer gespeicherten Abfrage lassen sich &auml;nderungen doch leichter durchf&uuml;hren.<\/p>\n<p>F&uuml;r den in der Variablen <b>strSQL <\/b>gespeicherten Ausdruck ersetzt die Abfrage zun&auml;chst den Platzhalter <b>[Startdatum] <\/b>durch das mit dem Parameter <b>dat <\/b>&uuml;bergebene Datum. Der Platzhalter <b>[Enddatum] <\/b>wird mit einem Datum versehen, das sich 28 Tage hinter dem Datum aus der Variablen <b>dat <\/b>befindet.<\/p>\n<p>Nach dem Ersetzen der Parameter der Kreuztabellenabfrage weist die Prozedur diese der Eigenschaft <b>RecordSource <\/b>des im Unterformularsteuerelement enthaltenen Formulars zu.<\/p>\n<p>Dann passt die Prozedur die Bezeichnungsfelder und Steuerelemente an, und zwar in einer <b>For&#8230;Next<\/b>-Schleife &uuml;ber die Werte von <b>0 <\/b>bis <b>27<\/b>. Innerhalb der Schleife geschehen die folgenden Schritte:<\/p>\n<ul>\n<li>Die Beschriftung der Bezeichnungsfelder wird auf das in dat gespeicherte Datum eingestellt, wobei dat erstens mit jedem Schleifendurchlauf um eins erh&ouml;ht wird und zweitens das Datum im Format <b>d.m <\/b>abgebildet wird. Das zu &auml;ndernde Bezeichnungsfeld wird schlie&szlig;lich mit <b>&#8222;lbl&#8220; &#038; Format(i + 1, &#8222;00&#8220;) <\/b>referenziert.<\/li>\n<li>Dann &auml;ndert die Prozedur die Eigenschaft <b>ControlSource<\/b>, also <b>Steuerelementinhalt<\/b>, des Textfeldes. Die Abfrage&uuml;berschriften werden ja automatisch auf die Werte des Feldes Arbeitstag f&uuml;r den angegebenen Zeitraum eingestellt, also etwa <b>01_08_2013<\/b>. Genau auf diesen Wert muss auch die Eigenschaft <b>Steu-erelementinhalt <\/b>eingestellt werden, damit es den Wert der Kreuztabellenabfrage f&uuml;r den entsprechenden Arbeitstag anzeigt.<\/li>\n<li>Schlie&szlig;lich stellt die Prozedur noch die <b>Tag<\/b>-Eigenschaft des jeweiligen Textfeldes auf das Datum f&uuml;r die aktuelle Spalte ein &#8211; wof&uuml;r dies ben&ouml;tigt wird, erfahren Sie sp&auml;ter.<\/li>\n<\/ul>\n<p>Das waren die notwendigen Anpassungen &#8211; anschlie&szlig;end ruft die Prozedur noch die Routine <b>SpaltenbreitenAnpassen <\/b>auf, welche die optimale Breite f&uuml;r die Spalten einstellt.<\/p>\n<p><b>Spaltenbreiten anpassen<\/b><\/p>\n<p>Das Anpassen der Spaltenbreiten erledigt die kleine Routine SpaltenbreitenAnpassen (s. Listing 4).<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>SpaltenbreitenAnpassen()\r\n     <span style=\"color:blue;\">Dim <\/span>ctl<span style=\"color:blue;\"> As <\/span>Control\r\n     For Each ctl In Me!sfmSchichten.Form.Controls\r\n         Select Case ctl.ControlType\r\n         <span style=\"color:blue;\">Case <\/span>acTextBox, acComboBox, acCheckBox\r\n         <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Len<\/span>(ctl.ControlSource) &gt; 0<span style=\"color:blue;\"> Then<\/span>\r\n             ctl.ColumnWidth = -2\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         <span style=\"color:blue;\">Case Else<\/span>\r\n         <span style=\"color:blue;\">Debug.Print<\/span> ctl.ControlType, ctl.Name\r\n         <span style=\"color:blue;\">End Select<\/span>\r\n     <span style=\"color:blue;\">Next<\/span> ctl\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><!--30percent--><\/p>\n<p><b><span style=\"color:darkgrey;\">Listing 4: Diese Prozedur bringt die Spalten in die optimale Breite zum Anzeigen ihres Inhalts.<\/span><\/b><\/p>\n<p>Dabei durchl&auml;uft sie in einer <b>For Each<\/b>-Schleife alle Steuerelemente des Unterformulars und pr&uuml;ft, ob diese den Typ <b>acTextbox<\/b>, <b>acCombobox <\/b>oder <b>acCheckbox <\/b>aufweisen. Ist dies der Fall, stellt sie f&uuml;r die Eigenschaft <b>ColumnWidth <\/b>den Wert <b>-2 <\/b>ein. Dies entspricht einer Breite, bei der alle aktuell sichtbaren Eintr&auml;ge angezeigt werden.<\/p>\n<p><b>Kreuztabelle nach Datumseingabe f&uuml;llen<\/b><\/p>\n<p>Wenn Sie nun in die Formularansicht wechseln, tut sich noch nichts &#8211; das Unterformular zeigt eine Reihe Fehler an.<\/p>\n<p>Damit sich dies &auml;ndert, m&uuml;ssen wir die Prozedur <b>KreuztabelleFuellen <\/b>auch noch ausl&ouml;sen. Dies soll beispielsweise geschehen, wenn der Benutzer ein Datum in das Textfeld <b>txtStartdatum <\/b>eintr&auml;gt. Dazu hinterlegen wir eine Prozedur, die durch das &auml;ndern des Wertes in <b>txtStartdatum <\/b>ausgel&ouml;st wird. Die Prozedur enth&auml;lt nur eine Anweisung, welche die Routine <b>KreuztabelleFuellen <\/b>aufruft und das Datum aus <b>txtStartdatum <\/b>als Parameter an die Routine &uuml;bergibt:<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>txtStartdatum_AfterUpdate()\r\n     KreuztabelleFuellen Me!txtStartdatum\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p>Dies zeigt zumindest die noch leeren Felder der Kreuztabellenabfrage an.<\/p>\n<p><b>Schichtplan bearbeiten<\/b><\/p>\n<p>Nun soll der Benutzer wie in Bild 8 &uuml;ber die Kreuztabelle auch noch die Schichten f&uuml;r die jeweilige Kombination aus Mitarbeiter und Arbeitstag einstellen k&ouml;nnen. Dies sollte optimalerweise so gelingen, dass der Benutzer einen Bereich der Kreuztabelle markiert und dort das Kontextmen&uuml; aufruft, um die entsprechende Schichtart festzulegen oder diese gegebenenfalls zu entfernen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_009.png\" alt=\"Der Benutzer kann per Kontextmen&uuml; die Schichten f&uuml;r einen oder mehrere Mitarbeiter und\/oder Arbeitstage festlegen.\" width=\"570\" height=\"315,9685\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 8: Der Benutzer kann per Kontextmen&uuml; die Schichten f&uuml;r einen oder mehrere Mitarbeiter und\/oder Arbeitstage festlegen.<\/span><\/b><\/p>\n<p>Dazu sind ein paar Vor&uuml;berlegungen n&ouml;tig. Die Erste ist: Wie bringen wir das Formular dazu, f&uuml;r jedes Element der Kreuztabelle im Datenblatt bei Rechtsklick ein Kontextmen&uuml; anzuzeigen<\/p>\n<p>Hier kommt wieder die &uuml;bliche Frage: Lege ich f&uuml;r das Ereignis <b>Bei Maustaste ab<\/b> eines jeden Steuerelements im Unterformular eine entsprechende Ereignisprozedur ab, die dann das Kontextmen&uuml; anzeigt (das w&auml;re eine Flei&szlig;arbeit mit viel Copy und Paste, bei der gern mal ein Fehler auftritt) oder investiere ich etwas Hirnschmalz und baue eine Klasse, die f&uuml;r alle Steuerelemente einmal erstellt wird und deren Ereignis <b>Bei Maustaste ab <\/b>implementiert Bei mir ist eher Hirnschmalz als Flei&szlig;arbeit angesagt, also verwenden wir hier die Version mit der Klasse.<\/p>\n<p>Das zweite Problem ist: Wie sollen wir erkennen, welche Felder der Benutzer im Datenblatt markiert hat &#8211; und wenn dies erledigt ist, wie lesen wir aus, zu welchem Mitarbeiter und zu welchem Arbeitstag die markierten Zellen geh&ouml;ren<\/p>\n<p>Erschwerend kommt hinzu, dass wir ja &uuml;ber die Kreuztabelle noch nicht einmal Zugriff auf die Prim&auml;rschl&uuml;sselwerte der jeweiligen Datens&auml;tze der Tabellen <b>tblMitarbeiter <\/b>und <b>tblArbeitstage <\/b>erhalten, denn die Kreuztabelle nimmt ja nur die angezeigten Daten auf. Wie auch immer: Das Problem konnte gel&ouml;st werden und Sie lesen auf den kommenden Seiten, wie es funktioniert.<\/p>\n<p><b>Ein Wrapper f&uuml;r jedes Wert-Feld<\/b><\/p>\n<p>Als Erstes ben&ouml;tigen wir eine <b>Collection<\/b>-Variable, welche alle Instanzen der noch zu erstellenden Klasse zum Kapseln der Felder der Kreuztabelle beziehungsweise der Steuerelemente des Unterformulars aufnimmt.<\/p>\n<p>Diese deklarieren wir wie folgt im Kopf des Klassenmoduls <b>Form_frmSchichten<\/b>:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>colTextboxes<span style=\"color:blue;\"> As <\/span>Collection<\/pre>\n<p>Desweiteren ben&ouml;tigen wir noch weitere Variablen, deren Bedeutung im Folgenden noch erl&auml;utert wird:<\/p>\n<pre><span style=\"color:blue;\">Dim <\/span>WithEvents objDSF<span style=\"color:blue;\"> As <\/span> clsDatasheetSelectionForm\r\n<span style=\"color:blue;\">Dim <\/span>WithEvents sfm<span style=\"color:blue;\"> As <\/span>Form\r\n<span style=\"color:blue;\">Dim <\/span>strMitarbeiter()<span style=\"color:blue;\"> As String<\/span>\r\n<span style=\"color:blue;\">Dim <\/span>datArbeitstage(28)<span style=\"color:blue;\"> As Date<\/span><\/pre>\n<p>Beim Laden des Formulars legen wir die Wrapper-Klassen f&uuml;r alle 28 Steuerelemente von <b>txt01 <\/b>bix <b>txt28 <\/b>an und weisen diesen jeweils einen Verweis auf das entsprechende Steuerelement zu.<\/p>\n<p>Dies erledigen wir in der durch das Ereignis <b>Beim Laden <\/b>ausgel&ouml;sten Ereignisprozedur aus Listing 5.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>Form_Load()\r\n     KreuztabelleFuellen Date\r\n     <span style=\"color:blue;\">Set<\/span> objDSF = <span style=\"color:blue;\">New<\/span> clsDatasheetSelectionForm\r\n     <span style=\"color:blue;\">With<\/span> objDSF\r\n         <span style=\"color:blue;\">Set<\/span> .Form = Me!sfmSchichten.Form\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     <span style=\"color:blue;\">Set<\/span> sfm = Me!sfmSchichten.Form\r\n     <span style=\"color:blue;\">With<\/span> sfm\r\n         .OnCurrent = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     sfm_Current\r\n     <span style=\"color:blue;\">Dim <\/span>rst<span style=\"color:blue;\"> As <\/span>DAO.Recordset\r\n     <span style=\"color:blue;\">Set<\/span> rst = sfm.RecordsetClone\r\n     <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> rst.EOF\r\n         ReDim Preserve strMitarbeiter(rst.AbsolutePosition + 1)\r\n         strMitarbeiter(rst.AbsolutePosition + 1) = rst!Mitarbeiter\r\n         rst.Move<span style=\"color:blue;\">Next<\/span>\r\n     <span style=\"color:blue;\">Loop<\/span>\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 5: Diese Ereignisprozedur wird beim Laden des Formulars ausgel&ouml;st.<\/span><\/b><\/p>\n<p>Die Prozedur deklariert eine Objektvariable namens <b>objTextbox<\/b>, die sp&auml;ter die auf Basis der Klasse <b>clsTextbox <\/b>erstellten Objekte referenziert, bevor diese in die <b>Collection <\/b>namens <b>colTextboxes <\/b>wandern. Dann ruft die Prozedur die Routine <b>KreuztabelleFuellen<\/b> mit dem aktuellen Datum auf, damit gleich nach dem &ouml;ffnen des Formulars entsprechende Daten angezeigt werden.<\/p>\n<p>Die folgenden Zeilen k&uuml;mmern sich um eine der oben aufgef&uuml;hrten, aber noch nicht beschriebenen Variablen: <b>objDSF <\/b>nimmt n&auml;mlich einen Verweis auf eine Instanz einer Klasse namens <b>clsData-sheetSelectionForm <\/b>auf.<\/p>\n<p>Diese Klasse wird im Beitrag <b>Datenblattmarkierungen <\/b>(<b>www.access-im-unternehmen.de\/901<\/b>) ausf&uuml;hrlich erl&auml;utert. Sie referenziert ein Formular in der Datenblattansicht und liefert nach Wunsch die Koordinaten der aktuellen Markierung im Datenblatt &#8211; entweder nach jeder &auml;nderung der Markierung oder nach Abruf der vier Eigenschaften <b>SelLeft, SelWidth<\/b>, <b>SelTop <\/b>und <b>SelHeight<\/b>.<\/p>\n<p>Wie wir diese einsetzen, erfahren Sie sp&auml;ter &#8211; aktuell reicht es, wenn Sie wissen, dass diese Klasse beim Laden des Formulars instanziert und mit einem Verweis auf das Unterformular <b>sfmSchichten <\/b>versorgt wird.<\/p>\n<p>Danach f&uuml;llt die Prozedur die mit dem Schl&uuml;sselwort <b>WithEvents <\/b>referenzierte Variable <b>sfm <\/b>ebenfalls mit einem Verweis auf das Unterformular <b>sfmSchichten<\/b>. Sie legt fest, dass das aktuelle Klassenmodul gegebenenfalls auf das Ereignis <b>Beim Anzeigen <\/b>des Unterformulars reagieren soll.<\/p>\n<p>Dann ruft die Prozedur genau die zu diesem Zweck angelegte Ereignisprozedur <b>sfm_Current <\/b>auf (Erl&auml;uterung siehe unten).<\/p>\n<p>Bevor wir uns um diese Prozedur k&uuml;mmern, stellt sich die Frage, warum wir diese im Klassenmodul des Hauptformulars implementieren. Dies ist einfach: Die Prozedur interagiert mit einigen Elementen, auf die auch vom Hauptformulars aus zugegriffen wird beziehungsweise die dort deklariert werden.<\/p>\n<p>Um unn&ouml;tige Abh&auml;ngigkeiten zwischen Haupt- und Unterformular zu vermeiden, haben wir direkt den kompletten Code im Hauptformular untergebracht.<\/p>\n<p>Bevor wir uns um die Prozedur <b>sfm_Current <\/b>k&uuml;mmern, schauen wir uns noch den Rest der Prozedur <b>Form_Load <\/b>an.<\/p>\n<p>Diese erstellt ein <b>Recordset<\/b>-Objekt auf Basis der <b>RecordsetClone<\/b>-Funktion des Unterformulars, also eine Kopie des dortigen Recordsets. In einer <b>Do While<\/b>-Schleife durchl&auml;uft die Prozedur alle Datens&auml;tze des <b>Recordset<\/b>-Objekts. Wie viele Datens&auml;tze hat eine Kreuztabelle aber nun eigentlich Nun: Genau so viele, wie die Datenherkunft Datens&auml;tze f&uuml;r die Zeilen&uuml;berschriften hergibt.<\/p>\n<p>In unserem Fall entspricht dies also der Anzahl der Mitarbeiter der Tabelle <b>tblMitarbeiter<\/b>. Beim Durchlaufen der Datensatzgruppe erweitert die Prozedur jeweils die als Array angelegte String-Variable <b>strMitarbeiter <\/b>auf die aktuelle Anzahl durchlaufener Datens&auml;tze und tr&auml;gt den Namen des Mitarbeiters des aktuellen Datensatzes f&uuml;r den entsprechenden Eintrag im Array ein.<\/p>\n<p>Ihnen ist noch nicht klar, was wir mit diesem Array anfangen Das wird gleich gekl&auml;rt.<\/p>\n<p><b>Wenn das Unterformular aktualisiert wird<\/b><\/p>\n<p>Das Ereignis <b>sfm_Current <\/b>wird ja nicht nur durch die <b>Form_Load<\/b>-Prozedur ausgef&uuml;hrt, sondern auch durch das Ereignis <b>Beim Anzeigen <\/b>des Unterformulars.<\/p>\n<p>Dieses wird sowohl ausgel&ouml;st, wenn der Benutzer den Datensatz wechselt als auch wenn dieser ein neues Datum in das Textfeld <b>txtStartdatum <\/b>eingibt und somit das Unterformular komplett neu f&uuml;llt.<\/p>\n<p>Die Prozedur aus Listing 6 ist f&uuml;r die Vorbereitung der Kontextmen&uuml;-Funktionalit&auml;t zum Anlegen von Schichtarten verantwortlich. Sie durchl&auml;uft dazu alle 28 Textfelder zur Anzeige der Schichtarten im Unterformular und erzeugt jeweils ein neues Objekt der Klasse <b>clsTextbox<\/b>. Die geschieht in einer <b>For&#8230;Next<\/b>-Schleife, welche mit der Laufvariablen <b>i <\/b>von <b>1 <\/b>bis <b>28 <\/b>durchlaufen wird. Dabei wird das neue <b>objTextbox<\/b>-Objekt erzeugt und mit einem Verweis auf das jeweilige Steuerelement gef&uuml;llt.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>sfm_Current()\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>objTextbox<span style=\"color:blue;\"> As <\/span>clsTextbox\r\n     <span style=\"color:blue;\">Set<\/span> colTextboxes = <span style=\"color:blue;\">New<\/span> Collection\r\n     For i = 1 To 28\r\n         <span style=\"color:blue;\">Set<\/span> objTextbox = <span style=\"color:blue;\">New<\/span> clsTextbox\r\n         <span style=\"color:blue;\">With<\/span> objTextbox\r\n             <span style=\"color:blue;\">Set<\/span> .Textbox = sfm.Controls(\"txt\" & Format(i, \"00\"))\r\n             datArbeitstage(i) = sfm.Controls(\"txt\" & Format(i, \"00\")).Tag\r\n         End <span style=\"color:blue;\">With<\/span>\r\n         colTextboxes.Add objTextbox, \"txt\" & Format(i, \"00\")\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 6: Die Beim Anzeigen-Ereignisprozedur des Unterformulars wird im Klassenmodul des Hauptformulars implementiert.<\/span><\/b><\/p>\n<p>Dieses landet dann im zuvor instanzierten <b>Collection<\/b>-Object <b>colTextboxes<\/b>. Derweil kommt auch die zweite weiter oben deklarierte Array-Variable namens <b>strArbeitstage <\/b>zum Zuge:<\/p>\n<p>Dieses wird n&auml;mlich mit dem in der <b>Tag<\/b>-Eigenschaft der Textfelder gespeicherten Datumswert f&uuml;r die aktuelle Spalte gef&uuml;llt.<\/p>\n<p>Damit ist die Aufgabe der Prozedur <b>sfm_Current<\/b> erf&uuml;llt &#8211; und die Vorbereitungen sind abgeschlossen.<\/p>\n<p>Nun fehlen noch die Erl&auml;uterungen der Klasse <b>clsTextbox<\/b>, welche die Kontextmen&uuml;s f&uuml;r die verschiedenen Textfelder bereitstellt, und der Prozedur, die durch das Bet&auml;tigen eines Kontextmen&uuml;-Eintrags ausgel&ouml;st wird.<\/p>\n<p><b>Die Klasse clsTextboxes<\/b><\/p>\n<p>Jedes Textfeld soll mit einer Funktion versehen werden, die f&uuml;r die Anzeige eines benutzerdefinierten Kontextmen&uuml;s beim Anklicken mit der rechten Maustaste sorgt.<\/p>\n<p>Dies erledigen wir, indem wir f&uuml;r jedes Textfeld ein Objekt auf Basis der Klasse <b>clsTextbox <\/b>erstellen, welche beim Rechtsklick ein entsprechendes Ereignis ausl&ouml;st und das Kontextmen&uuml; einblendet.<\/p>\n<p>Die Instanzierung der 28 Objekte haben wir uns weiter oben bereits angesehen, nun folgen die Details zur Klasse. Diese verwendet eine privat deklarierte Eigenschaft namens <b>m_Textbox<\/b>, um den Verweis auf das &uuml;bergebene Textfeld zu speichern.<\/p>\n<p>Die Variable ist mit dem Schl&uuml;sselwort <b>With-Events <\/b>deklariert, damit wir in dieser Klasse auch die Ereignisse des Textfeldes implementieren k&ouml;nnen:<\/p>\n<pre><span style=\"color:blue;\">Private <\/span>WithEvents m_Textbox<span style=\"color:blue;\"> As <\/span>Textbox<\/pre>\n<p>Damit die oben beschriebene Funktion <b>sfm_Current <\/b>dem Objekt einen Verweis auf das Textfeld &uuml;bergeben kann, definiert dieses eine entsprechende <b>Property Set<\/b>-Prozedur, die wie folgt aussieht:<\/p>\n<pre><span style=\"color:blue;\">Public Property <span style=\"color:blue;\">Set<\/span> <\/span>Textbox(txt<span style=\"color:blue;\"> As <\/span>Textbox)\r\n     <span style=\"color:blue;\">Set<\/span> m_Textbox = txt\r\n     <span style=\"color:blue;\">With<\/span> m_Textbox\r\n         .OnMouseUp = \"[Event Procedure]\"\r\n     End <span style=\"color:blue;\">With<\/span>\r\n<span style=\"color:blue;\">End Property<\/span><\/pre>\n<p>Hier legen Sie auch gleich fest, dass die aktuelle Klasse Code zum Reagieren auf das Ereignis <b>OnMouseUp <\/b>bereitstellt.<\/p>\n<p>Es fehlt noch die Implementierung des Ereignisses <b>Bei Maustaste Ab<\/b>. Diese sieht wie in Listing 7 aus. Damit Sie diese Prozedur verwenden k&ouml;nnen, m&uuml;ssen Sie dem VBA-Projekt einen Verweis auf die Bibliothek <b>Microsoft Office x.0 Object Library <\/b>hinzuf&uuml;gen (s. Bild 9). Diese stellt die Elemente zur Bearbeitung der Men&uuml;leisten zur Verf&uuml;gung.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_015.png\" alt=\"Verweis auf die Office-Bibliothek\" width=\"370\" height=\"286,0172\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 9: Verweis auf die Office-Bibliothek<\/span><\/b><\/p>\n<p>Die Ereignisprozedur  <b>m_Textbox_MouseUp <\/b>pr&uuml;ft zun&auml;chst &uuml;ber den Parameter <b>Button<\/b>, welche Maustaste der Benutzer &uuml;berhaupt bet&auml;tigt hat. Liefert <b>Button <\/b>den Wert <b>2<\/b>, entspricht dies der rechten Maustaste und das Kontextmen&uuml; soll angezeigt werden. In diesem Fall legt die Prozedur zun&auml;chst fest, dass das eingebaute Kontextmen&uuml; des &uuml;bergeordneten Elements deaktiviert werden soll.<\/p>\n<p>Da die Prozedur ein neues Kontextmen&uuml; erzeugen soll, muss ein eventuell vorhandenes Kontextmen&uuml; gleichen Namens eventuell zuvor gel&ouml;scht werden. Da dieses wom&ouml;glich gar nicht vorhanden ist, was beim Versuch, es zu l&ouml;schen, zu einem Fehler f&uuml;hren w&uuml;rde, deaktivieren wir w&auml;hrend des Aufrufs der <b>Delete<\/b>-Anweisung f&uuml;r das betroffene Element der <b>CommandBars<\/b>-Auflistung die Fehlerbehandlung.<\/p>\n<p>Dann f&uuml;gen wir mit der <b>Add<\/b>-Methode sogleich ein neues Exemplar gleichen Namens hinzu und stellen sein Aussehen auf <b>msoBarPopup <\/b>ein &#8211; also als Kontextmen&uuml;.<\/p>\n<p>Der Wert <b>True <\/b>f&uuml;r den letzten Parameter legt fest, dass es sich um ein tempor&auml;res Men&uuml; handelt, dessen Lebensdauer auf die aktuelle Sitzung beschr&auml;nkt ist.<\/p>\n<p>Danach &ouml;ffnet die Prozedur ein <b>Recordset<\/b>-Objekt auf Basis der Tabelle <b>tblSchichtarten <\/b>und liest alle Eintr&auml;ge ein. Sie durchl&auml;uft diese in einer <b>Do While<\/b>-Schleife und f&uuml;gt f&uuml;r jeden Datensatz einen Eintrag zum Kontextmen&uuml; hinzu.<\/p>\n<p>Die Beschriftung wird auf den Wert des Feldes <b>Schichtart <\/b>festgelegt, die mit <b>OnAction <\/b>festzulegende VBA-Funktion <b>SchichtartAendern <\/b>erh&auml;lt die <b>ID <\/b>der Schichtart als Parameter.<\/p>\n<p>Nach dem Durchlaufen der kompletten Datensatzgruppe f&uuml;gt die Prozedur einen weiteren Eintrag zum Kontextmen&uuml; hinzu, der das aktuelle Feld leeren soll und die Funktion <b>Schichtart <\/b>mit dem Wert <b>0 <\/b>als Parameter aufrufen soll. Schlie&szlig;lich sorgt die <b>Popup<\/b>-Methode f&uuml;r das Einblenden des Kontextmen&uuml;s.<\/p>\n<p><b>&auml;ndern der Schichtart per Kontextmen&uuml;<\/b><\/p>\n<p>Fehlt noch die Funktion, die nach der Auswahl der gew&uuml;nschten Schichtart nicht nur eines, sondern alle Felder entsprechend anpasst. Hier treffen wir auf das bereits erw&auml;hnte Problem, dass Kreuztabellenabfragen nur die angezeigten Daten bereithalten, sie aber nicht wie etwa bei einem Kombinationsfeld ein Feld anzeigen und das Prim&auml;rschl&uuml;sselfeld als erste, unsichtbare Spalte zur Abfrage des ausgew&auml;hlten Wertes verwenden k&ouml;nnen. Wir haben es also mit den Arbeitstagen als Spaltenk&ouml;pfen und mit den Mitarbeiterbezeichnungen als Zeilenk&ouml;pfen zu tun. Das ist prinzipiell auch kein Problem &#8211; wir haben ja beide Felder so eingestellt, dass die enthaltenen Werte eindeutig sind, also k&ouml;nnen wir etwa &uuml;ber die Mitarbeiterbezeichnung auch die MitarbeiterID ermitteln.<\/p>\n<p>Die Auswahl eines der Kontextmen&uuml;-Eintr&auml;ge l&ouml;st nun die Funktion <b>Schichtart-Aendern <\/b>aus Listing 8 aus. Die Funktion erwartet den Prim&auml;rschl&uuml;sselwert f&uuml;r die gew&auml;hlte Schichtart als Parameter. Sie verwendet zun&auml;chst die beim Laden des Formulars in <b>objDSF <\/b>gespeicherte Instanz der Klasse <b>clsDatasheetSelectionForm<\/b>, um die Koordinaten der Markierung im Datenblatt zu entnehmen.<\/p>\n<pre><span style=\"color:blue;\">Public Function <\/span>SchichtartAendern(lngSchichtartID<span style=\"color:blue;\"> As Long<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>intZeile<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intSpalte<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intSelLeft<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intSelWidth<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intSelTop<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>intSelHeight<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngMitarbeiterID<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngArbeitstagID<span style=\"color:blue;\"> As Long<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     <span style=\"color:blue;\">With<\/span> objDSF\r\n         intSel<span style=\"color:blue;\">Left<\/span> = .SelLeft\r\n         intSelWidth = .SelWidth\r\n         <span style=\"color:blue;\">If <\/span>intSelWidth = 0<span style=\"color:blue;\"> Then<\/span> intSelWidth = 1\r\n         intSelTop = .SelTop\r\n         intSelHeight = .SelHeight\r\n         <span style=\"color:blue;\">If <\/span>intSelHeight = 0<span style=\"color:blue;\"> Then<\/span> intSelHeight = 1\r\n         <span style=\"color:blue;\">If <\/span>intSel<span style=\"color:blue;\">Left<\/span> &lt; 3<span style=\"color:blue;\"> Then<\/span>\r\n             intSel<span style=\"color:blue;\">Left<\/span> = 3\r\n             intSelWidth = intSelWidth - 1\r\n         <span style=\"color:blue;\">End If<\/span>\r\n         For intSpalte = intSel<span style=\"color:blue;\">Left<\/span> To intSel<span style=\"color:blue;\">Left<\/span> + intSelWidth - 1\r\n             For intZeile = intSelTop To intSelTop + intSelHeight - 1\r\n                 lngMitarbeiterID = DLookup(\"MitarbeiterID\", \"tblMitarbeiter\", \"Mitarbeiter = ''''\" & strMitarbeiter(intZeile) _\r\n                     & \"''''\")\r\n                 lngArbeitstagID = DLookup(\"ArbeitstagID\", \"tblArbeitstage\", \"Arbeitstag = \" _\r\n                     & ISODatum(datArbeitstage(intSpalte - 2)))\r\n                 <span style=\"color:blue;\">If <\/span><span style=\"color:blue;\">Not<\/span> lngSchichtartID = 0<span style=\"color:blue;\"> Then<\/span>\r\n                     db.Execute \"UPDATE tblSchichten SET SchichtartID = \" & lngSchichtartID & \" WHERE ArbeitstagID = \" _\r\n                         & lngArbeitstagID & \" AND MitarbeiterID = \" & lngMitarbeiterID, dbFailOnError\r\n                 <span style=\"color:blue;\">Else<\/span>\r\n                     db.Execute \"UPDATE tblSchichten SET SchichtartID = NULL WHERE ArbeitstagID = \" & lngArbeitstagID _\r\n                         & \" AND MitarbeiterID = \" & lngMitarbeiterID, dbFailOnError\r\n                 <span style=\"color:blue;\">End If<\/span>\r\n             <span style=\"color:blue;\">Next<\/span> intZeile\r\n         <span style=\"color:blue;\">Next<\/span> intSpalte\r\n     End <span style=\"color:blue;\">With<\/span>\r\n     Me!sfmSchichten.Form.Requery\r\n     <span style=\"color:blue;\">Call<\/span> SpaltenbreitenAnpassen\r\n<span style=\"color:blue;\">End Function<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 8: &auml;ndern der Schichtart f&uuml;r einen oder mehrere Mitarbeiter\/Arbeitstage<\/span><\/b><\/p>\n<p>Hier kann es vorkommen, dass der Benutzer nur ein einziges Feld markiert hat &#8211; in diesem Falle liefern <b>SelWidth <\/b>und <b>SelHeight <\/b>beide den Wert <b>0<\/b>. Diese Werte m&uuml;ssen f&uuml;r das nachfolgende Durchlaufen zweier <b>For&#8230;Next<\/b>-Schleifen auf <b>1 <\/b>korrigiert werden.<\/p>\n<p>Wenn der Benutzer eine oder mehrere komplette Zeilen markiert hat, liefert <b>SelLeft <\/b>au&szlig;erdem den Wert <b>1<\/b>. Wie im Beitrag <b>Datenblattmarkierungen <\/b>zu lesen ist,  beginnt die Z&auml;hlung f&uuml;r die erste Spalte aber bei <b>2 <\/b>&#8211; und in unserem Falle sogar bei <b>3<\/b>, da die erste Spalte ja noch den Mitarbeiter enth&auml;lt. Sollte <b>SelLeft <\/b>einen Wert kleiner als <b>3 <\/b>liefern, wird dieser auf <b>3 <\/b>korrigiert. <b>SelWidth <\/b>muss dann gleichzeitig um <b>1 <\/b>vermindert werden.<\/p>\n<p>Anschlie&szlig;end starten die beiden <b>For&#8230;Next<\/b>-Schleifen, welche die markierten Elemente der Kreuztabelle durchlaufen &#8211; in der &auml;u&szlig;eren Schleife die Spalten, in der inneren die Zeilen. Diese Reihenfolge kann auch vertauscht werden.<\/p>\n<p>Die Funktion ermittelt dann per <b>DLook-up <\/b>f&uuml;r jeden Durchlauf die Prim&auml;rschl&uuml;sselwerte des Mitarbeiters und des Arbeitstages. Und hier kommen nun die beiden Arrays <b>strMitarbeiter() <\/b>und <b>strArbeitstage() <\/b>ins Spiel: Diese liefern n&auml;mlich die Werte der Spalten- und Zeilenk&ouml;pfe f&uuml;r die jeweiligen Koordinaten, die dann wiederum in den <b>DLookup<\/b>-Funktionen zur Gewinnung der Prim&auml;rschl&uuml;sselwerte <b>ArbeitstagID <\/b>und <b>MitarbeiterID <\/b>gewonnen werden k&ouml;nnen.<\/p>\n<p>Sollte die Funktion <b>SchichtartAendern <\/b>nicht mit dem Wert <b>0 <\/b>im Parameter <b>lngSchichtartID <\/b>aufgerufen worden sein, f&uuml;hrt sie im Anschluss eine <b>UPDATE<\/b>-Anweisung aus, welche die Schichtart f&uuml;r die aktuelle Kombination aus <b>MitarbeiterID <\/b>und <b>ArbeitstagID <\/b>in die Tabelle <b>tblSchichten <\/b>aktualisiert. Anderenfalls wird die <b>SchichtartID <\/b>auf den Wert <b>Null <\/b>eingestellt und somit geleert.<\/p>\n<p>Nach der Anpassung aller markierten Elemente aktualisiert die Funktion noch den Inhalt des Unterformulars und optimiert die Spaltenbreiten mit der Prozedur <b>SpaltenbreitenAnpassen<\/b>.<\/p>\n<p>Das war nicht trivial, zeigt aber, dass man auch mit Access-Bordmitteln meist zum gew&uuml;nschten Ziel kommt.<\/p>\n<p><b>Wochenendmarkierungen<\/b><\/p>\n<p>Wenn wir uns die aktuelle Version ansehen, fehlt doch noch etwas Entscheidendes: Der Schichtplan zeigt zwar die Datumsangaben an, aber keine Tage.<\/p>\n<p>Und es w&auml;re ja schon hilfreich, wenn man erkennen k&ouml;nnte, welche Schicht auf welchen Tag f&auml;llt.<\/p>\n<p>Nun k&ouml;nnten wir den &uuml;berschriften der Kreuztabelle noch die Tage hinzuf&uuml;gen. Dies erledigen Sie mit einem einzigen Schritt &#8211; n&auml;mlich durch Anpassen der folgenden Zeile in der Prozedur <b>KreuztabelleFuellen<\/b>:<\/p>\n<pre>Me!sfmSchichten.Form(\"lbl\" & Format(i + 1, \"00\")).Caption = Format(DateAdd(\"d\", i, dat), \"ddd, d.m.\")<\/pre>\n<p>Allerdings w&uuml;rde eine farbige Markierung der Wochenenden noch eine zus&auml;tzliche Orientierungshilfe bieten.<\/p>\n<p>Kein Problem: Wozu gibt es die bedingte Formatierung Probieren wir dies zun&auml;chst mithilfe der Benutzeroberfl&auml;che aus, und auch nur f&uuml;r ein einziges Feld &#8211; n&auml;mlich f&uuml;r <b>txt01<\/b>.<\/p>\n<p>Klicken Sie &#8211; gegebenenfalls auch in der Datenblattansicht, um das Ergebnis schneller zu sehen &#8211; auf den Ribbon-Eintrag <b>Datenblatt|Formatierung|Be-dingte Formatierung <\/b>und f&uuml;gen Sie zwei Bedingungen wie in Bild 10 hinzu.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_010.png\" alt=\"Hinzuf&uuml;gen zweier bedingter Formatierungen zu einem Textfeld\" width=\"670\" height=\"357,1108\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 10: Hinzuf&uuml;gen zweier bedingter Formatierungen zu einem Textfeld<\/span><\/b><\/p>\n<p>Der Ausdruck <b>Wochentag(txt01.Marke) = 1 <\/b>pr&uuml;ft, ob die Funktion <b>Wochentag <\/b>f&uuml;r das in der Eigenschaft <b>Tag<\/b>\/<b>Marke <\/b>des Textfeldes <b>txt01 <\/b>den Wert <b>1 <\/b>liefert &#8211; dies entspricht in der Standardeinstellung dem Wochentag <b>Sonntag<\/b>.<\/p>\n<p>Wenn wir f&uuml;r jedes Feld zwei solcher Bedingungen anlegen, wobei sich die Hintergrundfarbe f&uuml;r Samstag und Sonntag noch unterscheidet, m&uuml;ssten Sie nun 56 bedingte Formatierungen definieren.<\/p>\n<p>Das ist wiederum eine Flei&szlig;arbeit, von der wir uns ausdr&uuml;cklich distanzieren wollen. Also erledigen wir dies per VBA &#8211; und zwar mit der Prozedur <b>BedingteFormatierungen <\/b>aus dem Modul <b>mdlSchichten<\/b>.<\/p>\n<p>Die Prozedur &ouml;ffnet das Unterformular <b>sfmSchichten<\/b> in der Entwurfsansicht und durchl&auml;uft alle gebundenen Textfelder von <b>txt01 <\/b>bis <b>txt28<\/b>. Dabei weist sie jedem Textfeld zwei bedingte Formatierungen zu, wobei als Typ <b>acExpression <\/b>und als Ausdruck f&uuml;r den Sonntag beispielsweise <b>&#8222;Weekday(txt&#8220; &#038; Format(i, &#8222;00&#8220;) &#038; &#8222;.Tag)=1&#8220; <\/b>Verwendung findet. Zuvor entfernt die Prozedur in einer <b>Do While<\/b>-Schleife noch alle eventuell vorhandenen bedingten Formatierungen (s. Listing 9).<\/p>\n<pre><span style=\"color:blue;\">Public Sub <\/span>BedingteFormatierungen()\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>frm<span style=\"color:blue;\"> As <\/span>Form\r\n     <span style=\"color:blue;\">Dim <\/span>objFormatCondition<span style=\"color:blue;\"> As <\/span>FormatCondition\r\n     DoCmd.OpenForm \"sfmSchichten\", acDesign\r\n     <span style=\"color:blue;\">Set<\/span> frm = Forms!sfmSchichten\r\n     For i = 1 To 28\r\n         <span style=\"color:blue;\">Do While<\/span> <span style=\"color:blue;\">Not<\/span> frm.Controls(\"txt\" & Format(i, \"00\")).FormatConditions.Count = 0\r\n             frm.Controls(\"txt\" & Format(i, \"00\")).FormatConditions(0).Delete\r\n         <span style=\"color:blue;\">Loop<\/span>\r\n         <span style=\"color:blue;\">Set<\/span> objFormatCondition = frm.Controls(\"txt\" & Format(i, \"00\")).FormatConditions.Add(acExpression, , \"Weekday(txt\" _\r\n             & Format(i, \"00\") & \".Tag)=1\")\r\n         objFormatCondition.BackColor = 12566463\r\n         <span style=\"color:blue;\">Set<\/span> objFormatCondition = frm.Controls(\"txt\" & Format(i, \"00\")).FormatConditions.Add(acExpression, , \"Weekday(txt\" _\r\n             & Format(i, \"00\") & \".Tag)=7\")\r\n         objFormatCondition.BackColor = 14211288\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 9: Anlegen bedingter Formatierungen f&uuml;r die Wochenendtage<\/span><\/b><\/p>\n<p>Das Ergebnis sieht dann wie in Bild 11 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_011.png\" alt=\"Die Schicht&uuml;bersicht mit bedingter Formatierung\" width=\"570\" height=\"345,7867\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 11: Die Schicht&uuml;bersicht mit bedingter Formatierung<\/span><\/b><\/p>\n<p><b>Schicht-Bericht<\/b><\/p>\n<p>Nun sind l&auml;ngst nicht alle Betriebe, in denen im Schichtbetrieb gearbeitet wird, fl&auml;chendeckend mit Computern ausgestattet (was auch nicht n&ouml;tig ist), sodass die Ausgabe der Schichtpl&auml;ne in Berichtsform unabdingbar ist. Wir wollen zwei verschiedene Ausgaben erreichen:<\/p>\n<ul>\n<li>Eine &uuml;bersicht mit allen Mitarbeitern &uuml;ber einen Zeitraum etwa von vier Wochen und<\/li>\n<li>eine &uuml;bersicht f&uuml;r einen Mitarbeiter f&uuml;r das aktuelle Kalenderjahr.<\/li>\n<\/ul>\n<p><b>Schicht&uuml;bersicht mit allen Mitarbeitern<\/b><\/p>\n<p>Wenn wir die &uuml;bersicht aus dem Formular in einen Bericht &uuml;bertragen wollen, ben&ouml;tigen wir grunds&auml;tzlich die gleichen Steuerelemente. Allerdings baut der Bericht die Kreuztabelle nicht automatisch auf, da dieser keine Datenblattansicht kennt. Wir m&uuml;ssen dies also zu Fu&szlig; erledigen. Dazu erstellen Sie zun&auml;chst einen neuen Bericht. Um die aufwendige Benennung der 28 Bezeichnungs- und Textfelder zu vereinfachen, kopieren wir diese einfach aus dem Entwurf des Formulars <b>sfmSchichten<\/b> in den Berichtsentwurf. Danach ordnen wir die Steuerelemente so an, dass die Bezeichnungsfelder im Seitenkopf landen und die Textfelder im Detailbereich.<\/p>\n<p>Ab Access 2007 wird diese Aufgabe erheblich vereinfacht: Dort m&uuml;ssen Sie einfach nur noch alle Bezeichnungs- und Textfelder markieren (s. Bild 12) und dann den Ribbon-Befehl <b>Anordnen|Tabelle|Tabelle <\/b>anwenden. Das Ergebnis sieht dann wie in Bild 13 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_013.png\" alt=\"... um Bezeichnungsfelder und Textfelder anzuordnen und auf zwei Bereiche aufzuteilen.\" width=\"570\" height=\"179,9453\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 12: &#8230; um Bezeichnungsfelder und Textfelder anzuordnen und auf zwei Bereiche aufzuteilen.<\/span><\/b><\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_012.png\" alt=\"Von dieser Konstellation ben&ouml;tigen Sie ab Access 2007 nur einen Mausklick, ...\" width=\"570\" height=\"277,3563\"\/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 13: Von dieser Konstellation ben&ouml;tigen Sie ab Access 2007 nur einen Mausklick, &#8230;<\/span><\/b><\/p>\n<p>Der Bericht soll seine Daten im Querformat anzeigen, da wir so etwas mehr Platz haben. Die Bezeichungs- und Textfelder bringen wir noch auf eine entsprechende Breite, also etwa auf 0,8  Zentimeter.<\/p>\n<p>Auch dies ist unter Access 2007 und j&uuml;nger viel einfacher, wenn Sie alle Felder innerhalb einer Anordnung markieren und die Breite neu einstellen: Dann brauchen Sie die nun schmaleren Textfelder noch nicht einmal manuell zusammenzur&uuml;cken.<\/p>\n<p>Im Gegensatz zum Formular k&ouml;nnen wir im Bericht au&szlig;erdem die H&ouml;he der Spaltenk&ouml;pfe einstellen, sodass das K&uuml;rzel f&uuml;r den Tag und das Datum jeweils in einer eigenen Zeile erscheinen.<\/p>\n<p>Nun k&ouml;nnen wir aber, genau wie beim Formular, nicht direkt auf die Kreuztabellenabfrage als Datenherkunft zugreifen, da diese ja zuvor noch gefiltert werden muss.<\/p>\n<p>Dies wird beim Bericht noch etwas schwieriger, da dieser beim Entgegennehmen eines ad&auml;quaten SQL-Ausdrucks einen Fehler meldet: <b>Die Kreuztabelle einer nicht feststehenden Spalte kann nicht als Unterabfrage verwendet werden<\/b>.<\/p>\n<p>Als Workaround &auml;ndern wir den SQL-Ausdruck der Abfrage <b>qryCTSchichten <\/b>wie gew&uuml;nscht und speichern diese in einer neuen Abfrage namens <b>qryCTSchichtenBericht<\/b>.<\/p>\n<p>Grunds&auml;tzlich verwenden wir diesmal eine Ereignisprozedur, die beim &ouml;ffnen des Berichts ausgef&uuml;hrt wird und die wie in Listing 10 aussieht.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>Report_Open(Cancel<span style=\"color:blue;\"> As Integer<\/span>)\r\n     <span style=\"color:blue;\">Dim <\/span>db<span style=\"color:blue;\"> As <\/span>DAO.Database\r\n     <span style=\"color:blue;\">Dim <\/span>strSQL<span style=\"color:blue;\"> As String<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>i<span style=\"color:blue;\"> As Integer<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>datStart<span style=\"color:blue;\"> As Date<\/span>\r\n     datStart = Nz(Me.OpenArgs, Date)\r\n     <span style=\"color:blue;\">Set<\/span> db = CurrentDb\r\n     strSQL = db.QueryDefs(\"qryCTSchichten\").SQL\r\n     strSQL = <span style=\"color:blue;\">Replace<\/span>(strSQL, \"[Startdatum]\", ISODatum(datStart))\r\n     strSQL = <span style=\"color:blue;\">Replace<\/span>(strSQL, \"[Enddatum]\", ISODatum(DateAdd(\"d\", 28, datStart)))\r\n     On Error Resume <span style=\"color:blue;\">Next<\/span>\r\n     db.QueryDefs.Delete \"qryCTSchichtenBericht\"\r\n     <span style=\"color:blue;\">On Error GoTo<\/span> 0\r\n     db.CreateQueryDef \"qryCTSchichtenBericht\", strSQL\r\n     Me.RecordSource = \"qryCTSChichtenBericht\"\r\n     For i = 0 To 27\r\n         Me(\"lbl\" & Format(i + 1, \"00\")).Caption = Format(DateAdd(\"d\", i, datStart), \"ddd, d.m.\")\r\n         Me(\"txt\" & Format(i + 1, \"00\")).ControlSource = Format(DateAdd(\"d\", i, datStart), \"dd_mm_yyyy\")\r\n     <span style=\"color:blue;\">Next<\/span> i\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 10: F&uuml;llen der Datenherkunft des &uuml;bersichtsberichts beim &ouml;ffnen<\/span><\/b><\/p>\n<p>Die Prozedur fragt zun&auml;chst den Wert der Eigenschaft <b>OpenArgs <\/b>ab, die eventuell beim &ouml;ffnen mit dem Startdatum f&uuml;r den Bericht ausgezeichnet wurde.<\/p>\n<p>Ist OpenArgs leer, verwendet der Bericht das aktuelle Datum als Startdatum. Nun &ouml;ffnet sie, genau wie im Formular, die Abfrage <b>qryCTSchichten <\/b>und entnimmt den SQL-Ausdruck.<\/p>\n<p>Nach dem Ersetzen der Platzhalter durch das Start- und das Enddatum erzeugt die Prozedur eine neue Abfrage namens <b>qryCTSchichtenBericht <\/b>&#8211; aber nicht, ohne eine eventuell bereits existierende Abfrage gleichen Namens zuvor zu l&ouml;schen.<\/p>\n<p>Wie auch im Formular durchl&auml;uft die Prozedur nun alle Bezeichnungsfelder und Textfelder und passt sowohl die Bezeichnung als auch den Wert der Eigenschaft <b>Steuerelementinhalt <\/b>der Textfelder an.<\/p>\n<p>Das war es schon &#8211; der Bericht sieht nun wie in Bild 14 aus.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_014.png\" alt=\"Der Bericht zur Anzeige des Schichtplans f&uuml;r alle Mitarbeiter und einen begrenzten Zeitraum von 28 Tagen\" width=\"570\" height=\"244,8271\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 14: Der Bericht zur Anzeige des Schichtplans f&uuml;r alle Mitarbeiter und einen begrenzten Zeitraum von 28 Tagen<\/span><\/b><\/p>\n<p>Dort haben wir noch die alternative Zeilenfarbe auf wei&szlig; eingestellt, da dieses Feature hier eher st&ouml;rend wirkte.<\/p>\n<p><b>Schichtplan f&uuml;r einen Mitarbeiter<\/b><\/p>\n<p>Fehlt noch der &uuml;bersichtsplan f&uuml;r einen einzigen Mitarbeiter. Dieser soll die Schichten f&uuml;r einen gr&ouml;&szlig;eren Zeitraum, also beispielsweise ein komplettes Jahr, in einem Bericht abbilden. Dabei soll ein Monat immer in einer Zeile landen, die einzelnen Wochentage sollen jeweils in der gleichen Spalte dargestellt werden.<\/p>\n<p>Da diese Art der Darstellung eines Berichts auch f&uuml;r andere Anwendungen interessant ist, haben wir die Erstellung des Berichts in einen weiteren Beitrag namens <b>Jahres&uuml;bersicht per Bericht <\/b>(<b>www.access-im-unternehmen.de\/903<\/b>) ausgelagert.<\/p>\n<p>Die Anpassungen f&uuml;r die Anzeige der verschiedenen Schichten erledigen Sie wie folgt: Die im genannten Beitrag als Datenherkunft des Berichts verwendete Abfrage <b>qryJahresuebersicht<\/b> muss um die Schichtdaten des betroffenen Mitarbeiters erweitert werden. Die Abfrage soll dem Bereicht <b>rptJahresuebersicht <\/b>drei Felder liefern: ein Prim&auml;rschl&uuml;sselfeld namens <b>DatumID<\/b>, ein Datumsfeld namens <b>DatumWert <\/b>und ein Feld namens <b>AnzeigeWert <\/b>mit dem auszugebenden Text.<\/p>\n<p>Die Abfrage passen Sie dazu wie in Bild 15 an. F&uuml;gen Sie zun&auml;chst die beiden Tabellen <b>tblSchichten <\/b>und <b>tblSchichtarten <\/b>zur Abfrage hinzu.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_016.png\" alt=\"Datenherkunft f&uuml;r den Bericht zur Darstellung des Schichtplans f&uuml;r einen Mitarbeiter f&uuml;r ein Jahr\" width=\"570\" height=\"308,1741\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 15: Datenherkunft f&uuml;r den Bericht zur Darstellung des Schichtplans f&uuml;r einen Mitarbeiter f&uuml;r ein Jahr<\/span><\/b><\/p>\n<p>Das Feld <b>DatumID <\/b>behalten Sie bei. Das Feld <b>DatumWert <\/b>ist bereits mit dem Feld <b>Arbeitstag <\/b>belegt, das die Datumsangaben liefert (diese m&uuml;ssen f&uuml;r die anzuzeigenden Monate durchg&auml;ngig vorliegen). Damit der Bericht den Schichtplan genau f&uuml;r ein Jahr anzeigt, legen wir hier vorerst ein Kriterium wie das Folgende fest (sp&auml;ter geben wir dieses per Formular ein):<\/p>\n<pre>&gt;=#01.01.2014# Und &lt;#01.01.2015#<\/pre>\n<p>Das Feld <b>AnzeigeWert <\/b>soll den Inhalt des Feldes <b>Kuerzel <\/b>der Tabelle <b>tblSchichten <\/b>anzeigen &#8211; deshalb haben wir noch die Tabelle <b>tblSchichtarten <\/b>zur Abfrage hinzugef&uuml;gt. Damit die Abfrage alle Datens&auml;tze der Tabellen <b>tblArbeitstage <\/b>und <b>tblSchichten <\/b>anzeigt, m&uuml;ssen beide Tabellen f&uuml;r jeden Tag des gew&uuml;nschten Zeitraums je einen Datensatz enthalten. Allerdings wird nicht f&uuml;r jeden Tag eine Schicht festgelegt sein, das Feld <b>SchichtartID <\/b>der Tabelle <b>tblSchichten <\/b>enth&auml;lt also mitunter keinen Fremdschl&uuml;sselwert. Der entsprechende Datensatz der Tabelle <b>tblSchichten <\/b>ist dann nicht mit der Tabelle <b>tblSchichtarten <\/b>verkn&uuml;pft. Damit die Abfrage dennoch alle Tage ber&uuml;cksichtigt, muss die Verkn&uuml;pfung zwischen den beiden Tabellen <b>tblSchichten <\/b>und <b>tblSchichtarten <\/b>so definiert werden, dass alle Datens&auml;tze der Tabelle <b>tblSchichten <\/b>erscheinen &#8211; auch wenn die Tabelle <b>tblSchichtarten <\/b>keinen verkn&uuml;pften Datensatz enth&auml;lt.<\/p>\n<p>Zu guter Letzt m&uuml;ssen Sie das Ergebnis der Abfrage noch auf einen einzigen Mitarbeiter einschr&auml;nken &#8211; in diesem Fall durch das Kriterium <b>1 <\/b>f&uuml;r das Feld <b>MitarbeiterID<\/b>. Anderenfalls liefert die Abfrage die Datumsangaben m&ouml;glicherweise f&uuml;r mehr als einen Mitarbeiter. Bei zwei Mitarbeitern w&uuml;rden dann etwa f&uuml;r den Januar 2014 genau 62 Datens&auml;tze geliefert werden. Dies kann der Bericht allerdings nicht verarbeiten &#8211; er w&uuml;rde versuchen, 62 Tage f&uuml;r einen Monate anzuzeigen, was zu einem Fehler wegen zu kleiner Berichtsbreite f&uuml;hren w&uuml;rde.<\/p>\n<p>Wenn Sie den in dem oben genannten Beitrag entwickelten Bericht auf Basis auf der ge&auml;nderten Abfrage <b>qryJahresuebersicht<\/b> in der Seitenansicht &ouml;ffnen, liefert dies den Bericht aus Bild 16.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_017.png\" alt=\"Schichtplan in der Jahres&uuml;bersicht f&uuml;r einen Mitarbeiter\" width=\"700\" height=\"461,6758\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 16: Schichtplan in der Jahres&uuml;bersicht f&uuml;r einen Mitarbeiter<\/span><\/b><\/p>\n<p><b>Formular zum Aufruf der Berichte<\/b><\/p>\n<p>Nun fehlt noch ein Formular, mit dem Sie die Einstellungen f&uuml;r die Anzeige der einzelnen Berichte festlegen k&ouml;nnen.<\/p>\n<p>Beim Bericht <b>rptSchichtenuebersicht<\/b> reicht es, wenn Sie ein Datum der ersten anzuzeigenden Woche angeben. Der Bericht soll dann automatisch den Schichtplan f&uuml;r den vierw&ouml;chigen Zeitraum anzeigen, der mit der betroffenen Woche beginnt.<\/p>\n<p>Das Formular soll <b>frmBerichte<\/b> hei&szlig;en und sieht in der Entwurfsansicht wie in Bild 17 aus. Es enth&auml;lt zwei Bereiche, die zum Einstellen und &ouml;ffnen der Berichte <b>rptSchichtenUebersicht <\/b>und <b>rptJahresuebersicht <\/b>dienen.<\/p>\n<p class=\"image\"><img decoding=\"async\" src=\"..\/fileadmin\/_temp_\/2013_05\/pic_906_018.png\" alt=\"Formular zum &ouml;ffnen der Berichte in der Entwurfsansicht\" width=\"570\" height=\"366,7358\" \/><\/p>\n<p><b><span style=\"color:darkgrey;\">Bild 17: Formular zum &ouml;ffnen der Berichte in der Entwurfsansicht<\/span><\/b><\/p>\n<p>Das Textfeld <b>txtStartdatumUebersicht <\/b>nimmt das Datum entgegen, das sich in der ersten Woche der vierw&ouml;chigen Anzeige befindet. Ein Klick auf die Schaltfl&auml;che <b>cmdSchichtuebersichtOeffnen <\/b>zeigt den Bericht an.<\/p>\n<p>Der zweite Bereich enth&auml;lt zwei Kombinationsfelder, mit denen der Benutzer den Mitarbeiter und das Kalenderjahr ausw&auml;hlen kann, f&uuml;r das die Jahres&uuml;bersicht angezeigt werden soll. Ein Mausklick auf die Schaltfl&auml;che <b>cmdJahresuebersichtOeffnen <\/b>zeigt schlie&szlig;lich auch diesen Bericht an.<\/p>\n<p>Das Kombinationsfeld <b>cboMitarbeiter <\/b>verwendet die Tabelle <b>tblMitarbeiter <\/b>als Datensatzherkunft, das Kombinationsfeld <b>cboJahr <\/b>eine Abfrage, die alle in der Tabelle <b>tblArbeitstage <\/b>enthaltenen Jahre ermittelt.<\/p>\n<p>Beim Laden des Formulars wird das Textfeld <b>txtStartdatumUebersicht <\/b>mit dem aktuellen Datum gef&uuml;llt. F&uuml;r die beiden Kombinationsfelder stellt die Prozedur aus Listing 11 jeweils den ersten Wert der Datensatzherkunft ein.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>Form_Load()\r\n     Me!txtStartdatumUebersicht = Date\r\n     Me!cboMitarbeiter = Me!cboMitarbeiter.ItemData(0)\r\n     Me!cboJahr = Me!cboJahr.ItemData(0)\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 11: Einrichten der Steuerelemente beim &ouml;ffnen des Formulars<\/span><\/b><\/p>\n<p>Ein Klick auf die Schaltfl&auml;che zum Anzeigen der Schicht&uuml;bersicht l&ouml;st die Prozedur aus Listing 12 aus. Diese ermittelt das Datum des Montags der Woche des im Textfeld <b>txtStartdatumUebersicht <\/b>angegebenen Datums und speichert dieses in der Variablen <b>datStart<\/b>. <b>datStart <\/b>wird beim Aufruf des Berichts <b>rptSchichtenuebersicht <\/b>als &ouml;ffnungsargument &uuml;bergeben. Der Bericht wertet dieses entsprechend aus.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdSchichtuebersichtOeffnen_Click()\r\n     <span style=\"color:blue;\">Dim <\/span>datStart<span style=\"color:blue;\"> As Date<\/span>\r\n     datStart = DateAdd(\"d\", -Weekday(Me!txtStartdatumUebersicht, vbMonday) + 1, Me!txtStartdatumUebersicht)\r\n     DoCmd.OpenReport \"rptSchichtenuebersicht\", OpenArgs:=datStart, View:=acViewPreview\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 12: Diese Prozedur &ouml;ffnet den Schichtplan f&uuml;r die gew&uuml;nschten vier Wochen.<\/span><\/b><\/p>\n<p>Die Schaltfl&auml;che zum Anzeigen der Jahres&uuml;bersicht &uuml;ber die Schichten eines ausgew&auml;hlten Mitarbeiters startet die Prozedur aus Listing 13. Die Prozedur wertet den Inhalt des Kombinationsfeldes <b>cboJahr <\/b>aus und tr&auml;gt den ersten Tag des Jahres in die Variable <b>datStart <\/b>und den ersten Tag des Folgejahres in die Variable <b>datEnde <\/b>ein. Den Mitarbeiter, dessen Schichten angezeigt werden sollen, entnimmt die Prozedur dem Kombinationfeld <b>cboMitarbeiter<\/b>.<\/p>\n<pre><span style=\"color:blue;\">Private Sub <\/span>cmdJahresuebersichtOeffnen_Click()\r\n     <span style=\"color:blue;\">Dim <\/span>datStart<span style=\"color:blue;\"> As Date<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>datEnde<span style=\"color:blue;\"> As Date<\/span>\r\n     <span style=\"color:blue;\">Dim <\/span>lngMitarbeiterID<span style=\"color:blue;\"> As Long<\/span>\r\n     datStart = DateSerial(Me!cboJahr, 1, 1)\r\n     datEnde = DateSerial(Me!cboJahr, 12, 31) + 1\r\n     lngMitarbeiterID = Me!cboMitarbeiter\r\n     DoCmd.OpenReport \"rptJahresuebersicht\", WhereCondition:=\"MitarbeiterID = \" & lngMitarbeiterID & \" AND DatumWert &gt;= \" _\r\n         & ISODatum(datStart) & \" AND DatumWert &lt; \" & ISODatum(datEnde), View:=acViewPreview\r\n<span style=\"color:blue;\">End Sub<\/span><\/pre>\n<p><b><span style=\"color:darkgrey;\">Listing 13: Prozedur zum Anzeigen der Jahres&uuml;bersicht f&uuml;r einen Mitarbeiter<\/span><\/b><\/p>\n<p>Beim &ouml;ffnen des Berichts <b>rptJahresuebersicht<\/b> &uuml;bergibt die Prozedur eine <b>WhereCondition<\/b>, die den Mitarbeiter festlegt und das Datum so einschr&auml;nkt, dass nur Tage des angegebenen Jahres ber&uuml;cksichtigt werden.<\/p>\n<p>Beide Berichte werden standardm&auml;&szlig;ig in der Vorschauansicht ge&ouml;ffnet.<\/p>\n<p><b>Zusammenfassung und Ausblick<\/b><\/p>\n<p>Mit der vorgestellten L&ouml;sung k&ouml;nnen Sie bequem Schichten eintragen und diese in Berichten anzeigen. Die Beschreibung des Dialogs finden Sie unter <b>Persistente Mehrfachauswahl mit Klasse  <\/b>(<b>www.access-im-unternehmen.de\/902<\/b>). Auf Basis der beiden Berichte lassen sich weitere Ansichten ableiten &#8211; beispielsweise mit farbigen Markierungen f&uuml;r die verschiedenen Schichten.<\/p>\n<h3>Downloads zu diesem Beitrag<\/h3>\n<p>Enthaltene Beispieldateien:<\/p>\n<p>Schichtplaner.mdb<\/p>\n<p><a href=\"..\/fileadmin\/beispiele\/{0D7F9F78-14EB-4758-BC57-01C4E31CC860}\/aiu_906.zip\">Download<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Wenn ein Arbeitsplatz nicht nur zu gew&ouml;hnlichen Arbeitszeiten besetzt sein muss, sondern im Extremfall den kompletten Tag, m&uuml;ssen mehrere Mitarbeiter bereitstehen. Diese werden dann in mehreren Schichten &uuml;ber den Tag verteilt. Dabei ist es gar nicht so einfach, nicht den &Uuml;berblick zu verlieren &#8211; immerhin sollen alle Mitarbeiter im Laufe eines Zeitraums m&ouml;glichst die angestrebte Anzahl Schichten durchf&uuml;hren. Gleichzeitig sollen zu jeder Zeit ausreichend Mitarbeiter arbeiten. Was liegt da n&auml;her, als dieses Problem mit einer geeigneten Access-Datenbank anzugehen<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[662013,66052013,44000027],"tags":[],"class_list":["post-55000906","post","type-post","status-publish","format-standard","hentry","category-662013","category-66052013","category-Loesungen"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v20.9 (Yoast SEO v27.4) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Schichtplaner - Access im Unternehmen<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/access-im-unternehmen.de\/Schichtplaner\/\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Schichtplaner\" \/>\n<meta property=\"og:description\" content=\"Wenn ein Arbeitsplatz nicht nur zu gew&ouml;hnlichen Arbeitszeiten besetzt sein muss, sondern im Extremfall den kompletten Tag, m&uuml;ssen mehrere Mitarbeiter bereitstehen. Diese werden dann in mehreren Schichten &uuml;ber den Tag verteilt. Dabei ist es gar nicht so einfach, nicht den &Uuml;berblick zu verlieren - immerhin sollen alle Mitarbeiter im Laufe eines Zeitraums m&ouml;glichst die angestrebte Anzahl Schichten durchf&uuml;hren. Gleichzeitig sollen zu jeder Zeit ausreichend Mitarbeiter arbeiten. Was liegt da n&auml;her, als dieses Problem mit einer geeigneten Access-Datenbank anzugehen\" \/>\n<meta property=\"og:url\" content=\"https:\/\/access-im-unternehmen.de\/Schichtplaner\/\" \/>\n<meta property=\"og:site_name\" content=\"Access im Unternehmen\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-22T21:30:59+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62\" \/>\n<meta name=\"author\" content=\"Andr\u00e9 Minhorst\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Verfasst von\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andr\u00e9 Minhorst\" \/>\n\t<meta name=\"twitter:label2\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data2\" content=\"40\u00a0Minuten\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/\"},\"author\":{\"name\":\"Andr\u00e9 Minhorst\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\"},\"headline\":\"Schichtplaner\",\"datePublished\":\"2020-05-22T21:30:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/\"},\"wordCount\":7018,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/e65adcf4674949eca1ef7cfbe01a6f62\",\"articleSection\":[\"2013\",\"5\\\/2013\",\"L\u00f6sungen\"],\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/\",\"name\":\"Schichtplaner - Access im Unternehmen\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#primaryimage\"},\"thumbnailUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/e65adcf4674949eca1ef7cfbe01a6f62\",\"datePublished\":\"2020-05-22T21:30:59+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#breadcrumb\"},\"inLanguage\":\"de\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#primaryimage\",\"url\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/e65adcf4674949eca1ef7cfbe01a6f62\",\"contentUrl\":\"http:\\\/\\\/vg05.met.vgwort.de\\\/na\\\/e65adcf4674949eca1ef7cfbe01a6f62\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/Schichtplaner\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Schichtplaner\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#website\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"name\":\"Access im Unternehmen\",\"description\":\"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access\",\"publisher\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/access-im-unternehmen.de\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"de\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#organization\",\"name\":\"Andr\u00e9 Minhorst Verlag\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"contentUrl\":\"https:\\\/\\\/access-im-unternehmen.de\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/aiu_wp.png\",\"width\":370,\"height\":111,\"caption\":\"Andr\u00e9 Minhorst Verlag\"},\"image\":{\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/access-im-unternehmen.de\\\/#\\\/schema\\\/person\\\/13395c4bcd7d7963efe33be9c584d93f\",\"name\":\"Andr\u00e9 Minhorst\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"de\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g\",\"caption\":\"Andr\u00e9 Minhorst\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Schichtplaner - Access im Unternehmen","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/","og_locale":"de_DE","og_type":"article","og_title":"Schichtplaner","og_description":"Wenn ein Arbeitsplatz nicht nur zu gew&ouml;hnlichen Arbeitszeiten besetzt sein muss, sondern im Extremfall den kompletten Tag, m&uuml;ssen mehrere Mitarbeiter bereitstehen. Diese werden dann in mehreren Schichten &uuml;ber den Tag verteilt. Dabei ist es gar nicht so einfach, nicht den &Uuml;berblick zu verlieren - immerhin sollen alle Mitarbeiter im Laufe eines Zeitraums m&ouml;glichst die angestrebte Anzahl Schichten durchf&uuml;hren. Gleichzeitig sollen zu jeder Zeit ausreichend Mitarbeiter arbeiten. Was liegt da n&auml;her, als dieses Problem mit einer geeigneten Access-Datenbank anzugehen","og_url":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/","og_site_name":"Access im Unternehmen","article_published_time":"2020-05-22T21:30:59+00:00","og_image":[{"url":"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62","type":"","width":"","height":""}],"author":"Andr\u00e9 Minhorst","twitter_card":"summary_large_image","twitter_misc":{"Verfasst von":"Andr\u00e9 Minhorst","Gesch\u00e4tzte Lesezeit":"40\u00a0Minuten"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#article","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/"},"author":{"name":"Andr\u00e9 Minhorst","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f"},"headline":"Schichtplaner","datePublished":"2020-05-22T21:30:59+00:00","mainEntityOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/"},"wordCount":7018,"commentCount":0,"publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#primaryimage"},"thumbnailUrl":"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62","articleSection":["2013","5\/2013","L\u00f6sungen"],"inLanguage":"de","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/access-im-unternehmen.de\/Schichtplaner\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/","url":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/","name":"Schichtplaner - Access im Unternehmen","isPartOf":{"@id":"https:\/\/access-im-unternehmen.de\/#website"},"primaryImageOfPage":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#primaryimage"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#primaryimage"},"thumbnailUrl":"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62","datePublished":"2020-05-22T21:30:59+00:00","breadcrumb":{"@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/access-im-unternehmen.de\/Schichtplaner\/"]}]},{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#primaryimage","url":"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62","contentUrl":"http:\/\/vg05.met.vgwort.de\/na\/e65adcf4674949eca1ef7cfbe01a6f62"},{"@type":"BreadcrumbList","@id":"https:\/\/access-im-unternehmen.de\/Schichtplaner\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/access-im-unternehmen.de\/"},{"@type":"ListItem","position":2,"name":"Schichtplaner"}]},{"@type":"WebSite","@id":"https:\/\/access-im-unternehmen.de\/#website","url":"https:\/\/access-im-unternehmen.de\/","name":"Access im Unternehmen","description":"Das Magazin f\u00fcr Datenbankentwickler auf Basis von Microsoft Access","publisher":{"@id":"https:\/\/access-im-unternehmen.de\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/access-im-unternehmen.de\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"},{"@type":"Organization","@id":"https:\/\/access-im-unternehmen.de\/#organization","name":"Andr\u00e9 Minhorst Verlag","url":"https:\/\/access-im-unternehmen.de\/","logo":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/","url":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","contentUrl":"https:\/\/access-im-unternehmen.de\/wp-content\/uploads\/2019\/09\/aiu_wp.png","width":370,"height":111,"caption":"Andr\u00e9 Minhorst Verlag"},"image":{"@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/access-im-unternehmen.de\/#\/schema\/person\/13395c4bcd7d7963efe33be9c584d93f","name":"Andr\u00e9 Minhorst","image":{"@type":"ImageObject","inLanguage":"de","@id":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1b9d010cf1716692cb9c34f21554e07d17d461acaea5b61b8cb21cbec678d48a?s=96&d=mm&r=g","caption":"Andr\u00e9 Minhorst"}}]}},"_links":{"self":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000906","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/comments?post=55000906"}],"version-history":[{"count":0,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/posts\/55000906\/revisions"}],"wp:attachment":[{"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/media?parent=55000906"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/categories?post=55000906"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/access-im-unternehmen.de\/data\/wp\/v2\/tags?post=55000906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}