infobase: EDV - MS-Access


Formulare

aufrufen   Quelle: dmt   Datum: 10.2004   nach oben

FORMULARE per CODE aufrufen und steuern:

Hier kann man wirklich verzweifeln, aber mit ein bißchen Praxis gehts dann schon.

Und bevor es einem so richtig schlecht wird, erst mal was positives:

Wenn bei 'DoCmd OpenForm' im Parameter FILTER z.B. der Name einer gültigen Abfrage
eingegeben wird, erscheinen die Datensätze gemäß den Abfragebedingungen der angegebenen Abfrage. Mit dem Menü-Befehl 'Datensätze | Alle Datensätze anzeigen' erinnert sich das Formular wieder an seine eigene Datenquelle. Auch zum Thema Geschwindigkeit gibts keine Klagen.


Problem 1:
Ein Formular wird geöffnet und ihm soll eine variable Datensatzquelle zugeordnet werden. Das tut, aber alle Stadien der Code-Zuweisungen sind am Bildschirm nachverfolgbar.

Abhilfe:
In der Sub nur die Zeile 'DoCmd OpenForm "Name"'.
Alle weiteren Manipulationen müssen dann innerhalb dieses Formulare in FormOpen abgewickelt werden. Wird die Einschränkung der Datensätze im externen Modul nach der Anweisung 'DoCmd OpenForm' vorgenommen, wird nämlich zuerst das Formular geöffnet und die zugrundeliegenden Daten angezeigt ( und alle Anweisungen, die in FormOpen stehen ), bevor die nächste Zeile des aufrufenden Modules abgearbeitet wird. Abhängig davon, wie ACCESS bzw. die GDI-Engine gerade Lust haben, Bildschirmdaten überhaupt anzuzeigen, werden die Ergebnisse von Anweisungen oft gar nicht angezeigt ( bis zu einem demütigenden 'Me!Repaint' ). Wenn man aber wie oben beschrieben, nach 'DoCmd OpenForm' weitere Anweisungen ausführen lassen möchte, werden die einzelnen Stadien natürlich in aller Deutlichkeit auf dem Monitor abgelichtet.

Ein Application.Echo False hat da aber auch schon Wunder bewirkt.


Problem 2:
Das Darstellen eines bestimmten Datensatzes in einer Liste mehrerer, die sogar noch sortiert sein sollen.

Abhilfe:Wenn die zugrundeliegende Tabelle mit der zugewiesenen, dynamischen Datenquelle identisch ist, sollte die Sortierung im Formular-Eigenschaftenfenster eingestellt werden.

Das Finden, Übergeben und Darstellen des gewünschten Datensatzes geht nach folgendem Muster vonstatten:

Sub Form_Open (Cancel As Integer)

    If Not IsNull(Me.Openargs) Then
       Me.Recordsetclone.FindFirst "ID='" + Me.Openargs + "'"
       s$ = Me.Recordsetclone.Bookmark
       Me.Bookmark = s$
    End If

End Sub

Problem 3:
Eine Routine ruft im Code per 'DoCmd OpenForm "Name" ' ein Formular auf.
Dort wird im Ereignis FormOpen durch eine Prüfung das Flag 'Cancel' gesetzt, weil irgendwas schiefging. Das Ereignis 'FormOpen' wird zwar brav gecancelt, aber der Befehl 'DoCmd OpenForm' löst dennoch einen Fehler aus.

Abhilfe:In der Routine, in der die Anweisung 'DoCmd OpenForm' steht, muß ein Fehlerbehandler eingebaut werden, der diesen Fall mit einem 'Resume Next' getrost abhandeln kann.


Problem 4:
Ein Formular wird wie oben beschrieben aufgerufen, um einen in einem Quellformular eingestellten Datensatz anzuzeigen. Der Benutzer hat die Möglichkeit, zwischen den zur Verfügung gestellten Datensätzen zu blättern. Unter Umständen ist es sinnvoll, beim Verlassen des Formular den zuletzt aktiven Datensatz zurückzugeben.

Abhilfe:
Im HP_VHP-Manager in Form!Symbole->Form_Close ist dies gründlich mißlungen. Es wurde stets der ersten Werte der Datensatzliste zurückgegeben. Das gewünschte, zuletzt aktive ID mußte idiotischerweise per Form_Currentan eine Formular-globale Variable übergeben werden, da bei Form_Close anscheinend eine Art Gedächtnis-Verlust für die angezeigten Daten eintritt !


Problem 5:
Aus einem daten-zeigenden Formular soll per PushButton ein zweites Formular geöffnet werden, das aus einer Tabelle, die Zusatz-Daten enthält, den zum Datensatz des Mutterformulares gehörenden Zusatz-Datensatz anzeigt.

Ein Aufruf a'la 'DoCmd OpenForm "Formularname",,,WhereBedingung' führt zwar zum gewünschten Ergebnis. Doch wenn der Fokus im Zusatzdaten-Formular per <Bild ab> oder <Tab> oder <Datensatzmarkierer vorwärts> vom Formular weg zum nächsten Datensatz bewegt wird, tritt ein wie üblich merkwürdiges Phänomen auf. Durch den obigen Aufruf wird die Datenherkunft auf '1 von 1 Datensätze' gesetzt, wie auch die Navigationsschaltflächen bestätigen.
Durch die genannten Eingaben erhält das Formular aber wieder Kenntnis aller in der Tabelle enthaltenen Datensätze.
Ein simples 'Gehe zum letzten Datensatz' erzeugt diesen Fehler aber nicht.

Abhilfe:
Die gewünschte SQL-Einschränkung darf nicht per WHERE-Parameter, sondern sollte als Öffnungsargument

'DoCmd OpenForm ,,,,,, SQL-String'
übergeben werden. In FormOpen des aufgerufenen Formulares wird dies dann aufbewährte Weise abgecheckt. Das Formular erhält und behält nun die Datensatzmenge '1 von 1 Datensätze'. Beim Vorwärtsbewegen ist zwar noch ein verunsicherndes Flackern im Formular zu sehen, so als ob ACCESS es doch noch verbocken möchte, aber der Fehler tritt nicht mehr auf.

Diesem Problem könnte man auch über ein Ereignis-gebundenes Wechseln der DefaultEditing-Eigenschaft begegnen ( je nach
Anwendungsfall ). Hierbei werden allerdings die Daten neu sortiert, wenn im Code DefaultEditing auf 4 (Keine neuen Datensätze) gesetzt wird. Außerdem muß leider festgestellt werden, daß ein Code-mäßiges Einstellen auf DefaultEditing=2 (bearbeiten zulassen) nicht ausgeführt wird, wenn das Formular im Entwursmodus mit der Eigenschaft 'Keine neuen Datensätze' gespeichert wurde. So ein Dreck !


Problem 5 b:
In der neueren dat013kl.mdb, bei der alle Aufrufe des Ventil-Formularesauf den Krit-Parameter abgestellt sind, wird in FormOpen eine Sub Formular_sperren ausgeführt, die im Nicht-Master-Mode die Eigenschaften AllowEditing und DefaultEditing setzte. Über die einen einzigen Datensatz liefernde 'Direkt'-Suche ging immer alles klar, wurde aber mehr als 1 Datensatz gefunden, führte das Ändern der Eigenschaft AllowEditing zur Anzeige aller Datensätze. Interessanterweise reicht in diesem Falle das Einstellen von DefaultEditing von FormOpen aus völlig, um den gewünschten Schreibschutz zu gewährleisten.


Problem 6:
Im HP/VHP-Manager kann für einen Ventil-Datensatz ein sogenanntes Extra-Memo-Formular (System-Dialog) geöffnet werden, indem ein Extra-Memofeld sowie ein OLE-Feld zur Verfügung steht. Nachdem für das Extra-Memofeld der Aufruf eines Vollbild-Formulares gewünscht wurde, das aber selbst keinen System-Dialog-Status benötigt (sonst keine Menüleiste), mußte das erste Extra-Memo-Formular ausgeblendet und beim Schließen des Vollbildformula-res wieder eingeblendet werden. So weit, so gut. Nach Schließen des eigentlichen Extra-Memo-Formulares mußte aber leider festgestellt werden, daß zwar das zugrundeliegende Ventilformular wieder aufgetaucht, aber ohne zugehörige Symbolleiste.

Ursache:
In diesem Fall hat Access wieder mal die Kontrolle über die eigenen Objekte verloren.

Abhilfe:
Über Forms(Forms.Count-2).SetFocus im Ereignis Form_Close des Dialog-Formulares kann das Formular, daß diesem zugrunde liegt, zwangsweise fokussiert werden.


Problem 7:
Ein Formular besitzt die Eigenschaft DefaultEditing=NurLesen.
Beim Aufruf des Formulares läßt sich aber der erste angezeigte Datensatz löschen. Hat man zuerst die Ansicht gewechselt, erinnert sich das Formular wieder an seine Schreibgeschütztheit.

Ursache:
Wenn der Aufruf des Formulares per Code mit einem einfachen

DoCmd OpenForm Name
erfolgt, wird z.Bsp. diese Eigenschaft überschrieben.
Ein Aufruf a'la
DoCmd OpenForm Name , , , , A_READONLY
setzt die sowieso vergebene Eigenschaft von Anfang an auf den richtigen Wert. Bei näherem Hinsehen erweisen sich solche Sachen einfach nur als Scheiße. Ein einfaches 'DoCmd OpenForm' müßte ein Formular mit den gespeicherten Standard-Einstellungen öffnen, so, wie wenn es aus dem Datenbankfenster heraus geöffnet worden wäre.


Problem 8:
Es kann vorkommen, daß ein Formular nur einen Teil des Bildschirms ausfüllt. Wenn der Anwender auf dahinterliegende Formulare klicken würde, wäre der Zugriff auf das gewünschte Formular verloren. Das Einstellen der Eigenschaften Popup und Gebunden ist aber nicht möglich, wenn man den Gebrauch der Menüleisten weiterhin erlauben will.

Abhilfe:
Eine witzige Lösung aus dem Hause DMT besteht darin, in Form_Open alle geöffneten Formulare bis auf dieses unsichtbar zu machen.

    For i% = 0 To Forms.Count - 2
        Forms(i%).Visible = False
    Next i%

Das kommt auch Ergonomie-mäßig gut, da die Augen des Anwenders lediglich das kleine Formular ohne umliegende Informationen verarbeiten muß.

Beim Schließen dieses Formulares können die unsichtbaren Fenster wieder angezeigt werden. Sollten sich darunter Popup- oder Modal-Fenster befinden, die durch nachfolgende, normale Formulare bereits ausgeblendet wurden, werden diese beim Einblenden übergangen, da sie, wenn die Kette der Fenster vom Anwender wieder geschlossen wird, (hoffentlich) wieder eingeblendet werden, ansonsten würde ein wiedereingeblendetes Modal-Formular die Berabeitung eines folgenden, normalen nicht verhindern.

    ' Alle Formulare, die weder Popup, noch modal sind,
    ' wieder einblenden.

    For i% = 0 To Forms.Count - 2
       If Forms(i%).Popup = False And Forms(i%).Modal = False Then
           Forms(i%).Visible = True
        End If
    Next i%


Problem 9:
Sehr schöne Dinge kann man mit dem Ereignis 'Resize' anstellen (siehe das Formular Information in hp_vhp.mdb).
Störend war nur, daß es mir nicht gelang, bei reinem Anschauen und Ok-Button-Betätigen das Schreiben des Datensatzes auf die Festplatte zu verhindern. Konnte behoben werden.

Ursache:
Das Ereignis Form_Resize tritt ( zumindest hier ) allein schon beim Öffnen des Formulares zweimal auf.


Problem 10:
Ein Formular wird mit 'DoCmd OpenForm' und dem Parameter Datenmodus 'A_READONLY' geöffnet. Das funktioniert, wie auch die häkchenlose MenüOption 'Datensätze | Datensätze bearbeiten' anzeigt. Wird jedoch die Ansicht gewechselt, wird die Eigenschaft, die laut ACCESS-Hilfe Vorrang vor Formularinternen Einstellungen hat, wieder außer Kraft gesetzt.

Abhilfe:
Die Umstände, aus denen heraus ein Öffnen im 'Nur Lesen'-Modus erforderlich sind, sollten per 'OpenArgs' oder 'If ExistsForm ...' o.ä. im Ereignis FORM_OPEN erkannt und die Eigenschaft DefaultEditing auf 3 gesetzt werden, anstatt auf A_READONLY als Parameter von 'DoCmd OpenForm' zu vertrauen. Dann klappt's auch mit dem 'Nur Lesen'.


Problem 11:
Ein Kombinationsfeld verwaltet gültige Einträge, die in einer anderen Tabelle hinterlegt sind.
Bei Basisgeschichten wie 'Branche' muß dann nur ein bißchen mit 'NotInList'- und zentralen 'ZeigeStammdatenFormular'-Routinen gespielt werden und alles geht in Ordnung.

Schwieriger wird es, wenn es sich um komplexere Datensätze wie z.Bsp. Adressen handelt, die vielleicht sogar nur mit Hilfe geeigneter Formulare eingegeben werden dürfen.

Abhilfe:
Zum erstem Male im Hause Kelkel angegangen und mittlerweile im Hause DMT gereift:

Eine unserer bewährten NotInList-Routinen erkennt die wahre Absicht des Anwenders, öffnet das geeignete Formular mit dem Datenmodus-Parameter A_ADD und einem aussagekräftigem OpenArg und setzt den eingegebenen Wert des Kombinationsfeldes zurück, damit dieses später meckerfrei neu abgefragt werden kann. Das neue Formular erscheint mit den
Navigationsinformationen '1 von 1' und der Datensatz kann ganz normal eingegeben werden. Als einziger Extra-Schritt wird für das Ereignis Form_AfterInsert des aufgerufenen Eingabe-Formulares das übergebene OpenArg überprüft. Dort findet im entsprechenden Fall ein Neuabfragen des vormals bearbeiteten Kombinationsfeldes statt, der neue Wert wird wieder zugewiesen und das Eingabeformular kann geschlossen werden.


Problem 12:
Design-Einstellungen, die vom Standard abweichen, werden nur dann dargestellt, wenn dieses Formular in der Einzelblatt-Ansicht geöffnet wird. Soll aber ein Formular in der Datenblattansicht z.B. rahmenlos geöffnet werden, kann das nicht innerhalb des Formular-Codes realisiert werden.

Eine externe Routine, die das Application.Echo abschaltet, das Formular gegebenenfalls mit Krit) öffnet, danach die Datenblattansicht einstellt und die Bildschirm-Wiedergabe wieder aktiviert, löst die Sache auf zumutbare Weise.


Bearbeitungsmodus   Quelle: dmt   Datum: 03.2004   nach oben

BEARBEITUNGSMODUS in Abhängigkeit vom FENSTER-ANZEIGE-MODUS einstellen:

Sub Form_Current ()

    If iLastView = 88 Then iLastView = Me.CurrentView

    If iLastView <> Me.CurrentView Then
       If Me.CurrentView = 2 Then                ' Datenblatt
          iLastView = 88
          DoCmd Maximize
          Me.AllowEditing = False
          Me.DefaultEditing = 3
       Else                                      ' Formular
          iLastView = 88
          DoCmd Restore
          Me.AllowEditing = True
          Me.DefaultEditing = 2
       End If
       iLastView = Me.CurrentView
    End If

End Sub

Der "Umweg" mit der 88er-Zuweisung für die Variable iLastView muß gemacht werden, weil alleine die Änderung von AllowEditing ein Neu-Darstellen des Fenster bewirkt und deswegen dieser Code-Teil erneut ausgeführt wird, was nach wenigen Malen zu einem Überlaufen eines internen Stapelspeichers führt.

Klar, daß auch das nicht ohnr Ärger von statten geht, wenn das Formular keine Datensätze enthält. In der Datenblattansicht werden in diesem Fall gar keine Datensätze angezeigt und der Wechsel zur Formularansicht stellt nur ein voll leeres Formular dar, da mangels Datensätze auch kein Form_Current eintritt. Sämtliche Versuche, daran mittels Makro-Aufruf in einem der Ereignisse Laden, Öffnen, Anzeigen etwas zu drehen, schlugen fehl.

Die einzige Möglichkeit scheint darin zu bestehen, nach der Codezeile, in der das Formular geöffnet wurde, folgendes einzufügen:

    If Forms("Ventile_Filter").RecordsetClone.RecordCount = 0 Then
        DoCmd RunMacro "Menübefehle.Ansicht_Formular"
    End If


Daten, aktualisieren   Quelle: dmt   Datum: 03.2004   nach oben

Manchmal ist es sinnvoll, ein Code-mäßiges Requery anzubringen.

Die Datenquelle wird brav neu abgefragt und die Daten erscheinen auch in der gewünschten Reihenfolge. Doch konnte bisher nicht herausgefunden werden, nach welchem System der aktuelle Datensatz nach einem erfolgtem Requery bestimmt wird ( meistens der erste, manchmal aber auch der letzte ). Nicht weiter drum kümmern, ist eh' nur verlorene Zeit. Außerdem spielen Datensätze in einer Datenbank mittlerweile doch wohl eh' nur eine untergeordnete Rolle.


Daten, neue   Quelle: dmt   Datum: 03.2004   nach oben

EINGABE von Datensätzen in einem Formular:

In einer ausgeklügelten Anwendung kommt es ab und zu vor, daß ein Formular bei der Eingabe eines Datensatzes dem Anwender mit Routinen zur Seite stehen sollte, die bei nachträglichen Bearbeiten eines bereits vorhandenen Datensatzes nicht mehr ausgeführt werden sollten.

Nachdem bis jetzt keine direkt-ansprechbare Access-Eigenschaft ausgemacht werden konnte, wurde zuerst eine Lösung gefunden, die bei jedem Form_Current ein DLookup durchführt und dementsprechend ein prozedur-übergreifendes Flag 'iNeu'
setzt.

Noch schneller ist es, bei jedem Form_Current das Flag lediglich zu verneinen und es bei dem Ereignis Form_BeforeInsert zu setzen. Die durch Access bestimmte Reihenfolge der Ereignisse sorgt für den jeweils richten Wert des Flags.

Wenn der Zustand 'neuer Datensatz' bereits erkannt werden soll, bevor der Anwender durch eine Tasteneingabe das Ereignis Before_Insert auslöst, bietet sich im Ereignis Form_Current das Abfragen der Null-Eigenschaft eines geeigneten
Zähler-Steuerelementes an. Das ist schnell und obendrein harmlos, da Form_Current-Geschichten manchmal teuer bezahlt werden müssen.


Daten, neue feststellen   Quelle: dmt   Datum: 03.2004   nach oben

NEUEN DATENSATZ IDENTIFIZIEREN / OLDVALUE:

teilweise sehr hilfreich, macht aber auch nicht immer Freude:

speziell bei einem neuen, unbearbeitetem Datensatz ist für die Eigenschaft OldValue folgendes zu beachten:

- Bei Feldern mit DefaultValue ist dieser mit OldValue identisch !
- Bei Feldern ohne DefaultValue ist der OldValue=NULL
- Bei Zählerfeldern erzeugt der Verweis auf OldValue IMMER einen Fehler err=2455

Wurde ein Datensatz bereits editiert, kann er ohne ein explizites Dlookup, wenn Ident-Felder vergeben wurden, NICHT mehr von einem bereits vorhandenen, nur bearbeiteten unterschieden werden.


Datenblattansicht   Quelle: dmt   Datum: 03.2004   nach oben

DATENBLATT-ANSICHT


AUSBLENDEN VON SPALTEN:

Das Ausblenden von Spalten in der Datenblattansicht ist nur manuell und nicht über Code möglich. Eine nette Alternative für den angenommenen Fall, daß eine im Formular angelegte Check-Box nicht als Spalte in der Datenblattansicht zu sehen
sein soll, besteht darin, in der Datenblattansicht die Spaltenränder soweit zu verschieben, daß die unerwünschte Spalte verschwindet. -> Wechseln in den Entwurfsmodus und speichern.


FELDTITEL:

Für den Feldtitel in der Überschriften-Leiste der Datenblattansicht zeichnet nicht etwa der Name des bezogenen Tabellenfeldes geschweige denn der Name des bezogenen Steuerelementes verantwortlich, sondern die Caption-Eigenschaft des an das Steuerelement gebundenen Textfeldes ! Darauf zu kommen war nicht einfach; es bringt aber immerhin den Vorteil mit sich, daß auf diese Titel sehr einfach Einfluß genommen werden kann, ohne Feldnamen in Tabellen, Abfragen, Beziehungen etc. ändern zu müssen.


Erscheinung des DATENBLATT-FORMULARes:

Beim Öffnen eines Datenblattformulares wird dieses automatisch mit den allgemeinen Windows-Eigenschaften (verstellbarer Rahmen, Titelzeile mit Systemmenü und Symbol-/Vollbild-Schaltflächen) dargestellt, und zwar unabhängig davon, wie die entsprechenden Eigenschaften für dieses Formular eingestellt sind.

Wird dieses Formular in der Formularansicht geöffnet, stellt es sich mit den gespeicherten Eigenschaften dar. Wenn jetzt zur Datenblattansicht gewechselt wird, erscheint diese ebenfalls mit den Eigenschaften, die Access diesem eigentlichen Datenblatt-Formular beim Öffnen partout nicht geben will.

Um dies aber automatisch und für den Anwender unsichtbar vonstatten gehen zu lassen, sind wieder einige Klimmzüge nötig. Alle Versuche, dies innerhalb des Formularcodes zu bewerkstelligen, schlugen fehl. Wenn ein Formular auf die oben
beschriebene Weise geöffnet werden soll, kann das durch eine kleine, externe Routine (bzw. Makro) geschehen:

DoCmd OpenForm "UF_Projekte"
DoCmd DoMenuItem A_FORMBAR, 2, 2, , A_MENU_VER20

Um dieses Datenblatt in einer bestimmten Größe erscheinen zu lassen (z.B. den ganzen Arbeitsbereich ausfüllend, rahmenlos, oder mit dünnem Rahmen, um die Titelleiste erscheinen zu lassen, die ansonsten nur im Vollbildmodus in der
Anwendungstitelleiste gezeigt wird), kann entweder versucht werden, im Entwurfsmodus die Größe des Detailbereiches solange pixelzuficken, bis das Formular die gewünschte Größe besitzt ('Größe an Formular anpassen' geht nur in der FormularAnsicht; Access stellt Formulare beim wiederholtem Öffnen obendrein gerne in kleineren Größen dar), oder vielleicht zuverlässiger, in Form_Open den Sack per DoCmd MoveSize zumachen oder die simple Nummer mit Maximize / Restore in Form_De-Activate !


Spaltenbreite:

Normalerweise kein Problem, in der Datenblattansicht einstellen und beim Schliessen das Speichern bestätigen.

Alternativ können ab Access97 eine Reihe der Spalteneigenschaften per Visual Basic eingestellt werden.

Bei Formularen, die ein Register-Steuerelement beinhalten (ab Access97), muß jedoch in der Datenblattansicht festgestellt werden, daß sich die Spaltenbreiten allen Maßnahmen widersetzen. Die Spalten erscheinen stets in allgemeiner Breite.

Als einzige Gegenmaßnahme mußte wieder in die Mottenkiste peinlichster Lösungen gegriffen werden: Imitation ausgeführter Menübefehle per Sendkeys.

Ein Beispiel aus dem Code eines Menüformulares:

    ' **** widerspenstige Spalten auf altmodischste Weise einstellen ****

    Forms!Webalbum!Projekt.SetFocus
    SendKeys "%tb%a", True
    Forms!Webalbum!Arbeitsverzeichnis.SetFocus
    SendKeys "%tb0{ENTER}", True
    Forms!Webalbum!Keywords.SetFocus
    SendKeys "%tb55{ENTER}", True
    Forms!Webalbum!Projekt.SetFocus

%a steht für Anpassen, 0 für Ausblenden und numerische Angaben für eine gewünschte Breite.


Dialoge   Quelle: dmt   Datum: 03.2004   nach oben

DIALOG-Formulare:

Im Rahmen einer temporären Setup-Routine sollte ein Info-Dialog angezeigt werden, während Code ausgeführt wird. Dies schien sich um's Verrecken nicht bewerkstelligen lassen zu wollen.

Einzige Lösung: Ein Makro 'autoexec' öffnet ein Formular mit den Eigenschaften

Rahmen=Dialog
Popup=ja
Gebunden=nein

mit dem Parameter Fenstermodus=Normal und ruft danach den als globale Funktion definierten Code auf.

Dialog-Formulare sind u.a. sinnvoll, wenn der Anwender nicht zu einem anderen Formular wechseln darf. Werden die Eigenschaften POPUP und GEBUNDEN aktiviert, kann jedoch auf die Menüleiste nicht mehr zugegriffen werden. Ist nur POPUP
aktiviert, so daß das Formular immer im Vordergrund erscheint, ist die Menüleiste zwar zugänglich, enthält aber immer die Optionen, die zum nächst zugrundeliegenden Formular gehören ! Wenn nur die GEBUNDEN benutzt wird, ist die Menüleiste mit den gewünschten Option bedienbar.

Der für diese Zwecke sinnvolle DIALOG-Rahmen kann jedoch dummerweise nur angezeigt werden, wenn die POPUP=ja gesetzt ist.

* * * *

MENÜ-DIALOG-FORMULARE

wurden bisher immer als Gebunden und Popup definiert, um ein DOS-mäßige, eindeutige Benutzerführung zu gewährleisten. Verbunden war damit aber Code-Gechecke (Ausblenden des Menüformulares nach Aufruf eines Basistabellen-Formulares sowie Wiedereinblenden nach Schließen des Basistabellen-Formulares) sowie diverse Fokussierungsprobleme (Geistereffekte mit scheinbar gesetztem Fokus, aber kein Tastaturzugriff sowie verschwundene Statuszeilen-Texte).

Deswegen jetzt auch hier die konventionelle Arschloch-Lösung, und wer daneben klickt, den bestraft Windows.

Bei dieser Lösung sollten Menü- u.ä. -Formularen eine Mini-Menüleiste mit den rudimentärsten Funktionen zugeordnet werden.


Dreifach-Werte   Quelle: dmt   Datum: 03.2004   nach oben

TRIPLE-Status:

Im Zusammenhang mit Kontrollkästchen und Optionsfeldern (hierzu gehören auch Umschaltflächen) muß auf die erfolglosen Bemühungen im Kampf um die Durchsetzung eines Triple-Status' für die benannten Steuerelemente hingewiesen werden. Die Bedienung dieser Steuerelemente bewirkt einen ausschließlichen Wechsel zwischen 0 und -1. Selbst eine getürkte Zuweisung z.B. eines '2'-Wertes wird dann von quasi-wahr zu 0 umgeschaltet. Übermotivierteste Aktionen mit OldValue, BeforeUpdate, AfterUpdate, formularglobalen Variablen und selbst das Nachdenken über Static-Deklarationen haben einfach nur zu NICHTS geführt.

Entweder werden diese Sachen über Optionsfelder und -gruppen aufgefächert und damit regulär gelöst oder im (bezahlten) Bedarfsfall über dritte Zustände (z.B. NULL) per rechte Maustaste bzw. ShortCut.


Eingabeformat   Quelle: dmt   Datum: 03.2004   nach oben

EINGABEFORMAT / INPUTMASK / FORMAT:

Das Zuweisen eines Format-Strings für die Eigenschaften 'Format' oder 'Eingabeformat' eines Tabellenfeldes bzw. eines Steuerelementes ist häufig mit Problemen verbunden. So mußte neben dem oben beschriebenen Problem im Hause Kelkel festgestellt werden, daß das Anzeigen der einem Kombinationsfeld zugrundeliegenden Daten unzumutbar lange dauerte, wenn in der Tabelle für die entsprechenden Felder Format-Zuweisungen vorgesehen waren.

Das zum Beispiel häufig auftretende Problem, in ein Textfeld einzugebende Buchstaben in Großbuchstaben umzuwandeln, kann auch im Ereignis OnKey mit folgender Anweisung erreicht werden:

    KeyAscii = Asc(UCase(Chr$(KeyAscii)))

Durch Zuweisen eines Format-Strings können z.B. Einheiten angezeigt werden: <0,00" DM"> zeigt bei einer Eingabe von <2> folgendes an: <2,00 DM>. Das erscheint auf den ersten Blick toll. Versucht man aber in einem Formular verschiedene Werte so unterzubringen, daß die Zahlen bündig untereinander stehen, kann dies an verschieden langen Einheiten-Bezeichnung scheitern. In diesem Fall erhalten die Steuerelemente max. Zahlenformat-Anweisungen mit der
gleichen Anzahl von Nachkommastellen, werden auf rechtsbündig eingestellt und die Einheiten werden mit knappen Abstand als linksbündige Labels von Hand dahintergesetzt.

Wenn die Eigenschaften Format und Eingabeformat per Source-Code zugewiesen werden sollen, kommt es wieder mal zu Problemen. Versucht man, diesen auf den Grund zu gehen, wird man dann mit Scheiß-Effekten konfrontiert:

Das Standard-Eingabeformat für 'Datum, kurz' wird vom Eingabeformat-Assistenten mit '99/99/00;0;_' zurückgegeben, nach der Bestätigung mit wird dann '99.99.00;0;_' daraus, und wenn man das dann per Direktfenster-Basic abfragt, erhält man wieder '99/99/00;0;_', was auch dergestalt per Source-Code zugewiesen werden kann.

siehe auch DATUM.

Originell sind auch solche Versuche mit der Format-Eigenschaft. Die deutsche Einstelllung 'Datum, kurz' lautet in Basic (undokumentiert) 'Short Date'. Die Hilfe-Dokumentation meint hierzu in einem Beispiel zur Basic-Variante, daß der deutsche String ebenso im Code verwendet werden kann. Wenn man genau das tut, mutiert der Feldinhalt nach Bestätigung zu einem wunderschönen '11atu11, kurz', aus dem witzigerweise per Code-Auswertung der eigentlich eingegebene Datumswert
wieder richtig ausgelesen werden kann.

Da man als Programmierer mit Software leider auch arbeiten sollte, kommt man eben nicht umhin, sich Teile der Dokumentation selbst zu schreiben:


DEUTSCH. FORMAT  FORMAT     BASIC

Standarddatum    General    Date
Datum, lang      Long       Date
Datum, mittel    Medium     Date
Datum, kurz      Short      Date
Zeit, lang       Long       Time
Zeit, 12Std      Medium     Time
Zeit, 24Std      Short      Time
Allgemeine Zahl  General    Number
Währung          #,##0.00" DM";-#,##0.00" DM"
Festkommazahl    Fixed
Standardzahl     Standard
Prozentzahl      Percent
Exponentialzahl  Scientific
Wahr/Falsch      True/False
Ja/Nein          Yes/No
An/Aus           On/Off

Man beachte Zeit, 12Std - Medium Time und Zeit, 24Std - Short Time !


Ereignisse   Quelle: dmt   Datum: 03.2004   nach oben

EREIGNISSE in FORMULAREN:

FORM_ACTIVATE tritt für GEBUNDENE FORMULARE nicht ein ( wohl, weil sie selbst den Fokus an andere Formulare gar nicht abgeben können ), FORM_CURRENT aber schon, obwohl das Formular z.B. als Menü-Formular keine Datensätze anzeigen
kann.

Mit höchster Vorsicht zu genießen sind umfangreiche Code-Geschichten, die innerhalb eines sich öffnenden Formulares Datensatzzeiger bewegen, Steuerelement-Einstellroutinen aufrufen etc.. Da kommt es zu debugmäßig zu nicht nachvollziehbaren Mehrfach-Ereignissen, die einander kreuz und quer aufrufen und dafür manchmal auch gehörig viel Zeit benötigen. Obendrein mißlingen dann z.B. Verweise auf UF.Form-Eigenschaften usw..

Wenn mehrere, diffizile Dinge abgecheckt werden müssen, empfehlen sich nach dem Stand der Dinge (gilt wohl auch unverändert für ACCESS 7) zwei Strategien :

1.
Formular per simples DoCmd OpenForm öffnen und die nötigen Sachen im Folge-Code ausführen. Wenn es zu optischen Flackergeschichten kommt, kann das Formular ja auch hidden geöffnet werden und nach Abarbeitung aller Sachen wieder eingeblendet werden.

Nachteil:
Wenn so ein Formular wie z.B. das Termine-Formular von vielen, verschiedenen Stellen im Programm aus mit jeweiligen Spezialoptionen aufgerufen werden kann, verteilt sich eine Menge Code in den verschiedenen, anderen Modulen.
Wenn man einen evtl. Performanceverlust hinnimmt, kann man solche Aufrufroutinen auch in ein globales Modul auslagern.

2.
Wenn schon diffizile Schweinereien in Form_Open und Form_Current stattfinden müssen, dann mit äußerster Vorsicht und viel mit Flags abarbeiten, um Datensatzbewegende Anweisungen an das hintere Ende einer undurchschaubaren Ereigniskette zu verlagern.

Die Aktualisierung der Steuerelementinhalte funktioniert auf teilweise positiv wunderbare Weise pro angezeigten Datensatz, ein zielsicherer Bezug auf das zuvor geöffnete Formular (z.B. mit Screen.ActiveForm.Name) kann aber nur aber per Code (wo auch sonst?) im früher eintretenden Ereignis FORM_OPEN vorgenommen werden, wo Screen.ActiveForm im Code des Formulares B auf das zuletzt aktive Formular A verweist.

Screen.ActiveForm ist aber nicht unbedingt der Bringer; z.B. hat beim Ereignis FormOpen, das übrigens das letzte eintretende Formularereignis ist, der Verweis auf Screen.ActiveForm.Name noch lange nichts mit Formular zu tun, in dem der Code auch ausgeführt wird. Ein Bezug per 'Me' läßt die Sache dann schon besser aussehen. Screen.ActiveForm ist ok für Funktionen, die z.B. per Symbolleisten mehreren Formularen zur Verfügung stehen.

* * * *

BEFOREUPDATE:

Plausibilitätsprüfungen müssen manchmal für alle Fälle von z.B. Stammdatensatz-Änderungen erfolgen: für neue, geänderte und gelöschte Datensätze.

Das Abfangen kann leider nicht in einem einzigen Ereignis erfolgen. Bei Neuanlegen und Ändern scheint BeforeUpdate in Frage zu komen, aber selbst dazu gibt es Zweifel, deswegen IMMER nachprüfen. Beim Löschen muß aber auch Delete mit einem Code-Verweis auf die Plausibilitäts-Routine versehen werden.

* * * *

FORMULAR-CODE nach SCHLIEßEN:

Interessant ist manchmal die Frage, was codeseitig passiert, wenn in einer Formular-Prozedur das Schließen des Formulares und danach noch weiterer Code ausgeführt wird.

Der Code in mindestens dieser Prozedur (evtl. noch Ereignis-gebundener Code) wird in der Tat abgearbeitet, jedoch misslingen jegliche Verweise auf Formular-bezogene Elemente.

* * * *

MOUSEMOVE / DRAG and DROP:

Ein weites Feld, wird unter Visual Basic recht gut unterstützt, unter MS-Access nicht direkt.
Über eine geschickte Auswertung der MouseMove-Parameter könnten solche Dinge u.U. realisiert werden.

Vorraussetzungen:

Formular-KontextMenü ausschalten.


Existenz prüfen   Quelle: dmt   Datum: 06.2004   nach oben

FORMULARE auf VORHANDENSEIN prüfen / Objekt-Zustand / EXISTSFORM / ISFORMOPEN:

Die 'Mutterfenster'-Geschichte ist out und selbst der wirklich bewährten ExistsForm-Funktion ist der Einsatz folgender Verfahrensweise vorzuziehen:

    If OBJSTATE_OPEN = SysCmd(SYSCMD_GETOBJECTSTATE, A_FORM, Fenster) Then

In Datenformularen kann es allerdings vorkommen, daß der Anwender in der Datenblattansicht Spaltenbreiten verändert und das Formular den Status OBJSTATE_DIRTY erhält. Deswegen eine WorkAround-Erweiterung und die Sache ist perfekt. Das mußte aber im September gleich nochmal überarbeitet werden, da sich im Hause Klumpp - SEL ein bisher unbekannter und natürlich auch nicht dokumentierter OBJSTATE=3 ergab.

Das tauchte dann später auch selbstverständlich unter Access97 auf (dort als acObjState = 3 (in der webmanag-Anwendung, wenn das gebundene Formular "Aktueller_Besitzer" über dem Formular "Menue" steht).

ABER: Gleich beim Nachziehen älteren Codes ging das prompt mehrfach schief, da IsFormOpen den WIRKLICHEN Datenbank-Objekt-Namen checkt, während die Mutterfenster- und ExistsForm-Funktionen auf den Caption-String Bezug nehmen, der auch in der Liste geöffneter Fenster erscheint.

Function IsFormOpen (Fenster As String) As Integer

    Dim V As Variant

    Const FORM_DIRTY = 3    ' nicht dokumentierter OBJSTATE=3, wenn z.B.
                            ' die Spaltenbreiten in der Datenblattansicht
                            ' geändert wurden.

    V = SysCmd(SYSCMD_GETOBJECTSTATE, A_FORM, Fenster)

    If V = OBJSTATE_OPEN Or V = OBJSTATE_DIRTY Or V = FORM_DIRTY Then
       IsFormOpen = True
    End If

End Function

Eine vereinfachte Version, die auch kombinierte Werte, die Bitmasken bilden,
generell erkennt ist:

Function IsFormOpen(Fenster As String) As Integer

    If SysCmd(acSysCmdGetObjectState, acForm, Fenster) > 0 Then
       IsFormOpen = True
    End If

End Function

Der häufiger eintretende Fall, daß das Vorhandensein von Fenstern geprüft werden muß, um diese anschließend ein- oder auszublenden, kann in folgender Routine gekapselt werden:

Sub Form_Visible (sFormName As String, iVisible As Integer)

    If IsFormOpen(sFormName) = True Then
       Forms(sFormName).Visible = iVisible
    End If

End Sub

Interessant ist, daß SYSCMD_GETOBJECTSTATE für alle Arten von darstellbaren (und geöffneten) Access-Objekten angewendet werden kann:

A_TABLE
A_QUERY
A_FORM
A_REPORT
A_MACRO
A_MODULE

und außerdem noch die Zustände Neu und geändert feststellen kann:

OBJSTATE_OPEN   Geöffnet
OBJSTATE_NEW    Neu
OBJSTATE_DIRTY  Geändert

ABER es muß auch gesagt werden, daß es so aussieht, daß OBJSTATE_OPEN auch benutzt werden kann, um das Vorhandensein eines Access-Objektes zu prüfen, das zwar in der Datenbank vorhanden, aber NICHT geöffnet ist. Das sah schon beinah positiv aus, aber eigentlich ein Unsinn und obendrein 'tut' es nur bei Tabellen, wenn im 'normalen' Access das Datenbankfenster geöffnet ist.

Wieder mal DRECK !, zur Lösung siehe auch ExistsObject.

Interessant sind in diesem Zusamenhang auch CurrentObjectType und CurrentObjectName.

Obendrein konnte beim Durchforsten der in den Access-Libraries verborgenen Routinen ein undokumentierter SYSCMD-Schalter SYSCMD_CLEARHELPTOPIC = 11 entdeckt werden, der z.B. in der zweiten, nachgelagerten GetFileName-Funktion
wie folgt verwendet wird:

    unused = SysCmd(SYSCMD_CLEARHELPTOPIC)

Der Name des Return-Longs 'unused' sagt eigentlich alles und wofür das gut ist, weiß keiner.


Feldgröße   Quelle: dmt   Datum: 03.2004   nach oben

Das AUFFÜLLEN von FELDERN in einem Formular / FELDGRÖßE:

In einer Tabelle sei die Größe eines Feldes mit der Länge 3 vorgegeben. Ein an dieses Feld gebundenes Textfeld ist während der Eingabe von Daten über diesen Umstand informiert, wenn man NICHT von der Textfeld-Eigenschaft 'Eingabeformat' Gebrauch macht. Wird ein Eingabeformat angegeben, verliert das Textfeld die Information über die in der zugrunde-liegenden Tabelle vorgegebenen Feldlänge !


Fokus   Quelle: dmt   Datum: 07.2006   nach oben

FOKUS SETZEN auf TEXTFELDER / MEMOFELDER:

Eines vorneweg:

Bei vielen Access-2.0-Anwendungen haben Änderungen am Cursorverhalten (Feld-bezogener Navigationsmodus) oft Probleme mit sich gebracht. Die vielen nachfolgenden Beispiele beziehen sich auf diese Altlasten.

In Access 97 gelang mir in zumindest einer Anwendung das Code-gesteuerte Ändern des Cursorverhaltens mit geringem Aufwand.

Das Beispiel ist aus meiner Sicht ein sehr praktisches.
Beim Betreten eines Datum-Feldes finde ich es geil, wenn anstelle des an sich guten Access-Standardverhaltens der Fokus lediglich das erste Zeichen markiert. Eine sinnvolle Defaultvorgabe kann in vielen Fällen bestehen bleiben, ansonsten gibt der Anwender lediglich die nötigen Teile der Angabe ein (z.B. "0104") und schon prangt z.B. ein schönes "01.04.2006" im Feld. In der Access97-Anwendung kann das Eingabeverhalten des Feldes sogar mit der Maus nicht überlistet werden. Eine Voraussetzung für ein kongeniales Eingabeverhalten ist allerdings auch eine geeignete Eingabemaske (Inputmask), die ich in solchen Fällen gerne auf ein bewährtes "99.99.9999;0;_" setze. Gerade Access 97 kommt da mit seinen Drecks-ASSIstenten zum Teil mit abstrusestem Verhalten daher. Der Eingabeformat-Assistent präsentiert ganz gerne "99.99.00;0;_". Damit verhält sich das Eingabefeld geradezu idiotisch und sogar Trennzeichen-Punkte mutieren zu ungewollten Bestandteilen der Eingabe. Daher unbedingt beachten: "99.99.9999;0;_" !!!

Private Sub datum_GotFocus()

    On Error Resume Next

    Me!Datum.SelStart = 0
    Me!Datum.SelLength = 1

End Sub

Ich habe das dann doch nochmal im Access 2.0 getestet und in der Tat greift diese Lösung dort nicht ganz.
Aber eine im Code eingefügte F2-Tastenbetätigung, die das Eingabeverhalten umschaltet, löst das Problem

Das ganze sieht für Access 2.0 also so aus:

Private Sub datum_GotFocus()

    On Error Resume Next

    SendKeys "{F2}", True

    Me!Datum.SelStart = 0
    Me!Datum.SelLength = 1

End Sub

Und hier zu den Altlasten:

Speziell bei Memo- und Langtextfeldern ist das Standardverhalten von MS-Access 2.0, den gesamten Feldinhalt zu markieren und damit im Navigationsmodus zu bleiben, ungünstig. Bisherige Versuche, dies mit einem beherzten SendKeys "{F2}" zu umgehen, führte abhängig davon, ob mit der Maus oder der Tastatur gearbeitet wurde, zu unterschiedlichen und fehlerhaften Verhaltensweisen. Eine tragbare Lösung schien mit SelLength und SelStart in Reichweite zu liegen, da ein Hineinklicken mit der Maus sogar den Cursor an die richtige Position springen läßt und sich die SendKeys-Geschichten vermeiden lassen. Dummerweise befindet sich Access beim Feld-Wechseln per Tastatur erst mal grundsätzlich im Navigationsmodus, und selbst wenn der Cursor nach einer Sel...-Anweisung uns an der ersten oder letzten Stelle freundlich anblickt, können wir uns nicht im Feld bewegen, da der Navigationsmodus immer noch aktiv ist.
Folgendes Beispiel verdeutlicht eine wohl endlich tragbare Lösung, in der der Bearbeitungsmodus erzwungen wird und der Cursor gesetzt wird. Witzigerweise überschreibt ein Hineinklicken die Cursor-Position, während bei Tastatur-Aktionen die Einstellungen wunschgemäß vorgenommen werden.

Sub Info_Enter ()

    SendKeys "{F2}", True

    Me!Info.SelLength = 0
    Me!Info.SelStart = 0

End Sub

Der häufigere, weil sinnvollere Fall dürfte wohl das Setzen des Cursors an das Ende eines Memo-Feldes sein. Zum einen vermeidet man die potentielle Gefahr, einen umfangreichen und defaultmäßig markierten Memotext in der Hektik komplett zu überschreiben und zum anderen kann man am Ende direkt weitertippen.

Der Aufruf Cursor_am_Ende erledigt das allerdings auch mit einer der manchmal eher zweifelhaften SendKeys-Aktionen, um den Pfeiltasten-kompatiblen Editiermodus zu erreichen:

Sub Cursor_am_Ende ()

    On Error Resume Next

    SendKeys "{F2}", True

    Screen.ActiveControl.SelLength = 0
    Screen.ActiveControl.SelStart = Len(Screen.ActiveControl)

End Sub

Vergleichbar ist auch noch eine Version, die ich aus Langeweile im Hause SEL angefertigt habe:

Sub Focus2EOC (C As Control)

    ' **** Alternative zur ewig nervigen SendKeys-Scheisse ****

    ' und das allerallergeilste an dieser Lösung ist, daß das Mausverhalten
    ' - Klicken auf Caption -> kompletter Feldinhalt markiert
    ' - Klicken in Text -> Fokus an Klickstelle
    ' stimmig ist und beim Betreten des Elementes per Tastatur der Fokus OHNE
    ' Geistereffekte zuverlässig an das Ende des Elementes gesetzt wird.

    C.SetFocus

    If Not IsNull(C) Then
       C.SelStart = Len(C)
       C.SelLength = 0
    End If

End Sub

Diese hilfreichen Routinen geraten immer wieder an das Problem, das abhängig von irgendwelchen Zeitscheiben-Ereignis-etc-Scheißgeschichten nicht unbedingt zuverlässig davon ausgegangen werden kann, ob gerade z.B. der Navigationsmodus aktiv ist oder nicht.

Selbst Cursor-Contraprogrammierungen, die durchaus erfolgreich den einen oder anderen Modus erkennen und gewollte Zustände herstellen, scheitern u.U. daran, ob ein Formular mit angezeigtem oder ausgeblendetem Datenbankfenster geöffnet
wird, oder ob man aus einem anderen Formular zurückkehrt.

Als ich zum x-ten Male soweit war, ALLES hinzuschmeißen, nachdem endlose SendKeys mit oder auch ohne Pause-Parameter, und Rumgewurschtle mit SelLength etc. nichts gebracht hatten, kam ich auf die paranoide Idee, im Formular-Ereignis Activate erst einmal ein trockenes DoCmd SelectObject A_FORM, Me.Name reinzuwürgen und siehe da, der Spuk hörte auf und meine erweiterten Cursor-Routinen versahen ihren Dienst in jedem mir bekannten Fall.

So, und nun zur erweiterten Lösung:

Erstmal indifferente Geisterscheiße abstellen:

Sub Form_Activate ()

    DoCmd SelectObject A_FORM, Me.Name

End Sub

und nun die Ereignisroutinen:

Sub BNr_GotFocus ()

    BoschFeld_GotFocus Me!BNr, sBNr_Def

End Sub

Sub BNr_MouseUp (Button As Integer, Shift As Integer, X As Single, Y As Single)

    BoschFeld_MouseUp Me!BNr

End Sub

und für Navigationsfetischisten, die auf <Pfeil auf/ab> angewiesen sind:

Sub BNr_KeyDown (KeyCode As Integer, Shift As Integer)

    NavArrowKeysUpDown KeyCode

End Sub

jetzt noch die aufgerufenen Routinen:

Sub BoschFeld_MouseUp (C As Control)

    On Error GoTo err_BoschFeld_MouseUp

    Dim s As String

    If C.SelLength > 1 Then
       SendKeys "{F2}"
    End If

    s = "{RIGHT " & BoschFeld_GetSelStart(C.InputMask, C, True) & "}"

    C.SelStart = 1

    SendKeys s

    Exit Sub


err_BoschFeld_MouseUp:

    Fehler "BoschFeld_MouseUp"
    Exit Sub

End Sub

und

Sub BoschFeld_GotFocus (C As Control, sDefault As String)

    On Error GoTo err_BoschFeld_GotFocus

    Dim s As String

    If Screen.ActiveForm.Name = "Start" Then
       C = sDefault                         ' Default zuweisen
    End If

    s = ClearStringFrom(sDefault, """")     ' entferne enthaltene Anführungszeichen

    SendKeys "{F2}", True

    C.SelStart = BoschFeld_GetSelStart(C.InputMask, s, False)

    C.SelLength = 1

    Exit Sub


err_BoschFeld_GotFocus:

    Fehler "BoschFeld_GotFocus"
    Exit Sub

End Sub

sowie

Private Function BoschFeld_GetSelStart (vInputMask As Variant, vDef As Variant, iMouse As Integer) As Integer

    On Error GoTo err_BoschFeld_GetSelStart

    Dim i As Integer

    ' **** hardcodierte Interpretation ****
    ' **** der Bosch-Nummerdarstellung ****
    ' **** Regel: ">A\ 000\ 000\ 000"  ****

    If IsNull(vDef) Then
       i = 0
    Else
       i = Len(vDef)
    End If

    If iMouse = True Then
       BoschFeld_GetSelStart = i
       Exit Function
    End If

    If IsNull(vInputMask) Then
       ' keine Literal-Zeichen enthalten, nichts überspringen
    Else
       Select Case i
          Case Is >= 7:  i = i + 3
          Case Is >= 4:  i = i + 2
          Case Is >= 1:  i = i + 1
       End Select
    End If

    BoschFeld_GetSelStart = i

    Exit Function


err_BoschFeld_GetSelStart:

    Fehler "BoschFeld_GetSelStart"
    Exit Function

End Function

und schließlich

Sub NavArrowKeysUpDown (iKeyCode As Integer)

    On Error Resume Next

    If iKeyCode = 38 Then
       SendKeys "+{TAB}"
    ElseIf iKeyCode = 40 Then
       SendKeys "{TAB}"
    End If

End Sub

Man beachte bei der MouseUp-NavArrowKeysUpDown-"Lösung" den weggelassenen Pause-Parameter einer peinlichen SendKeys-Aktion.

* * * *

Teilweise kann es sogar zu SCHWEREN Fehlern kommen:
Eine SetCursor-Routine in bosch\dat013kl.mdb sorgt dafür, daß der Cursor in einem der entsprechenden Felder an der ersten Stelle nach dem vorgegebenen Defaultwert im Editmodus steht.
Werden z.B. nach mißlungenen Plausibilitätsprüfungen msgboxes angezeigt und der Cursor per SetFocus wieder in das leidige Feld gesetzt, greift OnEnter oder GotFocus nicht mehr und das Feld erstrahlt im Navigationsmodus. Contraprogrammierung mit diversen Kombinationen und erneutem Set_Cursor führte zu HÄRTESTEM TASTATURABSTURZ. Die Tastatur wurde abgeschaltet, Windows sowie alle Programme konnten noch einwandfrei per Maus geschlossen werden, doch der
Hänger hielt sich bis ins DOS. Selbst ein Reset reichte nicht aus, erst der Netzkiller erweckte die Tastatur wieder zu neuem Leben.

Allem zum Trotz die Routine, die vielleicht anderswo noch mal einen hilfreichen Dienst verrichten kann:

Sub Set_Cursor (c As Control, vWert As Variant, iPause As Integer)

    On Error GoTo err_Set_Cursor

    ' **** setze im Mastermodus Cursor ****

    Dim i As Integer, j As Integer, s As String

    If Not IsNull(vWert) Then

       s = "{F2}"

       c.SetFocus

       If InStr(vWert, """") Then       ' bei entahltenen Anführungszeichen
          j = Len(vWert) - 2            ' würde der Cursor zu weit nach hinten
       Else                             ' gesetzt werden.
          j = Len(vWert)
       End If

       For i = 1 To j
           s = s & "{Right}"
       Next i

       SendKeys s, iPause

    Else
       Beep
       MsgBox "Ungültiger Wert NULL für Parameter vWert !", 16, "Set_Cursor"
       End If
    Exit Sub


err_Set_Cursor:

    Fehler "Set_Cursor"
    Exit Sub

End Sub


Fokus, Probleme   Quelle: dmt   Datum: 05.2005   nach oben

V O R S I C H T:

Manchmal kommt es zu Problemen mit 'SetFocus' und 'Steuerelement.Visible = false'.
Es ist in einem solchen Fall sogar schon einmal vorgekommen, daß ich daran selbst schuld war! Soll z.B. durch eine Schaltfläche ein Auswahlelement eingeblendet werden, daß durch verschiedene Tastatur- und Maus-Geschichten Werte erhält und an das Formular weitergibt und danach automatisch wieder ausgeblendet werden und der Fokus auf irgendein sinnvolles Steuerelement gesetzt werden, kann es im Code, der den jeweils eintreffenden Ereignissen zugewiesen ist, zu sehr vertrackten Operationen kommen, bei denen Access die Kontrolle verliert und einfachste SetFocus-Anweisungen mit der Drecksmeldung 'Fokus kann auf Steuerelement nicht gesetzt werden' quittiert. Die Ursache kann darin liegen, daß bei LostFocus eine Sub angesprochen wird, die auch von anderen Ereignissen aus aufgerufen wird. Erzeugt ein solch anderes Ereignis selbst auch ein LostFocus, kann u.U. zuerst die Sub aufgerufen, dann LostFocus ausgelöst und von da aus noch einmal die Sub aufgerufen werden. Solche ungewollten Mehrfachgeschichten sollten vorhergesehener Weise unbedingt vermieden werden, da auch mit schrittweisem Durchtickern durch die jeweiligen Prozeduren der Sache nicht unbedingt auf die Schliche zu kommen ist.

Auch der Versuch, Variablen auf Prozedurebene mithilfe der STATIC-Anweisung einen bleibenden Wert zuzuordnen, geht schief, wenn man sich an die Hilfe-Info's hält. So konnte ein auf Formular-Ebene globales Recordset samt Zuweisung nur erreicht werden, indem im Deklarationsabschnitt das Recordset dimensioniert wurde und in einer Static Sub dieser Objektvariablen ein Wert zugewiesen wurde.

* * * *

Ebenfalls scheiße war im wohl gereiften Adressen-Modul der strobelschen bestatt.mdb das Ein- und Ausblenden des Memofeldes per althergebrachter Exit- oder auch LostFocus-Methode, wenn die Schaltfläche direkt per HotKey an- und vor allen Dingen auch wieder 'ab'-gesteuert wurde. Hier kam es dann auch zu selbstauslösenden Mehrfachgeschichten.

An Geduld hat es mir nicht gemangelt, aber es wollte nichts klappen. Auch konnten in diesem Zusammenhang zwischen LostFocus und Exit keine Unterschiede ausgemacht werden. Die Lösung bestand darin, von diesen Ereignissen keinen heftigen Gebrauch zu machen, sondern beim Verlassen des Memo-Steuerelementes erstmal die Schaltfläche wieder als default anzusteuern. Eine formularglobale Variable wird im Hotkey-Fall in Key_Down des Memofeldes gesetzt, damit die Schaltflächen_Click-Routine das wegen LostFocus ausgeblendete Memofeld nicht wieder erneut ansteuert.

Das wars und es funktioniert. Was leider nicht klappt, ist das Ausblenden des Memofeldes mit Fokussieren eines anderen HotKey-Elementes per ShortCuts.

* * * *

FOKUSVERLUST / FOKUS / FOCUS:

Tritt ab und zu beim Arbeitem mit gebundenen und normalen Formularen auf, kann aber bekämpft werden, oder auch, wenn nach dem Öffnen ienes normalen Formulares ein gebundener Modal-Dialog eingeblendet wird. Im Falle des Meldungs-Formulares reicht es in so einem Falle, das gebundene Formular zu entbinden und nur als Pop-Up einzustellen. Beim Schließen eines solchen entschärften Formulares wird der Fokus wieder richtig auf das zuletzt geöffnete gesetzt.:

' Der Bezug auf Screen.ActiveControl.Name erzeugt einen
' gewollten Fehler, der so extra behandelt werden kann.

x% = IsNull(Screen.ActiveControl.Name)

 Exit Sub

err_Form_Vollbild_Unload:

    If Err = 2474 Then
       ' fokussiere das erste Steuerelement in der Auflistung
       Me(0).SetFocus
    End if

Man kann es sich auch etwas einfacher machen, indem man beim Schließen gebundener Formulare einfach eine ExistsForm-abhängige SetFocus-Anweisung ausführt.

Zuweilen kommt es sogar (als ob es mich unerwartet treffen würde) vor, daß ein SetFocus nicht ausricht. Ein besonders hartnäckiger Fall ergab sich im LaserJob-Manager, bei dem von einem Auftragsformular aus ein gebundenes Druck-Menü geöffnet werden kann, daß bei 'Ok' sich selbst schließt und den entsprechenden Druckvorgang als Preview anzeigt. Nach Schließen des Previewfensters wollte der Focus auch per SetFocus nicht zum Objekt der Begierde gelangen. Ein möglicher Grund für die besondere Hartnäckigkeit besteht u.U. darin, daß das Auftragsformular ein Popup-Status-Formular mit sich führt. Erst eine zentrale Funktion, die beim Schließen aller auftragsbezogenen Berichte aufgerufen wird, konnte Abhilfe schaffen:

Sub ActivateForm (FName As String)

    If ExistsForm(FName) = True Then
       DoCmd SelectObject A_FORM, FName
       Forms(FName).SetFocus
    End If

End Sub

Genau dieses Problem trat dann nochmal auf, und konnte witzigerweise durch ZWEIMALIGES Einfügen einer SetFocus-Anweisung behoben werden !

Aber das Bemühen einer eigenen ActivateForm-Routine sieht dann doch ein bißchen professioneller aus. Beide Lösungen scheinen auch mit dem Problem verloren gegangener Statuszeilen klarzukommen.

Beim Beenden eines Formulares mit einer msgbox-Abfrage a'la Access beenden / Anwendung schließen / Zurück löst die Aktion Zurück ebenfalls den Fokusverlust-Fehler aus, ohne daß dem beizukommen wäre.

Aber so kann's ja nicht weitergehen:

Zum zuletzt beschrieben Problem:

Die klassische 'Abbrechen'-Rückkehr aus der 'Anwendung beenden'-msgbox hat z.B. in pb_Ende_Click wie folgt auszusehen:

Sub pb_Ende_Click ()

    On Error GoTo err_Ende

    DoCmd Close A_FORM, Me.Name

    Exit Sub


err_Ende:

    Me!pb_Ende.SetFocus

    Exit Sub

End Sub

Das Problem des Zusammenspiels meiner gebundenen Menü-Formulare kann entweder dadurch gelöst werden, daß diese Formulare entbunden werden (alles Windows, oder was?), oder mit folgender Vorgehensweise:

In der Control_Click-Sub steht:

    Me.Visible = False

    DoCmd OpenForm "Import_Export"

und zwar unabhängig davon, ob ein weiteres, gebundenes Menüformular oder ein Datenformular geöffnet werden soll. Eine Form_Open-Routine des zu öffnenden Formulares ist nicht mehr notwendig. Bei Form_Close der geöffneten Formulare steht dann das bewährte:

    If ExistsForm("Haupt") Then Forms!Haupt.SetFocus

* * * *

FOKUS auf ein bestimmtes Anwendungsfenster setzen und dieses nach vorne stellen:

Declare Function SetForegroundWindow Lib "user32.dll" (ByVal hwnd As Long) As Long

* * * *

SETFOCUS / GOTOCONTROL / SELECTOBJECT:

Mit SetFocus hat's schon manche Probleme gegeben.

Mit GotoControl kann man zumindest Felder in auf dem Desktop geöffneten Tabellen fokussieren, nachdem diese mit einem herzhaften SelectObject bestimmt wurde.

Unterschiede sind laut Dokumentation nicht erkennbar, aber dafür jetzt wieder was aus dem Gruselkabinett:

Ein ein Kalenderobjekt anzeigendes Popup-Formular schreibt bei einem Doppelklick den Datumswert in ein dahinterliegendes Textfeld, um per Menübefehl 'Kopieren' das Datum in die Zwischenablage zu übernehmen. Wenn das nicht geklappt hätte, weil das Formular ein Popup ist, hätte ich das noch verstanden, daß das aber immer nur schief geht, wenn ich in dmt.mdb das Termine-Formular über ToDo öffne, wollte mir nicht in den Sinn. Fast sah es so aus, als ob das mit einem auferlegten Filterkriterium zusammenhing, aber selbst da gab es vereinzelt Ausnahmen. Eine Lösung (?) konnte in der folgenden, für mich unsinnigen Anweisungskombination gefunden werden:

Nachdem der Anwender (tragischer Weise ich selbst) auf ein Datum doppelklickt und im Code der Wert an das Textfeld übergeben wurde, werden folgende Zeile ausgeführt:

    Me!ClipDate.SetFocus

    DoCmd SelectObject A_FORM, Me.Name

Erst das spätere Einfügen der SelectObject-Anweisung scheint den Fehler der Kopieren-Menü-Anweisung bei aktivem Formularfilter behoben zu haben. Ein testweises Deaktivieren der SetFocus-Anweisung ließ den Fehler zwar nicht mehr auftreten, führte aber zu einer scheinbaren Deaktivierung des Festplattencache's. Vielleicht sollte ich ab jetzt Anweisungen per Zufallsgenerator im Code verteilen, dann klappt's auch mit der Software.

Mehr zum Thema Daten in Zwischenablage übernehmen s.a. DataToClipboard().


Geschwindigkeit   Quelle: dmt   Datum: 03.2004   nach oben

GESCHWINDIGKEIT:

Anzeige von Daten in einem Formular:

Das Anzeigen von Daten in einem Formular (bis zur Freigabe durch Access) kann unter Umständen beschleunigt werden, wenn als Datensatzherkunft eine gespeicherte Abfrage verwendet wird (von wegen präcompiliert und Rushmore und optimiert und die ganze Scheisse).


Größe & Auflösung   Quelle: dmt   Datum: 03.2004   nach oben

Und nun zum AUFLÖSUNGS-(UN-)ABHÄNGIGEN FENSTERLN:

Angefangen hat alles damit, in einem Vollbild-Formular ein Steuerelement maximaler Größe anzuzeigen. Bei gegebener Auflösung und einem Textfeld ist das kein Problem. Soll ein grafisches OLE-Objekt angezeigt werden, bei dem ein variables Zoom in den Abstufungen bekannter Auflösungen angeboten werden soll, wird das schon etwas anspruchsvoller. Der Clou kommt aber erst, wenn ein solches Formular automatisch passend zur aktuellen Auflösung den gesamten Bildschirm mit maximal großem Steuerelement ausfüllen würde.

Zum ersten Schritt:

Ein grafisches OLE-Objekt kann in einem Vollbild-Formular in mehreren Stufen in Schritten gezoomt werden, als ob die Auflösung verändert worden wäre. Nach dem Stand der Technik bis 5.98 war das Formular aber relativ an die Auflösung des
Desktops gebunden.

Eine Routine Fit2Res nimmt Fensternamen sowie Auflösung entgegen und stellt für das benannte Formular, dessen Detailbereich sowie ein enthaltenes OLE-Objekt-Feld Position und Größe ein. Das Formular ist rahmenlos und das Objektfeld nimmt den gesamten, verfügbaren Platz ein (eine Art Quasi-Vollbilddarstellung), so daß die aufgeführten Dimensionen auch die Bildschirm-Dimensionen unter den entsprechenden Auflösungen darstellen. siehe in mertlik.mdb Form Zoom_Objekt.

Sub Fit2Res (Fenster As Variant, Modus As Variant)

    On Error GoTo err_Fit2Res

    Dim iH As Integer, iW As Integer, F As Form

    Select Case Modus
        Case "640 * 480":   iH = 6350: iW = 9580
        Case "800 * 600":   iH = 8150: iW = 12000
        Case "1024 * 768":  iH = 10660: iW = 15300
        Case "1280 * 1024": iH = 14500: iW = 19140
        Case "1600 * 1200": iH = 17140: iW = 24000
        Case Else:          Beep
                            MsgBox "Ungültiger Parameter Modus='" & Modus & "' !", 16, "Fit2Res"
                            Exit Sub
    End Select

    Set F = Forms(Fenster)

    DoCmd SelectObject A_FORM, Fenster
    DoCmd MoveSize 0, 0, iW, iH
    F.Section(0).Height = iH
    F!Object.Width = iW
    F!Object.Height = iH

    Exit Sub


err_Fit2Res:

    Fehler "Fit2Res"
    Exit Sub

End Sub

Nun zum wahren Objekt der Begierde:

Nachdem ein erneuter Blick ins Win31-SDK (zum tausendsten Mal) Unerhofftes zum Thema Auflösung (bisher krampfhafte, grafikkarten- und syntaxbezogene Auswertung der system.ini) zum Vorschein brachte, sieht alles anders aus (diesmal besser) und scheint folgende Möglichkeiten zu bieten:

- Vollbildformulare, die per API die auflösungsbezogenen Maße des Desktops auslesen und deswegen bei jeder Auflösung als Vollbild dargestellt werden können, und deren zentrales Steuerelement (Memo oder OLE-Grafik) anschließend Access-intern an die Größe des aufgeblasenen Formulares angepasst wird.

- klassische Stammdaten-Pflegeformulare, die zum einen ihre Größe je nach Formular- oder Datenblattdarstellung getrennt einstellen können und sich per API-Desktop-Maße bei jeder aktuellen Auflösung zentrieren. Das muß dann leider jedesmal im ereignis-kritischen Form_Current abgecheckt werden, kann aber per formularglobalem Flag auf reine CurrentView-Wechsel begrenzt werden, bei dem eine Zentrierroutine aufgerufen wird. Diese Routine berechnet ausgemittelte Koordinaten für das übergebene Formular (gilt absolut innerhalb der Parent-Anwendung, z.B. MS-Access im Vollbildmodus) und plaziert es gemäß der übergebenen Maße. Es erfolgt eine Korrektur um 30 Pixel (Anwendungstitel-und Menüleiste) sowie eine leichte, psychologisch geschmackvolle Korrektur nach oben. Bei übergebenen 0-Maßen kann sogar ein Currentview- und Auflösungs-bezogenes Aufblasen zum Quasi-Vollbild erfolgen (z.B. ein kleine Maske, die als Liste aber den gesamten Schirm nutzen sollte).

Den erfreulichen Abschuss des vielzitierten Vogels kann ich mit Blick auf die Mertlik-dat013el.mdb vermelden, in der die beschriebenen Features schnuckeligst realisiert wurden. Das Formular Zoom_Objekt enthält ein Objekt-Feld, in dem ein
Zeichnungsdatensatz möglichst groß dargestellt werden soll. Beim Öffnen wird die aktuelle Auflösung ermittelt und das Objekt-Formular incl. Objekt-Feld zum Vollbild aufgeblasen. Zusätzlich wird im Kombinationsfeld 'Resolution' eines kleinen Pop-Up-Formulares der Wert der aktuellen Auflösung eingestellt. Und das ist noch nicht alles: das Pop-Up-Formular 'merkt' sich seine Position über dem Vollbild-Formular, und das absolut und damit unabhängig von der eingestellten Auflösung.
Einziges Handicap: Wer seine Grafik mit z.B. 1600 * 1280 betrachtet und das Zoom-Formular nach rechts unten schiebt, hat beim Wechsel auf 640 * 480 erst mal ein kleines Suchproblem. What shells.

YES !

Ein paar Feststellungen vorneweg:

GRÖßE von Fenstern, Detailbereich, Rahmen / Fenstergröße:

- Alle Angaben erfolgen in Pixel (Fuck the verschissene Microsoft-Twips)
- Höhe Titelleiste + Menüleiste = 30
- Höhe Navigationsleiste sowie ScrollBar = 12
- Minimale Breite zum Anzeigen der Navigationsbuttons = 274, 275, 281, ???
- Die angezeigte Formularbreite errechnet sich aus
Form!xyz.Width/15 + 8 (Rahmen-Standard) + 19 (Datensatzmarkierer)

Diese Angaben werden oft auch gebraucht, wenn CenterForm zur geeigneten Plazierung der Formular- und Datenblattansicht eingesetzt werden soll, und es zu sehr nervt, sich per testweise MoveSize-Direktfenster-Aufrufe an die Wunschmaße heranzutasten. siehe auch 'Fenstergröße ermitteln'.

Die Maximalmaße für Quasi-Vollbild-Darstellung innerhalb von MS-Access sind von den Rändern unabhängig und verhalten sich proportional zur Auflösung:

max. Breite = xAuflösung
max. Höhe = yAuflösung - 57

Die aktuelle Version von CenterForm akzeptiert die Größenparameter 0,0 und stellt die oben genannte Quasi-Maximierung selbst her.

Der von den verschiedenen Randtypen benötigte Platz wird innerhalb des Dokumentenfensters verbraten, so daß bei normaler Fensterdarstellung die Maße leicht angepasst werden müssen, um z.B. Navigationsbuttons oder Formular-Detailbereiche ganz darzustellen.

Alle folgenden Angaben sind relativ zu 'veränderbaren' Rändern.

veränderbar: optimale Formularbreite für Darstellung der Navigations-Buttons = 281

Dialog: Da ein Dialog-Rahmen nur in Verbindung mit der Eigenschaft 'Popup' dargestellt werden kann und damit absolut auf dem Desktop plaziert wird, aber selbst in einer Datenblattansicht mit Dialograhmen nicht betrieben werden kann, sind Maximal-Maße nicht relevant. Allgemein kann man 2 Pixel Zuschlag für Dialog-Ränder geben, um Formularinhalte wieder ganz darzustellen.

dünn: NavigationsButtons = 275, allgemein 6 Pixel Abzug zu veränderbar

kein: (eigentlich 8 Pixel Abzug wegen fehlendem, dünnen Rand), aber de facto:
NavigationsButtons = 274 wegen Button-Rand.
35 weniger Höhe wegen fehlender Ränder sowie Dokument-Titelleiste.


**** Und so wird's gemacht: ****

Als erstes brauchen wir ein paar API-Funktionen inklusive RECT-Datentyp und
zentrale Routinen, die in einem Modul abgelegt werden können:

Type RECT                               ' Datenstruktur für die
     Left As Integer                    ' Eckpunkte eines Fensters
     Top As Integer                     ' -> GetWindowRect
     Right As Integer
     Bottom As Integer
End Type


Declare Function GetDesktopHwnd Lib "User" () As Integer
Declare Sub GetWindowRect Lib "User" (ByVal hWnd As Integer, lpRect As RECT)
Declare Sub SetWindowPos Lib "User" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer)


Sub GetResolution (x As Integer, y As Integer)

    On Error GoTo err_GetResolution

    Dim tRS As RECT

    GetWindowRect GetDesktopHwnd(), tRS

    x = tRS.Right - tRS.Left
    y = tRS.Bottom - tRS.Top

    Exit Sub


err_GetResolution:

    Fehler "GetResolution"
    Exit Sub

End Sub

Und hier das Objekt der Begierde:

Sub CenterForm (F As Form, WF As Integer, HF As Integer, WD As Integer, HD As Integer)

    On Error GoTo err_CenterForm

    Dim xDT As Integer, yDT As Integer
    Dim x As Integer, y As Integer

    Const TITLEBAR = 29                     ' Höhe in Pixeln
    Const HEIGHTKORR = 57                   ' Höhen-Korrektur Acess-Elemente

    GetResolution xDT, yDT                  ' Desktop-Maße

    If WF = 0 Then WF = xDT                 ' wenn Maße-Parameter = 0
    If HF = 0 Then HF = yDT - HEIGHTKORR    ' Quasi-Vollbild-Darstellung
    If WD = 0 Then WD = xDT
    If HD = 0 Then HD = yDT - HEIGHTKORR

    If F.CurrentView = 2 Then               ' bei Datenblattansicht
       WF = WD                              ' Werte an WF/HF übrgeben
       HF = HD
    End If

    x = ((xDT - WF) / 2)                    ' x ausmitteln
    y = ((yDT - HF) / 2) - TITLEBAR         ' y ausmitteln innerhalb der Parent-Anwendung

    y = y - (y / 6)                         ' dynamische Geschmacks-Höhenanpassung

    SetWindowPos F.hWnd, 0, x, y, WF, HF, 0 ' do it

    Exit Sub


err_CenterForm:

    Fehler "CenterForm"
    Exit Sub

End Sub

Wunderbar, und im Formular, dem eigentlichen Objekt unserer Begierde, passiert dann nicht mehr viel:

Dim iLastView As Integer im Deklarationsteil, (Spreu vom CurrentView-Wechsel trennen)

und

Sub Form_Current ()

    If Me.CurrentView <> iLastView Then
       iLastView = Me.CurrentView
       CenterForm Me, 281, 95, 1024, 711
    End If

End Sub

Zu CenterForm nur soviel:

Im Fenstermodus sollten bei kleinen Masken die Navigationsbuttons ganz dargestellt werden (siehe Ausführungen zum Thema Ränder im Vorfeld). Die maximale Breite darf der Auflösungsbreite entsprechen, bei der maximalen Höhe müssen 57 Pixel für Access-Elemente abgezogen werden. Der Clou besteht aber darin, daß CenterForm das selbst erledigt. Ein flockiges CenterForm Me, 281, 95, 0, 0 stellt in der Formularansicht eine niedrige Maske dar, deren Navigationsbuttons ganz sichtbar sind (veränderbare Rahmen); in der Datenblattansicht wird dieses Formular in CenterForm mit Korrektur
auflösungsbezogen auf Quasi-Vollbild aufgeblasen.


Größe beim Blättern   Quelle: dmt   Datum: 03.2004   nach oben

DATENSÄTZE in FORMULAREn BLÄTTERN:

Bei Access-Formularen kommt es häufig vor, daß in der Formularansicht beim Blättern zwischen den Datensätzen der Detailbereich mehr oder weniger arg gescrollt wird, bevor Access sich beim nochmaligen Blättern, dazu herabläßt, einen anderen Datensatz zu zeigen. Meistens muß dann im Entwurfsmodus ( manchmal sogar mit ausgeschaltetem Raster ! ) die Höhe des Detailbereichs mühsamst abgecheckt werden, um passende Werte zu erhalten.

Für ein Formular, das optimalerweise den gesamten Bildschirm-Arbeitsbereich ausfüllen soll ( Einstellungen: ohne Rahmen und Windows-Elemente, keine anderen Formular-Bereiche außer Detailbereich, mit Datensatzmarkierer und Navigationsschaltflächen ), kann eine Standard-Detail-Höhe angegeben werden: 10,8 cm nach dem Entwurfs-Lineal oder 6122 Twips. Auch möglich ist in der Formularansicht die Menüoption Fenster / Formular an Größe anpassen und schließen und speichern.

Auflösung    cm      Twips

640 * 480    10,801   6122
800 * 600    13,959   7915
1024 * 768   18,4    10433
1152 * 864   20,951  11879
1280 * 1024  25,182  14278
1600 * 1280  29,852  16926


Größe einstellen   Quelle: dmt   Datum: 03.2004   nach oben

FORMULARE an AUFLÖSUNG anpassen / FIT2RES / CENTERFORM / Fenstergrößem einstellen:

- Allem voran sei gesagt, daß Fenstergrößen nicht nur durch Probieren (man ist ja geduldig), sondern auch korrekt ermittelt werden können, s.a. Funktion GetWindowDimensions().

- Bei Programmstart kann auch die bei der Entwicklung vorgesehene Auflösung überprüft und gegebenenfalls ein Hinweis angezeigt werden. siehe Check_Resolution().


ALLGEMEINE STRATEGIEN:

- Größenzuweisung per DoCmd MoveSize, bei Bedarf bei Ansichtswechsel mit formularglobaler Variable. Keine Berücksichtigung der Auflösung.

- Formulare mit großen Masken einfach bei Form_Activate in den Vollbild-Modus und bei Form_Deactivate wieder Restore. Wenig Code, aber keine Berücksichtigung der Auflösung.

- Die Größenbestimmung bei Ansichtswechsel mit formularglobaler Variable per CenterForm ist die variabelste Lösung, zumal absolut Auflösungs-sensitiv. Der 0,0-Parameter erzwingt eine Art Quasi-Vollbild, bei der das Child-Window innerhalb der Parent-Anwendung noch über eigene, veränderbare Rahmen verfügt.

- eine Mischform stellt folgende Vorgehensweise dar:

  FormOpen ->

    CenterForm Me, 425, 344, 425, 344    ' Anpassung z.B. an native Maskengröße

  Form_Current ->

    If iLastView <> Me.CurrentView Then
       If Me.CurrentView = 2 Then                ' Datenblatt
          DoCmd Maximize
       Else                                      ' Formular
          DoCmd Restore
       End If
       iLastView = Me.CurrentView
    End If

  Form_Close und Form_Deactivate -> DoCmd Restore

In Form_Open wird eine Standardgröße eingestellt, die per CenterForm Auflösungs-bezogen mittig dargestellt wird. Bei Ansichtswechsel in die Datenblattansicht erfolgt ein simples DoCmd Maximize, das sicherheitshalber bei Form_Close wieder zurückgesetzt werden muß. iLastView noch formularglobal definieren und fertig.

* * * *

Positionieren von Fenstern mit MOVESIZE:

Die Hilfe zu MoveSize weist je nach Einstiegspunkt Fehler auf.

Die richtige Syntax lautet 'DoCmd MoveSize X, Y, Breite, Höhe'.

Ferner muß zum Positionieren von Fenstern gesagt werden, daß Access Fenster, die ohne Titelleiste definiert wurden, in verschiedenen Höhen positioniert, abhängig davon, ob das Fenster als GEBUNDEN oder nicht ausgelegt ist, da Access bei der Positionierung scheinbar die ohnehin nicht dargestellte Fenstertitelleiste bei der Positionierung mit berücksichtigt.

Trotz allem kann die Aktion MoveSize als probates Mittel angesehen werden, um folgendes Problem zu beheben:

Ein Fenster soll in einer bestimmten Größe und einer bestimmten Position angezeigt werden. Hierfür bietet sich eigentlich an, die entsprechenden Formulareigenschaften zu benutzen. Durch Änderungen des Formular-Entwurfes wird aber eigenartigerweiser auch die Größe des Formulares in kleinerer Form neu abgespeichert. Bevor man an diesem Phänomen verzweifelt, sollte man z.B. im Ereignis FORM_OPEN die Aktion MoveSize bemühen.

Das Formular wird ohne störende Effekte in der gewünschten Form geöffnet.

DoCmd MoveSize 0, 0, 9600, 6350 positioniert ein Formular mit veränderbarem Rahmen so, daß es den gesamten Platz einnimmt, der von den seitlichen Bildrändern sowie der Menüleiste und der unteren Statusleiste begrenzt wird,


Größe wechseln   Quelle: dmt   Datum: 03.2004   nach oben

FENSTERGRÖßEN EINFACH UMSTELLEN:

In der einfachsten Form soll ein Formular a'la Adressen den Bildschirm ganz ausfüllen (Vollbild ist ok), aber zum Beispiel untergeordnete Stammdaten-Formulare in einer Teildarstellung erscheinen. Ein simples DoCmd Maximize / Restore in Form_De-Activate sollte reichen, auch ohne iWinFrames-formularglobale Variable. Man erhält alle Windows-Vorteile (ein
Widerspruch in sich) bezüglich erweiterter Anwendungstitel etc. und kommt mit wenig Kot aus. Trotzdem sollte beachtet werden, daß die Größe des Detailbereiches ok ist und daß im Entwurfsmodus die Fenstergröße so gewählt wird, daß im Restore-Modus nur dezente Verkleinerungen vorgenommen werden.
Auflösungsbezogene Maximalgrößen für den Detailbereich siehe DATENSÄTZE in FORMULAREn BLÄTTERN.

Eine ausgereifte Version (Stand 06.2002) sieht so aus:


Deklarationsteil ->

Option Explicit

Dim iLastView As Integer


Sub Form_Activate ()

    If Me.Currentview = 2 Then DoCmd Maximize

    DoCmd ShowToolbar "Standard", A_TOOLBAR_YES

End Sub


Sub Form_Deactivate ()

    DoCmd Restore

    DoCmd ShowToolbar "Standard", A_TOOLBAR_NO

End Sub


Sub Form_Close ()

    Form_Deactivate

End Sub


Sub Form_Current ()

    If iLastView <> Me.Currentview Then
       If Me.Currentview = 2 Then                ' Datenblatt
          DoCmd Maximize
       Else                                      ' Formular
          DoCmd Restore
       End If
       iLastView = Me.Currentview
    End If

End Sub


Hot-Spots   Quelle: dmt   Datum: 03.2004   nach oben

HOT-SPOTS:

Auch in Access ist es möglich, auf z.B. einem grafischen Element mehrere 'Hot-Spots' zu verteilen, die angeklickt werden können und somit diverse Aktionen auslösen. Erreicht werden kann das durch normale Schaltflächen, deren Eigenschaft Transparent auf ja gesetzt ist. Kommt sehr gut, siehe Formular Hot-Spots in dmttrial.mdb.


Kombinationsfelder   Quelle: dmt   Datum: 03.2004   nach oben

KOMBINATIONSFELDER versus EINGABEFELDER:

verfügen im Gegensatz zu normalen Textfeldern nicht über die Eigenschaften Standardwert und Eingabeformat (auch Format nicht). In der Dokumentation soll Standardwert sich auch auf die Kombifelder beziehen, beim Eingabeformat hört der Spaß dann aber auf.

Einfachere Aufgaben durch Tastaturroutinen zu erledigen, muß als undurchführbar angesehen werden, da sich die Kombifelder allen Laufzeit-Zeichenzuweisungen widersetzen.

* * * *

Ein Kombinationsfeld kann eine mehrspaltige Liste anzeigen. Wurde ein Wert gewählt, kann mit der Eigenschaft Column darauf Bezug genommen werden. Dies funktioniert aber nur, wenn die Liste des Kombinationsfeldes geöffnet wurde. Wird dagegen ein Wert durch Eintippen und mit Hilfe der Eigenschaft 'automatisch ergänzen' bestimmt, gibt das Abfragen der Spalten einen leeren String zurück ( So war das jedenfalls im Hause Kelkel ).

Da dieses Phänomen auf meinem Rechner ( 386 DX 33 / MS-DOS 5.0 / Win 3.1 ) nicht nach-vollzogen werden konnte, kommt nur noch (die Beta-Version von) Windows 95 in Frage.

Abhilfe:

SendKeys "{F4 2}"    ' 2-maliges Betätigen der Listenfunktion


Kombinationsfelder a la Klumpp   Quelle: dmt   Datum: 03.2005   nach oben

Mit Kombinationsfeldern kann man aber auch etwas völlig anderes machen. Im Hause Klumpp wurden z.Bsp. Klappfelder soweit minimiert, bis nur noch die DropDown-Schaltfläche zu sehen war. Diese 'Schaltflächen' wurden dann so an Textfelder herangesetzt, daß das Ganze wie ein einziges Kombinationsfeld aussieht.

Die Bedienung dieser Klappfelder erfolgt dann ausschließlich über die Maus, ausgewählte Einträge werden dann dem Inhalt des Textfeldes vorangestellt, mit einem trennendem '; '. Mit sowas, dem Vergessen vom Wissen um die Weihen der referentiellen Integrität und der bedenkenlosen Bereitschaft zur ausschließlich Suche per Wildcard (bye, bye performance) werden dann umfangreichste Wert-Kombinationen 'arrangiert'.

Auch wenn mir sowas zutieftst zuwider ist, so kann es doch vorkommen, daß ich auf solche Wurscht-Lösungen zurückgreife. Zum einen kann man sich mit relationen Kunststückchen spätestens bei der Formulierung kombinierter Suchbedingungen mehr als nur ein Bein stellen, und außerdem, wer zahlt das denn überhaupt. Also auf zum Pfusch:

Dem Ereignis After_Update des getürkten Klappfeldes wird folgender Quelltext zugeordnet:

    Add_ComboBox_Value Me!SCHLAGW, Me!cmbSchlagw

und dann noch die zentrale Rund-um-sorglos-Routine, bei der C1 für das Textfeld und C2 für das Kombinationsfeld steht:

Sub Add_ComboBox_Value (C1 As Control, C2 As Control)

    On Error GoTo errAdd_Combobox_Value

    Dim iPos As Integer

    If C2.ListIndex > -1 Then       ' Nur, wenn ein gültiger ListIndex vorliegt !

       C1.SetFocus                  ' Focus auf das eigentliche Feld setzen

       iPos = InStr(C1, C2)         ' ist der neue Wert bereits enthalten ?

       If iPos Then                 ' Wenn ja, dann
          Beep                      ' Signalton
          C1.SelStart = iPos - 1    ' und den bereits vergebenen String
          C1.SelLength = Len(C2)    ' markieren
       Else
          C1 = C2 & "; " & C1       ' Neuen Wert + ';' dem alten voranstellen
          C1.SelLength = 0          ' und Cursor an Anfang setzen.
       End If

       C2 = Null                    ' Klappfeld-Wert leeren.

    End If

   Exit Sub


errAdd_Combobox_Value:

    If Err = 2448 Then
        Beep
        MsgBox "Das Feld ist voll"
    ElseIf Err = 94 Then
        Resume Next
    Else
        Fehler "Add_Combobox_Value: " & C2.Name
    End If

    Exit Sub

End Sub

na denn mal los ...


Kombinationsfelder, falsche Spalten-Werte   Quelle: dmt   Datum: 03.2005   nach oben

FALSCHE WERTE in KOMBINATIONSFELDER-SPALTEN:

Aber das soll leider nicht der einzige Wermutstropfen im Umgang mit den an sich komfortablen Kombinationsfeldern sein:

Abgesehen von der Programmier-Struktur-technischen Beschränkung von Listen- und Kombinationsfeldern auf 65 Tsd. Datensätze (was wohl nur äußerst selten vorkommen dürfte und dann eben anders abgecheckt werden muß), mußte im Hause DMT ein übler Bug entdeckt werden, der wohl nicht auf Win95 oder etwas anderes außer MS-Access selbst geschoben werden kann:

Gegeben ist in einem Formular ein Kombinationsfeld, das ein Textfeld, dessen mögliche Einträge mit denen in einer anderen Basis-Tabelle gespeicherten identisch sein sollen.

In dem speziellen Fall ist dies ein Kombinationsfeld für 'Termin_Gründe', das in einem Termin-Formular verwendet wird. In der Tabelle Termin_Gründe ist für jeden Grund u.a. auch die Eigenschaft 'geschäftlich' vermerkt. Das 2-spaltige Kombinationsfeld zeigt dies beim Aufklappen auch an. Abhängig von dieser Eigenschaft 'geschäftlich' soll im Termin-Formular eine Schaltfläche 'pb_Leistungsrapport' (de-)aktiviert werden. Im Source-Code wird für für das Ereignis Form_Current die Eigenschaft Column(1) des Kombinationsfeldes abgefragt, um festzustellen, ob der dem aktuellen Termin zugewiesene Grund geschäftlich ist oder nicht.

So, und jetzt kommt's !

In der Formularansicht (Endlos- oder Einzel-) geht alles klar, aber in der Datenblattansicht, in der die Kombinationsfelder im Gegensatz zu z.B. Unterformularen sehr wohl zur Verfügung stehen, gibt die Abfrage der Kombinationsfeld-Zeilen-Feldwerte per Me!Steuerelement.Column(x) die Werte der FALSCHEN Zeile, die in der aufgeklappten Liste direkt nach dem richtigen, gespeicherten Eintrag steht, zurück.

Selbst in abgespecktester Form tritt der Fehler in der oben beschriebenen Form auf !

Als Abhilfe konnte folgende, traurige Lösung ermittelt werden, mit der ich mein Haupt in denkbar unwilligster Demut vor der Pissigkeit dieser verschissenen Soft-Un-Ware dieser Kack-Firma, deren Programmierer scheinbar allesamt nur dreckige Scheißer und xxxxxxxx xxxxxxxxxxxxxx xxxxxxx xxxxxxxxxxxxxxxxxx-xxxxx-xxxxxxx sind, für die 'Söhne einer räudigen Hündin' bestenfalls als Ehrenbezeugung herhalten kann, beuge !

Wer auch immer so tief in die Abgründe von MS-Exzess gesunken ist, daß er (oder sie ???) es nötig hat, diesen Text bis zu dieser Stelle zu lesen, möge die vorangegangenen Worte nicht in den falschen 'deep throat' bekommen, sondern die Intention des unwürdigsten aller Verfasser in ihrem wahren Kern erkennen (hab' ich eh' zensiert; war dann doch etwas zu hart).

Und so wird's gemacht:

Dim gesch% as Integer

    If Me!Grund <> Me!Grund.Column(0) Then
       gesch% = DLookup("Geschäftlich", "Termin_Gründe", "Bezeichnung='" & Me!Grund & "'")
    Else
       gesch% = Me!Grund.Column(1)
    End If

Auf deutsch heißt das, wenn der WAHRE Wert des gebundenen Tabellenfeldes des Kombinationsfeld mit dem angeblichen Wert der ersten Zeile Column(0) NICHT übereinstimmt, dann wird der WAHRE Wert der dem Kombinationsfeld eigentlich bekannten Eigenschaft 'geschäftlich' manuell per DLookup aus der Tabelle geholt, ansonsten kann er ja unbedenklich dem Kombinationsfeld entnommen werden.

Da dieser Fehler nicht auschließlich innerhalb des Form_Current-Ereignisses, sondern auch in Steuerelement_AfterUpdate auftritt, muß von der Code-mäßigen Column-Auswertung völlig zugunsten eines doch recht schnellen DLookup abgesehen
werden.

Es konnte gezeigt werden, daß dieser Fehler bei explizitem Mausklick nicht, bei ergänzter Tatstatureingabe aber sehr wohl auftritt. Der einzige auswertbare Wert eines Kombinationsfeldes scheint wohl nur sein eigener Wert zu sein.

Eigentlich beachtlich, daß es in den letzten Jahren dieses Jahrtausends überhaupt noch möglich ist, mit Hilfe einer relationalen Datenbank an Feld-Werte gespeicherter Datensätze heranzukommmen. Irgendwas muß früher falsch gelaufen sein ...

Man kann sich dieser verschissenen Problematik auch dadurch entziehen, indem man Access und seinen Kombinationsfelder auch nur das glaubt, was einigermaßen Ernst genommen werden kann, nämlich den Inhalt seiner Kombinationsfelder selbst. Zu dem angezeigten Wert gehörende Werte anderer Felder müssen dann halt per explizitem Dlookup aus den Tabellen selbst geholt werden und fertig.

Im März 2005 wurde ich von Uwe Renner angeschrieben, der das Problem (leider) bestätigen konnte und einen sehr simples Workaround herausgefunden hat:

... hammerhart: die Spaltenüberschriften sinds:

an:  Access verrutscht in der Zeile
aus: alles okay

* * * *

Aber selbst für diese 'Lösung' kennt Microsoft noch eine Variante, in der das oben beschriebene Problem unbefangen zu Tage tritt:

Gegeben sei in einem Feld 'Vorwarnung' der Standardwert-Ausdruck

    =[Datum]+[von]-[Grund].Spalte(2)'

Hier wird ein Datums-Single ermittelt, der dem Zeitpunkt-genauem Datum des Termines entspricht und von dem ein Wert, der als 'Vorwarnung' in Tagen in der Tabelle 'Termin_-Gründe' hinterlegt ist. Das Kombinationsfeld zeigt unter anderem auch den für diese Terminart eingestellten Vorwarnwert an.

Nicht nur, das auch hier das oben beschriebene Problem eintritt.

Es kommt noch besser:

Da dieser Ausdruck in den Eigenschaften des Entwurfsmodus' festgelegt ist, wird er, wie so manch' anderes auch, zu einem Zeitpunkt ausgewertet, für den für einen Programmierer kein Zugriff besteht. Der Teilausdruck '[Grund].Spalte(2)'
gibt 'NULL' zurück, ohne einen Fehler zu melden. Auch Code-Fickereien per Direkt-Fenster wie 'Me!Grund.Requery' können Access nicht veranlassen, die im Kombinationsfeld enthaltenen Werte auszugeben.

Es sieht zunehmend so aus, als ob die Fähigkeit, mehrere durch die hinterlegte Abfrage ausgegebene Spalten anzuzeigen, zwar für das Auge des geneigten Betrachters ganz nett, aber ansonsten nicht zu gebrauchen ist.

Selbst ein Editieren und Bestätigen eines Eintrages führt nicht unbedingt zur Übernahme des entsprechenden Wertes !

Wenn also Daten eine Rolle spielen (wo eigentlich ?, EDV im Sinne des Wortes scheint ja wohl abgeschafft worden zu sein, oder bedeutet es heute etwa Elektronische Datenvernichtung), ist es scheinbar angesagt, sie per schnellem DLookup direkt aus der Tabelle zu holen. Dort sind Daten zumindest bei der Version 2.0 noch gut aufgehoben.

Und noch mehr SCHEIßE zum Thema Kombinationsfelder:

Bei der Entwicklung der Kolben-Mahle-Projekt-Datenbank mußte festgestellt werden, daß Kombinationsfelder den Zustand NotInList manchmal nicht erkennen. So wird z.B. bei einem 2-stelligen Eintrag ein entsprechender Rest ergänzt und vorgeschlagen, aber das Entfernen des in diesem Falle ungeliebten Restes und Bestätigen des Eingabefragmentes führt zum völlig unverständlichen Ergänzen der erstbesten Entsprechung. Dieser Fehler ist bei den nativen DMT-Tabellen nicht nachvollziehbar, tritt aber, wenn man eine einfache Tabelle gemäß der Mattner-Vorgabe entwirft, zuverlässig ein !!!!

Nach langwierigen Tests konnte dieser Bug, nachdem obendrein noch ein weiterer gefunden wurde, dingfest gemacht werden:

Wenn in einem scharfen Kombinationsfeld mit den Eigenschaften 'Automatischen ergänzen=ja' und 'Nur Listeneinträge=ja' zu einem eingegebenen Text nur noch EINE vorhandene Entsprechung existiert, und man den vorgeschlagenen, markierten Rest entfernt, wird nach Bestätigung der Eingabe just dieser Rest wieder hinzugefügt. Zum Beispiel erzeugt die Eingabe von 'al' den NotInList-Fehler, wenn zwei Datensätze 'aleker' und 'allgeier' vorhanden sind. Die Eingabe von 'all' wird jedoch immer mit 'geier' ergänzt und das Ereignis NotInList tritt dann halt einfach nicht ein.

Und, ein Bug kommt selten allein, weils so schön ist, gleich noch eins obendrauf:

In einer unsortierten Liste, bei der ein 'a' im unteren Bereich der Tabelle existiert, wird die Eingabe von 'a' mit 'leker' ergänzt. Nach Entfernen und Bestätigen meckert Access, daß 'a' nicht in der Liste enthalten ist. Spult man aber in der Liste solange nach unten, bis 'a' auftaucht, kann es sehr wohl eingesetzt werden.

Lösungen:

Den zuletzt beschriebenen Bug zu bekämpfen (Contraprogrammierung) halte ich für sinnlos. Kombinationsfelder zeigen meist auf wichtige Felder, die entweder bereits indiziert sind, oder, weil eine alphabetische Reihenfolge einfach sinnvoll ist, per 'ORDER BY'-Anweisung sortiert werden. In einem solchen Kontext tritt der Fehler einfach nicht auf.

Der zuvor beschriebene Bug ist da ungleich schwieriger.


Kombinationsfelder, Herkunft   Quelle: dmt   Datum: 03.2004   nach oben

KOMBINATIONSFELDER - DATENSATZHERKUNFT - ROWSOURCE:

Schon mehrmals kam es vor, daß eine ComboBox in einem Formular SQL-kontrollierte Wertelisten anzeigt, die mit bestimmten Werten eines anderen Formularfeldes in Verbindung stehen.

So soll z.B. ein Klappfeld 'BreakEven zu' in einem Telekom-Tarifvergleichs-Formular alle gespeicherten Provider, die vom angezeigten verschieden sind, auflisten.

    SELECT Anbieter FROM Tarife_Telekom WHERE Anbieter<>[Formular]![Anbieter];

als Datenherkunft löst das Problem. Ähnliches im Mertlik-Projekt ließ sich ums Verrecken nicht bewerkstelligen, da mußten die Formular-Feld-Bezüge dann in einer gespeicherten Abfrage untergebracht werden. Dreck, Dreck, Dreck !

Außerdem kam es auch schon mal vor, daß eine derartige Zuweisung 'on the fly' nicht zu einer Aktualisierung des Klappfeldinhaltes kam, so daß in der entsprechenden Set-Routine ein explizites Requery gesetzt werden mußte.


Kombinationsfelder, NotInList   Quelle: dmt   Datum: 03.2009   nach oben

NOTINLIST: Der im folgenden beschriebene Umgang mit den an sich praktischen Kombinatinsfeldern bezieht sich auf die Unzulänglichkeiten der an sich wunderbaren Version Access 2.0: Nachfolgend einige Variationen zum Umgang mit 'scharfen' Kombinationsfeldern, die nach vielen Jahren Geärgere über Unzulänglichkeiten der Microsoft-Produkte auch einen Überblick über die mühsame Annäherung an vernünftige Lösungen sowie meine eigenen Fortschritte geben. Die aktuellste und hoffentlich beste Version findet sich weiter unten. siehe auch SUCHEN VON DATENSÄTZEN wegen Stammdaten-Aufruf. Allgemein sollte erwähnt werden, daß die Gewährleistung einer konsistenten Schreibweise relevanter Begriffe ( z.B. 'lieferant' aut. als 'Lieferant' schreiben ) durch ein Kombinationsfeld nur erreicht werden kann, wenn 'Nur Listeneinträge' auf ja steht ! Die wahrscheinlich beste Art, auf das Ereignis 'Nicht in Liste' zu reagieren, besteht nach derzeitigem Stand in folgender Lösung: NULLWERTE in KOMBINATIONSFELDERN bei NUR LISTENEINTRÄGE / LIMITTOLIST In die Ereignisroutine x_NotInList(...) wird nur noch EINE Zeile Code eingefügt:

    Response = NichtinListe("Adressen", "Suchname", NewData)
Zusatz: Leider kommt es sehr häufig, wenn nicht immer vor, daß nach dem Ereignis NotInList das Ereignis AfterUpdate nicht mehr eintritt. Wenn also mit diesen 'potenten' Kombinationsfeldern gearbeitet werden soll, dann ab jetzt NOCH mehr Vorsicht. Wenn Before- und AfterUpdate relevant sind, dann siehe AFTERUPDATE. Um es für den Anwender Rolls-Royce-mäßig zu machen, werden Statuszeile sowie Maus- und Tastaturereignisse abgecheckt: Statuszeile -> "<Doppelklick/Strg+Leer> öffnet Stammdaten". Maus -> Doppelklick: ZeigeStammdaten TabName Taste ab:
    If KeyCode = 32 And Shift = CTRL_MASK Then
       ControlName_DblClick (0)
    End If

Der folgende NichtInListe()-Code sollte zu den ganz alten Geschichten gehören, da er noch mit Typ-Suffix ($ für String-Variablen) und einer globalen Variable arbeitet.

Die Funktion NichtInListe ist ein bißchen größer geworden, kann aber nun: - die entscheidende Variable Response direkt steuern - Werte sowohl in eine andere Basistabelle wie auch in das Kombinationsfeld selbst einfügen. - die Anzeige "Sie müssen einen Eintrag ..." elegant unterdücken - und zum nächsten Feld springen, als ob nichts gewesen wäre - und obendrein aussagekräftig auf eine Basistabellenfeld-Eingabepflicht reagieren. - Öffnen eines Basistabellen-Formulares für umfangreiche Eingaben - Setzen globaler Control/String-Werte für Aktualisierung nach Verlassen des Pflegeformulares.
Function NichtinListe (Basistabelle$, Feldname$, NeuerWert$) As Integer

    On Error GoTo err_NichtinListe

    Dim ret As Integer

    ret = DATA_ERRDISPLAY

    Set gcActiveControl = Screen.ActiveControl
    gsActiveControlValue = NeuerWert$

    If NeuerWert$ <> "" Then

       Beep

       Antwort% = MsgBox("'" + NeuerWert$ + "' in Liste '" + Basistabelle$ + "' einfügen ?", 36, "Wert in Liste nicht vorhanden")

       If Antwort% = 6 Then
          Anweisung$ = "INSERT INTO " + Basistabelle$ + " (" + Feldname$ + ") VALUES ('" + NeuerWert$ + "')"
          DoCmd SetWarnings False
          DoCmd RunSQL Anweisung$
          DoCmd SetWarnings true
          ret = DATA_ERRADDED
       Else
          ret = DATA_ERRCONTINUE
       End If

    ElseIf TypeOf gcActiveControl Is ComboBox Then

       If gcActiveControl.LimitToList = True Then
          gcActiveControl = Null      ' -> Eingabepflichtfehler oder weiter
          SendKeys "{TAB}", True
          ret = DATA_ERRCONTINUE
       End If

    End If

    NichtinListe = ret

    Exit Function


err_NichtinListe:

    If Err = 3314 Then      ' = Feld '|' kann keinen Nullwert enthalten
       ' Fehler beim Anlegen eines Stammdaten-Satzes, für den
       ' weitere Felder mit Eingabepflicht bestehen.
       ' Stattdessen versuchen, ein gleichnamiges Formular
       ' zu öffnen und Neueingabe vorzubereiten
       DoCmd OpenForm Basistabelle$, , , , A_ADD
       Forms(Basistabelle$)(Feldname$) = NeuerWert$
    ElseIf Err = 2448 Then  ' Wert kann nicht gesetzt werden.
       Beep                 ' Fehler bei Eingabepflicht
       MsgBox "Das Feld '" & Feldname$ & "' kann keinen Nullwert enthalten !",
16, "Funktion NichtinListe"
    Else
       Fehler "NichtinListe"
    End If

    NichtinListe = DATA_ERRCONTINUE

    Exit Function

End Function
Für den Trick mit dem Aktualisieren mußten leider globale Variablen bemüht werden: Global gcActiveControl As Control Global gsActiveControlValue As String damit in der folgenden Sub das Steuerelement, von dem aus NichtInListe aufgerufen wurde, aktualisiert werden kann.
Sub AktualisiereSteuerelement ()

    On Error GoTo err_AktualisiereSteuerelement

    gcActiveControl = Null

    gcActiveControl.Requery

    gcActiveControl = gsActiveControlValue

    Exit Sub


err_AktualisiereSteuerelement:

    If Err = 2467 Then
       ' Feld im Ausdruck nicht mehr vorhanden, wenn Basis-
       ' tabellenformular solo geschlossen wird
       Exit Sub
    Else
       Fehler "AktualisiereSteuerelement"
       Exit Sub
    End If

End Sub
Perfekterweise erfolgt die Zuweisung globaler Variablen auch in ZeigeStammdaten
Sub ZeigeStammdaten (FName As String)

    On Error GoTo err_ZeigeStammdaten

    Dim v As Variant

    v = Screen.ActiveControl

    Set gcActiveControl = Screen.ActiveControl
    gsActiveControlValue = gcActiveControl

    DoCmd OpenForm FName

    If Not IsNull(v) Then DoCmd FindRecord v

    Exit Sub


err_ZeigeStammdaten:

    Fehler "Modul ZeigeStammdaten"
    Exit Sub

End Sub
Das mutet gegenüber älteren Versionen zwar ein bißchen umständlich an, zumal in jedem Pflege-Formular bei DeActivate (gibt's eh' schon wegen Symbolleisten ausblenden) der Aufruf AktualisiereSteuerelement erfolgen muß, aber dafür ist das jetzt so richtig narrensicher. Selbst wenn der Anwender nach Änderungen in einem Pflege-Formular 'danebenklickt', tritt bei erfolgter Variablenzuweisung eine Aktualisierung des ursprünglichen Steuerelementes ein. Für Eilige: Die Modul-Komponenten (globale Variablen, Sub AktualisiereSteuerelement, Sub ZeigeStammdaten und die Funktionen NichtinListe und NichtinListe2) können in einem Modul 'Kombinationsfeld' zusammengefasst werden. Die Aufrufe erfolgen: Sub AktualisiereSteuerelement in Sta-Da-Formular /FormDeactivate (warum eigentlich?) Sub ZeigeStammdaten in KombiFeld_DblClick und BeiTasteab Funktion NichtinListe in KombiFeld_NotinList (dito für NichtinListe2) Kritische Betrachtungen: Wenn für die betroffenen Tabellen-/Felder Beziehungen mit referentieller Integrität vereinbart wurden, werden die Kombifelder scheinbar automatisch aktualisiert. Bravo ! Wo das nicht funktioniert, war AktualisiereSteuerelement in Form_Deactivate des aufgerufenen Stammdatenformulares angedacht, ist aber nicht so ganz sinnig, also erst mal auf Eis legen. Die Methode FindRecord in ZeigeStammdaten greift nur für die Inhalte des ersten Formular-Feldes (wie der Bearbeiten-Suchen-Dialog) Und für Kombinationsfelder, die nicht editierbare Daten wie Monat 1-12 enthalten, aber online mit NULL überschrieben werden dürfen, tut's dann auch ein
    Response = NichtinListe("", "", NewData)
im Element_NotInList-Ereignis und alles geht klar. * * * *

Diese Version von NichtinListe () stammt ebenfalls aus frühen Access 2.0 - Tagen, ist aber einen hauch moderner (explizite Typenzuweisung und keine globalen Variablen) und auch etwas einfacher:

Aufruf wie üblich in xyz_NotInList
    Response = NichtinListe("Adressen", "Suchname", NewData)
und dann
Function NichtinListe (Basistabelle As String, Feldname As String, NeuerWert As String) As Integer

    On Error GoTo err_NichtinListe

    Dim Steuerelement As Control
    Dim sAnweisung As String, iRet As Integer, iAntwort As Integer

    iRet = DATA_ERRDISPLAY

    Set Steuerelement = Screen.ActiveControl

    If NeuerWert <> "" Then

       Beep

       iAntwort = MsgBox("'" + NeuerWert + "' in Liste '" + Basistabelle + "' einfügen ?", 36, "Wert in Liste nicht vorhanden")

       If iAntwort = 6 Then
          sAnweisung = "INSERT INTO " + Basistabelle + " (" + Feldname + ") VALUES ('" + NeuerWert + "')"
          DoCmd SetWarnings False
          DoCmd RunSQL sAnweisung
          DoCmd SetWarnings True
          iRet = DATA_ERRADDED
       Else
          iRet = DATA_ERRCONTINUE
       End If

    ElseIf TypeOf Steuerelement Is ComboBox Then

       If Steuerelement.LimitToList = True Then
          Steuerelement = Null      ' -> Eingabepflichtfehler oder weiter
          SendKeys "{TAB}", True
          iRet = DATA_ERRCONTINUE
       End If

    End If

    NichtinListe = iRet

    Exit Function


err_NichtinListe:

    If Err = 2448 Then   ' Fehler, wenn Eingabepflicht gesetzt
       Beep
       MsgBox "Für das Feld '" & Feldname & "' besteht eine Eingabepflicht !", 16, "Funktion NichtinListe"
    Else
       Fehler "NichtinListe"
    End If

    NichtinListe = DATA_ERRCONTINUE

    Exit Function

End Function
* * * * NOTINLIST / STAMMDATEN NEU ANLEGEN: 1-feldige Datensätze in Stammdatentabellen können per den oben beschriebenen NotInList-Routinen mit minimaler Benutzerinteraktion angelegt werden. Schwieriger wird es hingegen, wenn aus einem Klappfeld heraus Datensätze mit umfangreicherer Struktur angelegt werden müssen. Da gab es schon manche praktikable Lösung und jetzt auch noch diese. Sie erzeugt zwar etwas redundanten Code, ist aber im Bedarfs-Einzel-Fall besser anpassbar, als wenn man alle Register der Kapselung gezogen hätte. Für das Klappfeld gilt:
Sub Suchname_NotInList (NewData As String, Response As Integer)

    Beep

    If MsgBox("Wollen Sie einen neuen Adressen-Datensatz '" + NewData + "' anlegen ?", 52, "Unbekannter Adressen-Suchname") = 6 Then
       Response = DATA_ERRCONTINUE
       DoCmd OpenForm "Adressen", , , , A_ADD, , "Neu"
       Forms!Adressen!Suchname = NewData
    End If

End Sub
Interessant ist die unspektakuläre Zuweisung des neuen Wertes nach dem OpenForm, während als OpenArg nur ein simples "Neu" übergeben wird. Im Formular der Begierde steht dann:
Sub Form_AfterUpdate ()

    On Error GoTo err_AfterUpdate_Adressen

    If Not IsNull(Me.OpenArgs) Then
       If Me.OpenArgs = "Neu" Then
          If IsFormOpen("Termine") Then
             Forms!Termine.SetFocus
             Forms!Termine!Suchname = Forms!Termine!Suchname.OldValue
             Forms!Termine!Suchname.Requery
             Forms!Termine!Suchname = Me!Suchname
             DoCmd Close A_FORM, Me.Name
          End If
       End If
    End If

    Exit Sub


err_AfterUpdate_Adressen:

    If Err = 2448 Then
       DoCmd RunMacro "Menübefehle.RückgängigFeld"
       Resume Next
    Else
       Fehler "AfterUpdate_Adressen"
    End If

    Exit Sub

End Sub
Hier kann sogar während der Eingabe der ursprünglich neue Wert geändert werden, die OldValue-Zuweisung erlaubt ein folgendes, meckerfreies Neuabfragen des Klappfeldes und selbst mit dem Fokus sollte alles klargehen. * * * * KONSISTENTE EINHEITLICHE SCHREIBWEISE / KONSISTENZ / INNERHALB EINES FELDES DER AKTIVEN TABELLE: kann wohl am besten mit Kombinationsfeldern erreicht werden, deren Eigenschaft 'Nur Listeneinträge' wahr ist. Leider muß man sich dann mit den Unwirtlichkeiten der NotInList-Problematik herumschlagen. Für die Pflege einheitlicher Begriffe in eigenen Stammdaten-Tabellen siehe NOTINLIST. Es können jedoch auch einheitliche Begriffe innerhalb eines Feldes der aktiven Tabelle verwaltet werden. Das Kombinationsfeld wird hier lediglich dazu benutzt, um Werte, die nicht in eigenen Stammdaten-Tabellen, sondern in Felder der Muttertabelle des Formulares stehen, übersichtlichkeitshalber zu pflegen und trotz LimitToList (Schreibergänzung) auch Leereinträge zuzulassen (oder auch nicht): Im NotinList-Code steht nur eine Anweisung:
Sub Zahlart_NotInList (NewData As String, Response As Integer)

    Response = NichtinListe2(Me!Zahlart, NewData)

End Sub
Alles weitere wird durch die 'Stammdaten-Tabellen-lose' NichtinListe2-Funktion gemanagt, die sowohl Leereinträge toleriert, wie auch die Begriffsvergabe steuert (Stand 06.2002):
Function NichtinListe2 (C As Control, ND As String) As Integer

    On Error GoTo err_NichtinListe2

    Dim ret As Integer

    ret = DATA_ERRDISPLAY

    If ND = "" Then
       'ND = Null
       ret = DATA_ERRCONTINUE
       GoSub NichtinListe2_weiter
    End If

    Beep

    If MsgBox("Möchten Sie '" + ND + "' als neuen Begriff aufnehmen ?", 36, "Wert in Liste nicht vorhanden") = 6 Then
       ret = DATA_ERRADDED
       GoSub NichtinListe2_weiter
    Else
       GoSub NichtinListe2_exit
    End If

NichtinListe2_weiter:

    C.LimitToList = False
    Rueckgaengig_Feld
    SendKeys "{TAB}", True
    C.LimitToList = True
    If ND = "" Then
       C = Null
    Else
       C = ND
    End If

    If ret = DATA_ERRADDED Then
       ret = DATA_ERRCONTINUE
       'C.Requery
       SendKeys "{F4}"
    End If


NichtinListe2_exit:

    NichtinListe2 = ret

    Exit Function


err_NichtinListe2:

    If Err = 2448 Then
       Beep
       MsgBox "Sie dürfen das Feld " & C.Name & " nicht leer lassen !", 16, "NichtInListe2"
       GoSub NichtinListe2_exit
    Else
       Fehler "NichtinListe2"
    End If

    Exit Function

End Function
Die erste Version kam ohne ein umständliches 'Rueckgaengig_Feld' aus, litt aber unter übelsten Formularfehlern in der Datenblattansicht. Da aber selbst in der Access-Dokumentation mit 'Rueckgaengig_Feld' gearbeitet wird, braucht sich meine neueste Version dafür auch nicht zu schämen. Das Requery des Steuerelementes macht bei NichtInListe2 keinen Sinn, da ein neuer Wert für das Feld erst dann erkannt werden kann, wenn der bearbeitete Datensatz gespeichert wurde. Das mit dem Aktualisieren ist so eine Sache, vor allem bei "selbstlernenden" Kombinationsfeldern, die auf Abfragen a'la "SELECT DISTINCT Land FROM ..." beruhen. Während der Verarztung des NotInList-Ereignisses kann das nicht erfolgen, da der Datensatz selbst ja noch gar nicht gespeichert ist. Ein "Schlag mich tot"-Speichern des Datensatzes beißt sich evtl. mit unvollständigen Eingaben. Eine ein wenig umständliche Lösung per Code kann so aussehen:
Dim iComboFieldEdited As Integer    ' Im Deklarationsteil


Sub Land_AfterUpdate ()

    iComboFieldEdited = True

End Sub


Sub Form_Current ()

    If iComboFieldEdited = True Then
       Me!Land.Requery
       Me!Genre.Requery
       Me!Bewertung.Requery
    End If

End Sub
* * * * Leider treten bei mehreren Klappfeldern unsinnige Aufklappungen folgender Felder an der Position des gerade verarzteten auf, nachdem NotInList2 im NotInList-Ereignis ausgewertet wird. Ein verschissenes SendKeys "{F4}" OHNE WAIT-Parameter scheint Abhilfe zu schaffen. Kein Wunder, daß in den neueren Access-Versionen erweiterte Methoden für diese Drecks-Klappscheisse eingeführt wurden. Dieses dumme Verhalten der Klappfelder tritt sogar innerhalb eines Formulares uneinheitlich auf. * * * * Was Positives: Erfolgt in der Ereignisprozedur NotInList der Funktionsaufruf mit "leerem" ND-Parameter a'la Response = NichtinListe2(Me!Zahlart, ""), dann wird lediglich das meckerfreie Entfernen eines Wertes aus einem an sich "scharfen" Kombinationsfeld ermöglicht, wenn auch mit vereinzelten Aufklapp-Fehlern. Und noch besser:
    If NewData = "" Then
       Response = NichtinListe2(Me!Symbol_ID, "")
    End If
sorgt dafür, daß zwar Löschen der Einträge meckerfrei vonstatten geht, aber das Eintragen Listen-fremder Werte mit der Access-eigenen Standardmeldung bestraft werden. * * * * KOMBINATIONSFELDER / AFTERUPDATE: Was man beachten sollte: Bei erfolgten NotInList-Verarztungen scheint das Ereignis AfterUpdate für das betroffenen Steuerelement nicht mehr einzutreten ! In einem solchen Fall folgende Anweisung in der NotinList-Sub, wenn ein Wert zugewiesen wurde:
    If Response = DATA_ERRADDED Then Steuerelement_AfterUpdate
oder gleich grundsätzlich in Steuerelement_AfterUpdate schicken, wenn NichtInListe ausgelöst wurde. Klappt aber auch nicht immer. Bei Kombinationsfeldern scheint es vorzukommen, daß die Ereignisse BeforeUpdate und AfterUpdate nicht eintreten !!! * Erfolgreich verarztet werden konnte folgender Fall in bestatt.mdb: Das Feld Adressen.Art (Klappfeld) wird in AfterUpdate auf verschiedene Plausibilitäten geprüft. Ein einfaches Entfernen eines Eintrages scheint dieses Ereignis aber nicht mehr auszulösen, da bereits zuvor NotInList eingetreten ist. Es ist noch zu früh, die These aufzustellen, ob nach Eintreten von NotInList NIE AfterUpdate eintritt, aber der gerade beschriebene Fall kann durch Einfügen von
    If NewData = "" Then        ' beim Entfernen eines Eintrages
       Art_AfterUpdate          ' tritt das Ereignis AfterUpdate
    End If                      ' nicht ein, deswegen expl. Aufruf
in Art_NotInList erfolgreich bekämpft werden. * Hier muß hinzugefügt werden, daß das Ereignis AfterUpdate für Formulare u.a. eintritt, wenn ein Formulardatensatz geändert oder auch hinzugefügt wurde. Toll, aber nichts dergleichen passiert, wenn ein neuer Datensatz hinzugefügt wird; hier müssen offensichtlich redundant die entsprechenden Befehle noch mal abgesetzt werden. Zu einem anderen Fall gehört folgender Aufschrieb, dessen Notwendigkeit sich mir mittlerweile nicht erschließt: * * * * Ein weiterer Aspekt einer Kombinationsfeld-gestützten Pflege (hier ein weiches Beispiel) ist ein Kombinationsfeld, das in einem Unterformular steht, das 1:n-Daten eines Hauptformulares anzeigt. Die mittlerweile klassische NichtInListe-Verarztung schlägt bei dem Versuch, einen neuen Datensatz direkt in der Tabelle anzulegen, fehl, da z.B. ein Eingabe-Muß-Ident nicht leer gelassen werden darf. Witzigerweise besitzt das evtl. ausgeblendete Identfeld im Unterformular bereits automatisch als Standardwert den Wert des mit dem Hauptformular verknüpften Ident-Feldes, obwohl dies in den Eigenschaften (DefaultValue) nicht eingetragen wurde. Das macht Access wohl selber richtig. Ok. In diesem Fall werden die nötigen Verarztungen im NotinList-Ereignis selbst vorgenommen:
Sub Personen_NotInList (NewData As String, Response As Integer)

    If MsgBox("Möchten Sie '" + NewData + "' als neuen Begriff aufnehmen ?", 36, "Wert in Liste nicht vorhanden") = 6 Then
       Me!Personen = NewData
       Response = DATA_ERRCONTINUE
       iRequeryComboBox = True
       SendKeys "{TAB}", True
    End If

End Sub
Die Zuweisung 'Me!Personen = NewData' wird benötigt, damit Access die bestätigte Änderung akzeptiert. 'Response = DATA_ERRCONTINUE' sorgt dafür, das Access die Fresse hält, 'iRequeryComboBox = True' * * * * Im Deklarationsteil des Formulares wird ein Integer a'la 'FeldXEdit' vereinbart. In Form_BeforeUpdate wird folgendes, eigenartige Konstrukt eingefügt:
    If Me!Art = Me!Art.OldValue = True Then
       ' nix
    Else
       ArtEdit = True
    End If
Nur bei dieser Schreibweise können eventuelle NULL-Fälle ( bei Neueingabe ) und andere Scheiße abgefangen werden. Der Ausdruck sollte sich nach Regeln der formalen Aussagelogik zwar auch direkt formulieren lassen, aber Access 2.0 - Basic erkennt das dann halt eben nicht ! In Form_Current sorgt
    If ArtEdit = True Then
       ArtEdit = False
       Me!Art.Requery
    End If
dafür, daß alles zurückgesetzt wird und das Kombinationsfeld neu abgefragt wird. Auf dem Weg dorthin kommt einem selbstverständlich das Ereignis NotInList in die Quere, aber dafür gibts ebenso selbstverständlich unter diesem Stichwort einen Lösungsvorschlag.


Kombinationsfelder, Tasten   Quelle: dmt   Datum: 05.2006   nach oben

Gesetzt der Fall, man möchte für ein Kombinationsfeld feststellen, ob die Taste <Return> gedrückt wurde oder nicht. Normalerweise kann dies im Ereignis Key_Press mit 'If KeyAscii=13' abgecheckt werden. Im Formular WV_Artikel in KORO.MDB geht das aber nicht! Erst wenn auch das Ereignis KeyDown abgecheckt wird, kann die Betätigung dieser Taste festgestellt werden.

Letztenendes konnte auch das halbwegs elegant gelöst werden, indem eine Formular-Prozedur-übergreifende Variable im Ereignis AfterUpdate gesetzt wird, die im darauffolgenden Ereignis BeforeExit überprüft und bei Bedarf zurückgesetzt wird. So kann mit wenigen Zeilen und nur 2 Ereignissen, die sich auch nicht in die Quere kommen, erreicht werden, daß mit Return solange neue Werte ohne Focus-Sprung eingegeben werden können, bis keine weitere Neueingabe vorgenommen wurde und der Focus kommentarlos auf das nächste Feld gesetzt wird.

Auch die bewährte Tastatur-Routine If KeyCode = 32 And Shift = CTRL_MASK Then als Tastaturäquivalent zu einem Doppelklick ist nicht immer 100 %-ig zuverlässig. Bei einem Memofeld hinterläßt <Strg+Leer> Editierspuren, die erst mit einer formular-globalen Variablen, die in KeyPress ausgelesen wird, wieder bekämpft werden können.

Bemerkungen_KeyDown (KeyCode As Integer, Shift As Integer)

    If KeyCode = 32 And Shift = CTRL_MASK Then
       iCtrlBlank = True
       Bemerkungen_DblClick (0)
    End If

End Sub


Sub Bemerkungen_KeyPress (KeyAscii As Integer)

    If iCtrlBlank = True Then
       iCtrlBlank = False
       KeyAscii = 0
    End If

End Sub

Da KeyDown vor KeyPress eintritt (die Reihenfolge der Ereignisse für ein tastaturbedientes Steuerelement ist KeyDown, KeyPress, Update und KeyUp), kann hier der Flag gesetzt und die Sonderaktion ausgeführt werden. Im direkt danach eintretenden Ereignis KeyPress wird der Flag abgefragt, zurückgesetzt und die die Aktion auslösende Tastatureingabe mit KeyAscii=0 aus diesem unserem Softwareuniversum hinausgefegt.


Kombinationsfelder, Tücken   Quelle: dmt   Datum: 03.2004   nach oben

KOMBINATIONSFELDER / NOTINLIST / KOMISCHES VERHALTEN:

Eine weitere, sehr schnuckelige Alternative zum Thema NotinList ergab sich bei Phase1 STG- SEL Alcatel darin, in Unterformularen 1:n-Datensätze stammdatenartig in einem Klappfeld anzeigen zu lassen. Da das Unterformular die notwendige Steuerung der Identnummern selbst bewerkstelligt, brauchte und konnte eine bisherige NotinList-Lösung nicht in Frage kommen.

Leider (wie immer) wurde das schöne Bild von einer bitteren Pille getrübt, denn nachdem datentechnisch eigentlich alles geklappt hat, baut Access darstellungsmäßigen Mist (nicht zum ersten Mal). Der Fokus steht im nächsten Feld, es werden aber zwei Klappfelder mit Klapp-Schaltfläche angezeigt, wovon sich das zweite auch noch mit aufgeklappter Liste präsentiert.

Egal, was ich im anschließenden Form_Current alles anstelle. Die Liste wurde immer wieder anschließend aufgeklappt. Aber irgendwann in einer ruhigen Minute will man es eben wissen

Im Ereignis NotinList wird folgender Code ausgeführt:

    Beep

    If MsgBox("Möchten Sie '" + NewData + "' als neuen Begriff aufnehmen ?", 36, "Wert in Liste nicht vorhanden") = 6 Then
       DoCmd Hourglass True
       Me!Kuerzel = NewData
       Response = DATA_ERRCONTINUE
       iRequeryComboBox = True
       SendKeys "{TAB}", True
       DoCmd Hourglass False
    End If

Relevant sind die Anweisungen Me!Kuerzel = NewData und Response = DATA_ERRCONTINUE, die dem Klappfeld mitteilen, den neuen Wert anzunehmen und erstmal weiterzumachen, als ob alles in Ordnung wäre.

Ein DATA_ERRADDED scheint an dieser Stelle angebrachter zu sein, zumal die Dokumentation verspricht, den neuen Wert der Liste auch wirklich hinzuzufügen. Toll, zumal das sogar passiert, aber dummerweise haben die meisten, wirklich geilen Klappfeld-Listen auch was mit echten Daten zu tun und da gehts halt schon wieder los. Access weigert sich in dem Unterformular zu einem neuen Datensatz zu gehen und fängt das Spinnen an. Deswegen also brav mit einem ignoranten DATA_ERRCONTINUE und weiter gehts, denn das beste kommt erst noch:

Nachdem ein formularglobales Flag und der Fokus mit einem peinlichen SendKeys auf das nächstes Feld gesetzt wurde (wir imitieren hier das eigentlich sinnvolle Verhalten von Access, bei einer mit

bestätigten Eingabe zum nächsten Feld zu springen), geht es dann mit Form_Current weiter:

    If iRequeryComboBox = True Then
       Me!Kuerzel.Requery
       iRequeryComboBox = False
       Me.TimerInterval = 300
    End If

Das Requery sorgt dafür, daß das datenzeigende Klappfeld auch wirklich Kenntnis von dem neu eingefügten Wert besitzt. Nachdem Rücksetzen des Flags, das ja nur den 'Eingabe hat stattgefunden'-Fall anzeigt, wird der defaultmäßig auf 0 eingestellte TimerInterval des Formulares auf ca. 1/3 Sekunde gesetzt. Das ist in dem konkreten Fall die Zeit, innerhalb der Access meinte, nach Abschluß aller zur Verfügung stehenden Ereignisse die Drecks-Liste aufklappen zu müssen, die aber jetzt vom Formulartimer abgewartet wird:

    DoCmd RunMacro "Menübefehle.Anzeige_aktualisieren"

    Me.TimerInterval = 0

Das mit dem Makro haut hin, der Timer hält danach auch die Fresse, und, von einem zaghaften Aufflackern der Liste, dem selbst mit abenteuerlichen 'Application SetWarnings'-Anweisungen nicht beizukommen war, mal abgesehen, tut das zu meiner Zufriedenheit. Der Laie freut sich, der Fachmann wundert sich.

Allerdings muß an dieser Stelle hinzugefügt werden, daß dem Spuk, als das Problem auch ein anderes Mal auftrat, mit dem Entfernen des true-Zusatzes der SendKeys-Anweisung ein Ende bereitet werden konnte.

Dafür tat's dann ein paar Wochen später wieder nicht mehr. Egal !


Kombinationsfelder, weiche   Quelle: dmt   Datum: 05.2006   nach oben

KOMBINATIONSFELDER / WEICHE STAMMDATENunterstützung:

Die weiche Pflege wird eigentlich durch NichtInListe2 bereitgestellt; das folgende ist also noch weicher !

Mal was Positives:

Folgende Routine ermöglicht einem Kombinationsfeld, das auch freien Text unabhängig von den hinterlegten Listeneinträgen akzeptiert, mit der Tastenkombination <Strg+Alt+Einfg> gemachte Eingaben in einer Stammdaten-Tabelle zu speichern:

    Dim CR As String, iStrg_Alt As Integer

    On Error GoTo exit_Bezeichnung_KeyDown

    ' **** Bei <Strg+Einfg> in Stammdaten aufnehmen ****

    iStrg_Alt = (Shift And CTRL_MASK) > 0 And (Shift And ALT_MASK) > 0

    If KeyCode = 45 And iStrg_Alt Then

       Beep
       KeyCode = 0                                ' Damit  nicht den Überschreibe-Modus aktiviert
       CR = Chr$(13) & Chr$(10)

       SendKeys "{TAB}+{TAB}", True   ' Evaluierung der Eingabe und Focus wieder zurück

       If MsgBox("Möchten Sie " & CR & CR & "'" & Me!Bezeichnung & "'" & CR & CR & "in die Liste der Positionsbezeichnungen aufnehmen ?", 33, "Bezeichnung speichern") = 1 Then
          Anweisung$ = "INSERT INTO Auftragspositionen_Bezeichnung (Bezeichnung) VALUES ('" + Me!Bezeichnung + "')"
          DoCmd RunSQL Anweisung$
          Me!Bezeichnung.Requery
       End If

    End If

exit_Bezeichnung_KeyDown:

    Exit Sub


Kontextmenüs   Quelle: dmt   Datum: 03.2004   nach oben

KONTEXTMENÜS a'la '95 lassen sich ansatzweise auch in Access unter Win 3.x realisieren.

Am Windows selbst liegts nicht, aber da Access seine Menüs an sich selbst bindet und die Access-Dokumente selbst nicht über Menüs verfügen, kann die bei den VB-Samples gezeigte Technik nicht vernünftig angewendet werden.


leere defaults   Quelle: dmt   Datum: 03.2004   nach oben

LEERE DEFAULT-WERTE:

Das Eingabeformat soll den Anwender unterstützen. Literale Zeichen können wahlweise mit den eingegebenen zusammen gespeichert werden oder auch nicht. Wenn aber wie in den Bosch-Fällen Meeuwisse und Mertlik eine Bedienerführung a'la carte gewünscht wird (ergrauende Captions bei nicht ausgefüllten Felder etc.) ist das alleinige Zuweisen toller Eingabeformate erst der Anfang:

Wird für ein Feld ein Defaultwert vereinbart, erscheint bei einem neuen Datensatz eben dieser String, eine Grau-Routine schwärzt den dazugehörenden Caption ein (da nicht NULL) und es sieht dann so aus, als ob da bereits etwas tolles passiert wäre. Wie kann ich aber einen Defaultwert vorschlagen lassen, ohne bei einem leeren Datensatz für das entsprechende Feld den Defaultwert anzeigen zu lassen ? Tja, auch so etwas geht, und zwar indem man zum einen den Defaultwert wegläßt und zum anderen nach zugewiesenem Eingabeformat für das Ereignis 'BeimHingehen' eine Routine a'la

if ineu then
   SendKeys "{F2}"
end if

einfügt.

Navigiert man durch einen bereits vorhandenen und vollständig ausgefüllten Datensatz, verhält sich alles ganz Access-normal. Bei der Neueingabe erscheinen einem Default gleich die Literale des Eingabeformates und es rutscht der Cursor im entsprechenden Feld an die erste Stelle, an der in dem Eingabeformat ein einzugebendes Zeichen vorgesehen wurde.
Übergeht man an dieser Stelle die Eingabe, wird das Feld 'genullt'. Yes !


Listenfelder   Quelle: dmt   Datum: 12.2006   nach oben

LISTENFELDER:

Als enge Verwandte von Kombinationsfeldern können auch diese Wertelisten oder Tabellen-Daten enthalten.

In Access97 wurde der OOP-Ansatz erweitert und neue Eigenschaften und Methoden hinzugefügt, aber einfachste Wünsche im Umgang mit aufgelisteten Daten scheinen doch wieder nur zu Fuß in Erfüllung zu gehen.

Speziell bei Listen, die "nur" mit Werten gefüllt sind, wäre ein komfortablerer Umgang wünschenswert.

Ein paar Beispiele sind in einem eigenen Modul s.a. "Listenfelder" enthalten.
Der Programmierstil entspricht eher Access 2.0 (wie auch diese Ausführungen), weil der Listenfeld-OOP-Ansatz einfach zu schlampig ist. So kann man in Access97 objektorientiert die evtl. mehrfach selektierten Elemente eines Listenfeldes per "for each ..." durchlaufen, aber die Sammlung der Einträge an sich darf man dann wieder wie früher per simplen "For i = 0 To Me!Dateitypen.ListCount - 1" abackern.

Deshalb werden in der Regel per ListIndex alle Elemente durchlaufen und wunschgemäß geprüft und behandelt.

Positiv ist die Möglichkeit, per Column-Eigenschaft an beliebige Werte mehrspaltiger Listenfelder heran zu kommen:

    For i = 0 To Me!Dateitypen.ListCount - 1
        iSummeAktiv = iSummeAktiv + Me!Dateitypen.Column(1, i)
    Next i

* * * *

Eine Besonderheit der Eigenschaft ListIndex besteht im Zusammenhang mit der Eigenschaft BoundColumn (GebundeneSpalte).
Wenn man die gebundene Spalte auf 0 setzt, erhält das Kombinationsfeld (bzw. Listenfeld) keinen der sichtbaren Werte, sondern es wird Werte-spezifisch mit den Listindex-Werten gearbeitet. Wenn man unbedingt darauf angewiesen ist, einen bestimmten Eintrag numerisch anzusteuern, kann das Steuerelement-Eigenschaft GebundeneSpalte auf 0 gesetzt und mit Me!Liste = i ein numerisches Ansteuern bestimmter Listeneinträge erreicht werden.

* * * *

Zusätzlich gibt es ein paar spezielle LISTENFELD-Funktionen, die, wenn Access sowas wie dynamisch übergeb-, zuweis- und ausführbaren Code beherrschen würde (a'la php), auch noch ausgelagert werden könnten.


Löschen von Listenfeld-Wertliste-Elementen, indem schlicht die Liste duchlaufen wird und nicht markierte Elemente als String mit ";" zusammengefasst werden, um eine verkürzte Liste zu bilden.

Private Function DeleteDateitypen(bolSpeichern As Boolean)

    On Error GoTo err_DeleteDateitypen

    Dim s As String, i As Integer

    If HasListfieldSelections(Me!Dateitypen) = False Then Exit Function

    For i = 0 To Me!Dateitypen.ListCount - 1
        If Me!Dateitypen.Selected(i) = True Then
           If bolSpeichern = True Then
              ManageStopWords Me!Dateitypen.ItemData(i), True
           End If
        Else
           s = s & Me!Dateitypen.ItemData(i) & ";"
        End If
    Next i

    Me!Dateitypen.RowSource = s

    Exit Function


err_DeleteDateitypen:

    Fehler "DeleteDateitypen"
    Exit Function

End Function

* * * *

Das Programm-gesteuerte AUFFÜLLEN von LISTENFELDERN:

Macht einen komplexen Eindruck, geht aber sehr fix über die Bühne.
Ausnahmsweise mal ein Kompliment an die Programmierer von der Turnschuh-Fraktion.

Function Listenfeld (Feld As Control, ID As Long, Zeile As Long, Spalte As Long, Code As Integer)

 Select Case Code

        Case 0                                    ' Initialisieren.
             Check_Dateien                        ' fülle Array
             Listenfeld = True
        Case 1                                    ' Öffnen.
             Listenfeld = Timer                   ' Eindeutige ID für Steuerelement
        Case 3                                    ' Zeilenanzahl.
             Listenfeld = UBound(DF_Dateien)
        Case 4                                    ' Spaltenanzahl.
             Listenfeld = 1
        Case 5                                    ' Spaltenbreite.
             Listenfeld = -1                      ' Standardbreite verwenden.
        Case 6                                    ' Mit Daten füllen.
             Listenfeld = DF_Dateien(Zeile)
        Case 7                                    ' Abschluß

 End Select

End Function


Listenfelder füllen   Quelle: dmt   Datum: 03.2004   nach oben

MAXIMALE LÄNGE VON ZEICHEN EINER WERTELISTE IN LISTENFELDERN UND KOMBINATIONSFELDERN:

Im Access-Eigenschaften-Dialog wie auch per Makro-"Programmierung" können nur 255 Zeichen zugewiesen werden.

Erst sehr spät mußte ich entdecken, daß die 32kB-Begrenzung für VisualBasic-Strings nicht auch die Grenze für VB-Zuweisungen für ein Listen-Kombinationsfeld sind. Dort ist dann trotz VB bei 2048 Schluß (MS-ACCESS 2.0).

Das oben beschriebene Verfahren hilft auch in diesem Fall.

Ein dynamisches Abfangen im Code kann gegebenfalls so aussehen:

    If Len(s) > 2048 Then
       WordsToArray s, ";", True
       Me!Wortliste.RowSourceType = "FuelleWortliste"
    Else
       Me!Wortliste.Rowsource = s
    End If

siehe auch WordsToArray

* * * *

KOMBINATIONSFELDER / KOMBINATIONSFELDER FÜLLEN:

Da gibt es zum einen die sehr komplizierte, aber schnelle Callback-Geschichte (s.o.) und zum anderen das einfache Verfahren, per Funktion einen String generieren zu lassen, der die Werteliste darstellt.

Die Callback-Lösung ist sehr flott.
Doku in Access-Hilfe unter Stichwort 'Kombinationsfeld: Ausfüllen' -> Beispiele.
Beim Benutzen beachten: In Herkunftstyp NUR Funktionsnamen angeben, ohne '=' und '( )'. Datenherkunft muß leer bleiben.

Das angegebene Stichwort führt zur rein deutschen Hilfeseite 'Füllen eines Listen- oder Kombinationsfeldes mit einer Access Basic-Funktion', in der das Hilfe-Topic 'Erstellen Sie die Funktion' eine endlich mal ausführliche Erklärung der ansonsten im Dunkeln liegenden Vorgänge innerhalb von Access liefert.

Dieses Beispiel setzt ein im globalen Kontext (Formular oder Modul) deklariertes Array voraus, da zumindest Access 2.0 leider keine Übergabe von Array als Parameter an Funktionen und Subs erlaubt:

z.B. im Deklarationsteil eines Formulares:

Dim arrWortliste() As String

und dann:

Function FuelleWortliste (Feld As Control, ID As Long, Zeile As Long, Spalte As Long, Code As Integer)

 Select Case Code

        Case 0                                          ' Initialisieren.
             FuelleWortliste = True
        Case 1                                          ' Öffnen.
             FuelleWortliste = Timer                    ' Eindeutige ID für Steuerelement
        Case 3                                          ' Zeilenanzahl.
             FuelleWortliste = UBound(arrWortliste)
        Case 4                                          ' Spaltenanzahl.
             FuelleWortliste = 1
        Case 5                                          ' Spaltenbreite.
             FuelleWortliste = -1                       ' Standardbreite verwenden.
        Case 6                                          ' Mit Daten füllen.
             FuelleWortliste = arrWortliste(Zeile)
        Case 7                                          ' Abschluß

 End Select

End Function

Diese Funktion wird innerhalb von Access 8 mal aufgerufen, um verschiedene Informationen zur zu füllenden Liste abzurufen.

Interessant ist auch das in der Funktion selbst nicht erkennbare Handling der verwendeten Parameter, daß irgendwo außerhalb in den Untiefen von Access erfolgt.


Listenfelder und ihre Tücken   Quelle: dmt   Datum: 01.2008   nach oben

LISTENFELDER:

haben unter Umständen mehr als nur ihre Tücken.

Bei SEL/STG steuert in einem Popup-Formular ein Unterformular die Datenherkunft eines Listenfeldes, und das über ein Netzwerk. Manche Datenmengen, deren Anzahl z.B. über 30 hinausgingen, scheinen nicht im Listenfeld anzukommen. Allerhärteste Contraprogrammierung konnte einiges retten (was ist eigentlich mit meinen Nerven ?), aber das Problem konnte nur weiter an den Rand (des Unterganges) verschoben werden.

Sub FuckListBox (F As Form)

    ' **** Listenfelder, oder wie ich sie hasse, die Microärsche ****

    Dim s As String

    DoEvents

    Application.Echo False

    DoCmd SelectObject A_FORM, F.Name

    F!Institutionen.SetFocus

    F!Institutionen.Requery

    DoEvents

    If Not IsNull(F!UF.Form!Adresse1) Then
       s = Left$(F!UF.Form!Adresse1, 1)
       SendKeys "^{Ende}", True
       SendKeys s, True
    End If

    Application.Echo True

    DoEvents

End Sub

Hier scheint DoEvents zum ersten Mal etwas zu bewirken. Eigentlich wird das Listenfeld nur (krampfhaft) fokussiert, per Sendkeys ans Listenende gesprungen (dann klappts auch mit den Daten) und dann per variablem Sendkey an den ersten, alphabetisch entsprechenden Datensatz der Liste. Das scheint ganz gut zu klappen, trotzdem gibt es danach vereinzelt weitere Probleme: sei es, daß die Bildschirmanzeige der Listbox quasi zusammenbricht (GUI oder Gülle ?), oder daß sich das Parent-Formular mit der idiotischen Fehlermeldung hervortut, daß irgendwelche Datensätze während der Gültigkeitsüberprüfung nicht gespeichert werden können.

Die Bildschirmkacke kann witzigerweise mit

<Strg+Pos1>
oder
<Strg+Ende>
wieder gerade-gebogen werden, die Fehlermeldung wird in Form_Error abgestellt.

Was lernen wir daraus ?

Wenn es brenzlig wird, Finger WEG von Kombinations- und Listenfeldern. Die sind nämlich scheisse programmiert.

* * * *

In den auf 2.0 folgenden Access-Versionen hat sich dann auch einiges getan, was Eigenschaften und vor allem Methoden betrifft.

Nur der Vollständigkeit halber noch ein Beitrag zum Thema "Durchlaufen der Einträge eines Listenfeldes" oder auch "Listenfeld-Daten prüfen" bzw. "Werte in Listenfeldern finden" unter Access-2.0:

Wenn die Einträge einer Liste nur durchlaufen werden müssen, geht das mit dem einfachen Abfragen der Itemdata(listindex)-Funktion:

    For i = 0 To C.ListCount - 1
        Msgbox C.ItemData(i)
    Next i

Fieseliger wird es, wenn das Listenfeld mehrspaltige Daten enthält, dann müssen in Access 2.0 die Listeneinträge allen Ernstes nicht nur durchlaufen, sondern der Wert dieses Felder einzeln gesetzt werden, um dann für den aktuellen Wert die Werte der zusätzlichen Spalten abfragen zu können.

    For i = 0 To Me!Liste.ListCount - 1
        Me!Liste = i
        If Me!Liste.Column(0) = Me!von And Me!Liste.Column(1) = Me!zu Then
           Exit For
        End If
    Next i

Hier wird allen Ernstes der Wert eines NICHT-GEBUNDENES Listenfeld mit einem Schleifenzähler gleichgesetzt (Access 2.0 bildet bei ungebundenen Listenfeldern solche Index-Werte, übrigens von 0 an zählend).
Somit wird jede Zeile der Reihe nach markiert, bis z.B. eine gewünschte Bedingung eingetreten und dann letztendlich auch die gewünschte Zeile markiert ist.


Maus   Quelle: dmt   Datum: 10.2004   nach oben

MAUS-EREIGNISSE:

Ein Rechtsklick kann in den Steuerelement-Ereignissen MouseDown und MouseUp abgefangen und behandelt werden.

Wird jedoch z.B. in einem Listenfeld ein anderer Wert als der gerade aktive rechts-geklickt, hat die Wertänderung, die für den Anwender sichtbar ist, in MouseDown noch nicht statt gefunden.

MouseUp kommt damit schon besser zurecht.

Allerdings gibt es Probleme, wenn der Rechtsklick (zu) schnell erfolgt (kein Witz !). Wenn z.B. mit der rechten Maustaste ein Listenfeld, das noch keinen Wert besitzt, oder einfach nur im normalen Tempo ein anderer Wert angeklickt wurde, werden für Listenfeld-Einträge die Werte NULL bzw. der zuletzt markierte Wert zurückgegeben.

Wer langsam rechtsklickt, bekommt bei MouseUp die richtigen Werte geliefert.
Versuche mit DoEvents oder fingierten Dummy-Focus-Wechseln waren alle erfolglos.

* * * *

MAUS bewegen:

Deklarationen:

Type POINTAPI                           ' Datenstruktur für eine
     x As Integer                       ' API-Koordinate
     Y As Integer                       ' -> Set/Get-CursorPos
End Type

Dim lpPoint As POINTAPI

Declare Sub GetCursorPos Lib "User" (lpPoint As POINTAPI)
Declare Sub SetCursorPos Lib "User" (ByVal x As Integer, ByVal Y As Integer)

Aufrufe:

GetCursorPos lpPoint
SetCursor x, y


Maus, Probleme   Quelle: dmt   Datum: 03.2004   nach oben

So kann bei Eintreten des Ereignisses 'MouseMove' die Statuszeile nicht gesetzt werden. Eine sich ständig selbst aktualisierende Info-Zeile hätte sich so toll realisieren lassen. Aber der Fenstertitel, bzw. Steuerelemente lassen sich in diesem Fall sehr wohl einstellen. Verstehe das wer kann ...

Ein geiles Info-System für Null-Checker könnte trotz dieser Bugs mit Hilfe eines Pop-Up-Fensters a´la 'Information', dessen Fenster- sowie Steuerelementgröße verändert werden können, realisiert werden. Bereits erledigt -> hp_vhp.mdb

Denkbar wäre mit Hilfe eines kleinen PopUp-Info-Formulares eine Mauspositions-abhängige Infotextausgabe, evtl. sogar mit positionsgerechtem Ein- und Ausblenden.

Aber wer auf so etwas besteht, sollte sich doch lieber mit einem Access >= 7 herumschlagen, in dem solche Dinge bereits implementiert sind.

* * * *

Ebenso wollte sich das Verlassen eines Hot-Spots mittels des Ereignisses 'MouseMove' nicht abfangen lassen. Über das Steuerelement selbst kann es nicht gehen; als einziges wollte sich der Detailbereich dazu herablassen, ein eigenes MouseMove auszulösen, auch wenn innerhalb des Detailbereiches mehrere Steuerelemente vom MouseMove-Ereignis Gebrauch machen.


mehrseitige   Quelle: dmt   Datum: 03.2004   nach oben

MEHRSEITIGE FORMULARE:

Die Assistenten zeigen, wie es gemacht wird. Einen Themenkomplex, der nach Schritten unterschieden viel Interaktionsfläche benötigt, kann so mit einem einzigen Formular bewältigt werden, ohne im Datenbankfenster eine Objektflut zu verursachen.

Beispiel siehe dmt.mdb Formular Tools.

Mit Blick auf eine übersichtliche Bedienerführung sind die Reiterkarten (ab Access97) eine tolle Sache. Klar, daß das aber auch wieder fehlerhaft implementiert wurde und bei regem Gebracu dann wieder Ärger macht.

* * * *

KARTEIKARTEN-REITER:

Spätestens seit Windows 95 Standard. Wie es im Ansatz auch in Access 2 gehen könnte, zeigt dmt.mdb Formular Tools. Bis allerdings verschiedene Interaktionsbereiche einander überblenden (in Kombination mit den wechselnden Reitern), muß noch manche Programmiererträne fließen.

Eventuell geht was mit mehrseitigen Formularen und Verschieben der Reiter-Captions.

* * * *

REGISTERKARTEN:

Endlich mal ein sinnvolles Feature in einer neuen Version (ab Access 97).

Das kleine Beispiel zeigt eine einfache Navigation zwischen den Registerkarten-Seiten eines Formulares:

    If Me!Register.Value = Me!Register.Pages.Count - 1 Then     ' letzte Seite erreicht
       Me.Register.Pages(0).SetFocus
    Else
       Me.Register.Pages(Me!Register.Value + 1).SetFocus
    End If


Popup   Quelle: dmt   Datum: 03.2004   nach oben

POP-UP - Formulare sind ebenfalls geeignet, einen zur Verzweiflung zu bringen, wenn man versucht, mit Symbolleisten oder eigenen Prozeduren, die bei Form_Activate ausgeführt werden sollen, zu arbeiten.

Die Ereignisse Form_(De)Activate treten nicht ein, wenn beim Anzeigen / Schließen von Formularen Pop-Up-Formulare im Spiel sind.

Lösung:
Wenn eines der beteiligten Formulare unbedingt ein Pop-Up-Formular sein muß, dann müssen für die nicht ausgeführten Form_(De)Activate-Ereignisse in dem Pop-Up- bzw. den anderen Formularen entsprechende Routinen in den Ereignissen Form_Open bzw. Form_Close des Pop-Up-Formulares geschrieben werden. Das ist dann auch in soweit hinreichend, da ein derartiges Formular ja nicht defokussiert, sondern nur geschlossen werden kann.


Probleme   Quelle: dmt   Datum: 03.2004   nach oben

Ein sehr obskures Phänomen tritt im HP_VHP-Manager auf:

Es gibt ein Formular 'Ventile_allgemein', das bei einer Suche nach Ventildatensätzen, bei der mehrere Daten gefunden wurden, diese auflistet. Dieses Formular erlaubt keine Bearbeitung der angezeigten Daten, da diese nur durch gruppenspezifische Einzelformulare erfolgen darf. Wird nun aus dem Listenformular ein anderes, Ventildatensätze betreffendes Formular aufgerufen, wird in der Statuszeile, bevor auch nur in irgendeinem Ereignis etwas abgefangen werden kann, die Meldungen "Diese Datensatzgruppe kann nicht aktualisiert werden", "Formular wird regeneriert" und dann wieder "Diese Datensatzgruppe kann nicht aktualisiert werden" angezeigt. Auch so harte Maßnahmen wie 'Application.Echo False' greifen in keinster Weise.

Auch hierfür gibt's eine Lösung / Erklärung:

Die oben beschriebenen Status-Meldungen ließen sich in der Hilfe-Dokumentation nicht finden. Erst als die Formular-Einstellungen 'Aktualisierung zulassen:
Keine Tabellen' und 'Datensätze sperren: Alle Datensätze' entschärft wurden, trat der Fehler nicht mehr auf.

Das Gebot, die angezeigten Datensätze nicht bearbeiten zu können, wird bereits durch die Einstellungen 'Standardbearbeitung: Nur Lesen' und 'Bearbeiten zulassen: Nicht verfügbar' voll und ganz befolgt.

Wahrscheinlich versucht die Datenbank-Engine beim Umschalten auf ein anderes Formular, das sich auch auf die Tabelle 'Ventile' bezieht, irgendeinen systeminternen Check-Zugriff durchzuführen und stolpert dabei über die entsprechenden Sperreinstellungen des Listen-Formulars.

* * * *

NERVIG:

Das code-gesteuerte Speichern eines Formular-Datensatzes per DoCmd mißlingt zuweilen schon mal:

       DoCmd.SelectObject acForm, Me.Name
       RunCommand acCmdSaveRecord

Ein vorangestelltes SelectObject hat da geholfen.

* * * *

Heute schon gekotzt ?

Wenn nicht, dann sollte man mal versuchen, in einem Formular mit der Einstellung 'Aktualisierung zulassen = Keine Tabellen' Daten anzuzeigen. Obwohl die Tabelle über einen Primärindex verfügt, weigert sich dieses Drecks-Formular, die Daten in der korrekten Reihenfolge anzuzeigen. Selbst wenn man dem Formular als Datenherkunft eine Abfrage zuweist, die die Daten selbst richtig anzeigt, tritt dieser Fehler auf. Erst wenn die Abfrage unnötigerweise eine explizite "ORDER BY
PrimärIndex-Feldname" enthält, bequemt sich das Formular, die Daten in der richtigen Reihenfolge anzuzeigen. Das trat auf, als ein Formular, das ursprünglich mit restriktiven Eigenschaften als Dialog-Formular ausgelegt worden war, auf ein Daten-anzeigendes umgebaut wurde. Nach Einstellen von 'Aktualisierung zulassen = Zugrundeliegende Tabellen' war der Spuk vorbei.

* * * *

Ein sehr (un-)witziges Phänomen trat auf, als der Fokus auf einer CheckBox stand und dann

gedrückt wurde. Obwohl das Formular auf's penibelste exakt ausgerichtet war, trat eine scheinbare Formularverlängerung um ca. 50 % auf, als ob das Formular sich plötzlich in ein Endlosformular verwandelt hätte. Dies schien daran gebunden zu sein, daß das Formular CheckBoxen sowie ein Formularansicht-Unterformular enthielt, in dem sich ebenfalls CheckBoxen befanden. Bevor für jede CheckBox ein abgefangen wurde, konnte herausgefunden werden, daß durch die Größenänderungs-Zuweisungen des Unterformulares Höhenänderungen des Formular-Detailbereiches eintraten, was dann zu komischem -Verhalten führte. Dies sieht erst mal nach einem Programmierfehler aus, aber ein Code-mäßiges Vorziehen der Top-Zuweisung führt unter Umständen zu Meldungen a'la "Das Steuerelement kann an dieser Stelle nicht plaziert werden.". Also dann lieber doch zuerst die Height einstellen, eine eventuelle Detailbereichsvergrößerung in Kauf nehmen und am Ende des Codes eine finale, gültige Section(0).Height=x -Zuweisung vornehmen.

Grieche ich mit so was dieser Software in den Arsch oder trete ich ihr eher in denselben ?

* * * *

Ganz und gar nicht komisch finde ich es, wenn beim Bedrucken des teuren Etiketten-DIN A4-Bogens immer neue Variationen des Software-Versagens auftreten. Ein eigenartiger Effekt bei den Stackelberg-Etiketten war, daß beim zweiten Druck (Testdruck korrekt) auf den Etikettenbogen das WMF-OLE-Objekt zur Hälfte in ein grafisches Nirwana verschoben, aber natürlich richtig auf dem Bildschirm dargestellt wurde. Nach mehrmaligen Schließen und Wiederöffnen wurde das OLE-Bild auch in der Seitenansicht dann völlig unterschlagen. Liegt hier ein grundsätzliches Problem mit Berichten oder mit WMF-Objekten in Berichten oder mit Windows oder was vor ? Auf jeden Fall konnte der Ausdruck dann doch noch vonstatten gehen, nachdem die Datenbank geschlossen, wieder geöffnet und der Bericht nach einmaligen, korrekten Seitenansichts-Anzeigen dann auch direkt gedruckt wurde !


Screenshot   Quelle: dmt   Datum: 10.2004   nach oben

FORMULARE per SCREENSHOT als Grafik exportieren:

Mit

<Druck>
wird der gesamte Bildschirminhalt als Grafik in die Zwischenablage kopiert. Mit
<Alt+Druck>
gilt das für den Inhalt des aktuellen Programmes. Wird in diesem Programm gerade ein System-Modal-Fenster angezeigt, so wird in diesem Fall nur dieses Fenster kopiert. Um also in Access per bequemen Formular-Entwurf ein Logo zu erstellen, kann dieses als modal und gebunden angelegt werden und nach Öffnen mit
<Alt+Druck>
direkt in ein Grafikprogramm übernommen werden.


Sortierung   Quelle: dmt   Datum: 03.2004   nach oben

DATENSÄTZE IN DATENBLÄTTERN SORTIERT DARSTELLEN:

Wenn man durch sinnvolle Index-Vergabe erreicht, daß bei Öffnen einer Tabelle die Daten in einer gewünschten Reihenfolge dargestellt werden, so gilt das auch für ein Formular, dessen Datenherkunft auf diese Tabelle verweist. Gleiches gilt auch für Abfragen, die NICHT über einen WHERE-Teil verfügen. In diesem Fall wird durch Indizes naturgemäß vorgegebene Sortierung außer Kraft gesetzt und muß mit einem ORDER BY wieder erzwungen werden. Zumindest bei größeren Datenmengen kostet das Zeit und Nerven (Plattengeratter), weshalb ja die Mühe mit den Indizes aufgewendet wurde.


Steuerelemente aktualisieren   Quelle: dmt   Datum: 11.2004   nach oben

AKTUALISIEREN von Formularen bzw. Steuerelementen:

Die schnellste Methode zum Aktualisieren von Steuerelementen scheint

DoCmd Formular.Repaint

zu sein.

Im konkreten Fall wird innerhalb einer Schleife ein Fortschrittswert an eine selbstgeschriebene Fortschrittsroutine übergeben. Die Sub Meter_Update errechnet die Width des Meter-Rechteck-Objektes und den Wert des Textfeldes Text_Prozent ( Beispiel für einen selbergemachten FORTSCHRITTSZEIGER in dmttrial.mdb, Formular Trenne_Zeichenkette). Soweit, so gut. Dummerweise wird die Anzeige erst dann aktualisiert, wenn das Programm mit

<Strg+Pause>
unterbrochen wird. Die Access-Hilfe meint dazu nur lapidar, das Access manchmal den Formularinhalt nicht aktualisiert. Von den angebotenen Methoden / Aktionen hat sich DoCmd Formular.Repaint als geeignet herausgestellt.

Es konnte mittlerweile gezeigt werden, daß ein Fortschrittszeiger auch ohne Drecks-Maßnahmen tun kann: Die wohl mit Abstand allergeilste Lösung zu diesem Problem kann betrachten werden in dmttrial.mdb / Formular Status_Anzeige_Test.
Man beachte das ausgeklügelte Zusammenspiel zwischen einem normalem Daten- und einem Pop-Up-Anzeige-Formular !

Kurz-Doku:

Wichtig ist, daß das Mutterformular beim Öffnen auch das Status-Zeiger-Formular öffnet und die vollständige Länge des Grün-Zeigers erfaßt.

Für De-/Aktivieren des Mutterformulares wird das Zeiger-Formular wie eine Symbolleiste ein- und ausgeblendet. Leider tritt das Ereignis DeActivate für das Zeigerformular zweimal ein (weil Code in Form_Close und in Form_DeActivate steht?), so daß mittels eines Close-Flags ein zweimaliges Ausblenden des Statusformulares unterbleibt.
Der Grünzeiger wird bei 0-Werten ausgeblendet, da sonst ein minimaler Streifen zu sehen ist.

Das Status-Formular selbst hat es faustdick hinter den Ohren:

Da es als eigenes Access-Formular den Fokus vom zu bearbeitenden Formular wegnehmen könnte, oder auch per

<Alt+F4>
(es ist Pop-Up, weil es sich sonst nicht außerhalb des Mutterformulares plazieren lassen würde) getrennt geschlossen werden könnte, wird dort bei
<Alt+F4>
ein Flag gesetzt, der in im unweigerlich ausgelösten Form_Unload Cancel setzt. Ein Mausklick auf eines der sichtbaren Steuerelemente bringt eine Klartext-Meldung zum Vorschein(die auch per pb im Mutterformular erscheinen könnte) und setzt den Focus wieder auf das Mutterformular. Da für ein Pop-Up-Formular leider keine GotFocus- und Activate-Ereignisse eintreten (die bleiben in diesem Fall beim zuletzt aktiven Formular), ist es als einziger Schönheitsfehler möglich, per
<Strg+F6>
den Formularfokus der Reihe nach zu verschieben, bis er beim Statusanzeiger-Formular landet. Allerdings wird der der Anwender beim Betätigen einer x-beliebigen Taste mit dem Info konfrontiert und aus die Maus.

YES !

Und es geht noch geiler:

In dmttrial.mdb wurde das Erscheinungsbild des Statuszeigers an das der dezent vertieften Anzeigefelder für 'ÜB' usw. rechts unten in der Statusleiste angepaßt. Die komplette Geschichte kann über einen simplen Sub-Aufruf erfolgen, in dem ein Text übergeben und sogar der Darstellungsmodus (Grün auf rot oder Access 2.0-Balken) gewählt werden kann. Einziger Wermutstropfen: Bei schnellen Werte-Folgen, bei denen kein Fokus-Erhalt für ein Steuerelement eintritt, wird der Anzeiger nicht aktualisiert; hier hilft dann nur ein DoEvents oder ein Form.Repaint, was bei dem kleinen Zeiger aber zu einer etwas flackrigen Darstellung führt.

Klar, daß auch hier der eine oder andere Wermutstropfen überläuft:

Beim erfolgreichen Einsatz des Status-Anzeige-Formulares im Laser-Job-Manager (dort wird für jeden Auftragsdatensatz der Status ermittelt und angezeigt; ein Klicken auf den Fort-schrittsbalken bringt ein Auswertungsformular zum Vorschein, das dem Anwender Klartext-Informationen bietet) konnte ein Phänomen beobachtet werden, das schon beim Öffnen und Schließen modaler Submenü's a'la Stammdaten auftrat:

Die feldbezogenen Statuszeilen-Eigenschaften eines Formulares schienen verloren gegangen zu sein; Symbolleisten und Access-eigenen Meldungen wurden zwar noch angezeigt, aber die Felder informieren nur noch mit einem stereotypen 'Bereit'.

Bei den modalen Submenü's half ein

If ExistsForm("Haupt") Then
   Forms!Haupt.SetFocus
End If

, aber beim Öffnen des Status-Anzeige-Formulares in Form_Open des Server-Formulares konnte dieser Effekt nur mit einem abschließendem

Me.SetFocus

im Server-Formular beseitigt werden.

Diese schönen Beispiele sollen aber nicht zu der Annahme verleiten, daß Access grundsätzlich dazu in der Lage ist, Code-Befehle auch auszuführen.

* * * *

Wenn's hart auf hart kommt, kann REPAINT einen entnervten Programmierer schon mal davor bewahren, sich oder seiner Umwelt Schaden anzutun, vorausgesetzt natürlich, daß ihn rechtzeitig die Eingebung trifft, daß genau dieses REPAINT die Lösung seines Problemes ist.

Folgendes Beispiel:

In einem Formular soll unter gewissen Umständen der Text eines Beschriftungsfeldes ('Label') durch einen anderen ersetzt werden.

Erreicht wird dies durch einen Befehl a'la Labelname.Caption = "Neuer Text:" .

Jetzt kann es dummerweiser passieren, daß die Abarbeitung dieser Programmzeile absolut ohne jede Auswirkungen bleibt. Der Befehl wurde zwar ausgeführt, ein code-mäßiges Abfragen des Caption-Inhaltes ergibt auch den neuen, zugewiesenen Text, nur angezeigt wird natürlich der alte. Genau dann ist es höchste Zeit für ein verschissenes Form.Repaint.

Die Ursache liegt nach meiner unmaßgeblichen Meinung in einer Reihe von Problemen, die MS-ACCESS mit der Grafik-Engine von Windows 'GDI' (große dumme Idiotie) hat. Siehe auch das haarsträubende Problem mit dem Kopieren von Ventildatensätzen in hp_vhp.mdb (und auch das fehlerhafte Drucken von grauen Linien auf dem Ventildatenblatt). Nachdem das Formular des neuen Datensatzes Stück für Stück mit den Werten der Vorlage ausgefüllt wurde, hat Access doch glatt 10 % der Bildschirm-Daten unterschlagen. Erst nach dem Einblenden und Herum-Draggen diverser MessageBoxen wurden die von 'losgelassenen' MessageBoxen verdeckten Bildschirm-Bereiche korrekt dargestellt. Hier half selbst ein Repaint nichts, oder hätte ich etwa jedes Steuerelement einzeln repainten sollen ? Abhilfe konnte damals gefunden werden, indem die Übertragung im Symbolzustand erfolgte und das aktualisierte Formular danach einfach wiederhergestellt wurde.

Hierzu hat sich nach mehrjähriger Arbeit sogar eine beinahe akzeptable Alternativ-Lösung ergeben:

Eine selbstgeschriebene Routine Sub Redraw (F as Form) führt lediglich die beiden Anweisungen F.Visible=false und F.Visible=true aus, dann klappt's auch mit dem Fensterl'n.

Sub Redraw (F As Form)

    ' Ein Bildschirm-Refresh will um's Verrecken nicht ?

    ' Das woll'n wir doch mal sehen ...

    F.Visible = False
    F.Visible = True

End Sub

Im Zweifelsfall kann man das auch für alle sichtbaren Formulare durchführen:

Sub RedrawAllForms ()

    ' **** In Härtefällen Redraw aller sichtbaren Formulare ****

    Dim i As Integer

    For i = 0 To Forms.Count - 1

        If Forms(i).Visible = True Then
           Redraw Forms(i)
        End If

    Next i

End Sub

Obendrein könnte es ja passieren, daß ein Formular nicht nur nur bunte Smarties, sondern auch mal Daten anzeigt, die durch Programm-Routinen auch ab und zu aktualisiert werden müssen. So kann z.Bsp. in hp_vhp.mdb vom Formular KV_Lizenznehmer aus eine Routine zur Erstellung von Vollversions- und Update-Disketten gestartet werden, die letzten Endes auch den aktuellen Lizenznehmer-Datensatz aktualisieren muß. Prompt kommt es zur Fehlermeldung, daß der Datensatz durch 'eine andere Sitzung gesperrt' sei.

Dazu folgendes Beispiel aus Form KV_Diskettenerstellung 'Erstelle_Disketten':

Anmerkung: Die Anwendung wird immer exklusiv geöffnet !

    ' **** Aktualisiere Lizenznehmer-Datensatz ****

    ' Hier tritt das Problem auf, daß der Lizenznehmer-Datensatz nicht über
    ' ein Recordset, das die Tabelle direkt anspricht, aktualisiert werden
    ' kann, da er ja noch im Formular 'KV_Lizenznehmer' geöffnet ist. Dies
    ' würde einen Mehrbenutzerfehler erzeugen. Eine Contra-Programmierung
    ' über andere Lock-Verfahren fangen wir besser gar nicht erst an.
    ' Witzigerweise kann der Datensatz im Clone-Verfahren sehr wohl ak-
    ' tualisiert werden.

    Set RS = Forms!KV_Lizenznehmer.RecordsetClone

    RS.Bookmark = Forms!KV_Lizenznehmer.Bookmark

    RS.Edit
    RS("AktVersion") = LetzteVersion
    RS.Update

    RS.Close

    ' Requery, damit das Steuerelement AktVersion die Liste der zu diesem
    ' Datensatz gehörenden Lizenzen in der aktuellen Form anzeigt.

    Forms!KV_Lizenznehmer.Requery


Steuerelemente berechnen   Quelle: dmt   Datum: 03.2004   nach oben

BERECHNETE STEUERELEMENTE / VIRTUELLE FELDER:

In Access können, ähnlich wie die Extern-Felder in OA3, Textfelder dazu benutzt werden, Informationen, die von den Inhalten anderer Steuerelemente abhängig sind, anzuzeigen. Um diese Textfelder aber als reine Anzeigefelder zu benutzen,
müssen diese mit den Eigenschaften 'Enabled=False' und 'Locked=True' versehen werden, damit sie weder mit TAB-Sprung noch mit MausKlick anspringbar sind.

Es muß darauf hingewiesen werden, daß Access die in der Eigenschaft 'Steuerelementinhalt' hinterlegten Berechnungs-Anweisungen beinem Form_Current für ALLE angezeigten Datensätze ausführt. Beim Anzeigen eines solchen Formulares in der Datenblattansicht kann es dann zu unerhört langen Berechnungszeiten kommen, besonders wenn sog. Aggregat-Funktionen bemüht werden.

Womit wir auch schon beim nächsten Thema sind:

Ein Formular 'Leistungsrapport_Liste' in DMT.MDB zeigt Leistungsrapport-Daten an und ist in der Lage, diese entsprechend zu filtern. Dementsprechend sollen berechnete Steuerelemente Statistikwerte anzeigen. Hierbei kommt man an den Aggregatfunktionen nicht vorbei (wieviele Leistungen wurden noch nicht bezahlt usw.). Diese Funktionen liefern Auswertungen auf der Basis sog. Domänen, die laut Dokumentation Tabellen, Abfragen und auch Recordsets sein können. Das würde in etwa den Fähigkeiten von OA3 entsprechen, das Basistabellenfunktionen und auch solche anbietet, die sich auf im Arbeitsspeicher befindliche views beziehen. Doch leider akzeptiert der Domänen-Parameter einer Access-Domänenfunktion nur Namen physikalisch gespeicherter Tabellen und Abfragen. Das führte dazu, daß bei Filterung der angezeigten Datensätze, was durch zusammenbaubare SQL-Strings gut funktioniert, parallel die Eigenschaft SQL einer temporären Abfrage eingestellt werden mußte (Festplatte schreibt!), um auf Basis dieser Temporär-Abfrage Domänen-Funktionen benutzen zu können. Und weil es dann auch noch zu den oben beschriebenen Problemen in der Datenblattdarstellung kam, wurde die ganze Sache im Basic-Code abgewickelt. Peinlich, peinlich, zumal das Problem über Mitternacht reichender Zeiteinträge (22:30 - 01:30 -> von>bis), das für eine Berechnung mit einer Excel-mäßigen WENN(...;...;...) erschlagen werden kann, sich partout nicht verschachteln lassen wollte. Hier mußte sogar ein Durchnudeln der Recordset-Datensätze im Code mit laufender Summenbildung durchgeführt werden, um zum gewünschten Ergebnis zu gelangen. Echt Arschlochmäßig !

Vielleicht sollten diesbezüglich SQL-Unterabfragen-Konstrukte in Betracht gezogen werden.

*

Berechnete Steuerelemente möchte ich meistens dazu benutzen, wenn ich intern IDs zuweisen möchte, der Anwender aber in einem Kombinationsfeld mit Klartext-Feldern arbeiten soll, und ich zur Verdeutlichung in weiteren virtuellen Feldern (die zwar im Formular, aber nicht in der Formular-Tabelle enthalten sind) weitere ID-bezogene Klartext-Felder anzeigen möchte. Die bisherige Methode, dem Kombinationsfeld per SQL weitere Spalten-Felder zuzuordnen, um sich in der Eigenschaft ControlSource der virtuellen Textfelder auf diese beziehen zu können, haben bisher immer prächtig funktioniert, bis ich eines Tages feststellen mußte, daß das nur in der Einzelblatt-Formularansicht der Fall ist !

In der Endlos-Formular- und in der Datenblatt-Ansicht werden allen virtuellen Felder, die dort auch enthalten sind, die entsprechenden Werte des aktuellen Formulardatensatzes zugewiesen. Sämtliche Versuche mit Form_Current gebundenen SQL-Zuweisungen etc. schlugen fehl.

Hier wurde wohl der Fehler gemacht, Werte an das Steuerelement selbst, anstatt an seine Eigenschaft Steuerelementinhalt zu übergeben.

Ein Ausdruck (würg!) im Eigenschaftsfenster für Steuerelementinhalt wie z.B.

'=([bis]-[von])*24'

wird hingegen auch in der Datenblattansicht singulär korrekt verarbeitet !

Sollten diffizile Umstände abgecheckt werden müssen, bleibt zu testen, ob das nicht durch eine Funktion bewerkstelligt werden kann, die den gewünschten Wert an die Eigenschaft Steuer-elementinhalt zurückgibt, wie z.B. folgende:

Erstellen einer kleinen Funktion, die sogar NULL-Werte abcheckt und per DLookUp jeweils gültige Werte in Abhängigkeit vom Formular-ID, das übrigens selbst als Steuerelement nicht im Formular enthalten sein muß, zurückgibt. In der Eigenschaft Steuerelementinhalt des virtuellen Feldes wird '=Funktionsname()' eingetragen.

Voila'.

Die folgende Funktion gibt für ein virtuelles Feld eines Formulares, das sich nur auf eine IDs enthaltende Zuweisungstabelle bezieht, die Klartext-Information des Feldes 'Art' in der Stammdaten-Tabelle 'Stichworte' zurück, wenn 'ID_Stichwort' nicht NULL ist. So wird ein häßliches '#NAME' für die letzte Zeile 'neuer Datensatz' vermieden. Die zweite Bedingung stellt sicher, daß beim Anlegen eines neuen Datensatzes, für den noch kein Zähler-ID vergeben wurde, kein ungültiger Wert '#FEHLER' zurückgegeben wird. Die Funktion selbst wird als Variant deklariert, damit '#FEHLER' nicht auftaucht, wenn beim Anlegen eines Datensaztes per 'DoCmd RunSQL' zwar ein ID als Zähler vergeben wurde, aber noch kein Art-Wert besteht. Die Funktion nimmt einen in diesem Fall doch auftretenden NULL-Wert klaglos entgegen und verhält sich per der Rückübergabe an die Eigenschaft ControlSource, als ob nichts gewesen wäre.

Function Ermittle_Art () As Variant

    On Error GoTo err_Ermittle_Art

    Ermittle_Art = DLookup("Art", "Stichworte", "ID=" & Me![ID_Stichwort])

    Exit Function


err_Ermittle_Art:

    Exit Function

End Function

Jetzt muß nur sichergestellt werden, in welchen Fällen angezeigte Daten geändert werden können, und überprüft werden, ob ein explizites Requery notwendig ist (z.B. Anlegen oder Aktualisieren durch Merker in Form_AfterUpdate und Merker in Form_AfterDelConfirm. Bei Form_Close kann dieser Merker dann ausgewertet und ein Requery des Datenblatt-Sorgenkindes ausgeführt werden.

Fairerweise muß hinzugefügt werden, daß eine ausgeklügelte Abfrage, die bei etwaigen Manipulationen nur die gewünschten Daten verändert ( TESTEN ! ), u.U. Klimmzüge, wie oben angeführt, ersparen kann. Wenn diese Abfrage funktioniert, entfallen auch die #NAME und #FEHLER-Geschichten. -> siehe WISSEN.MDB, Formular 'Stichworte', Form_Open


Mal was Positives / CONTROLSOURCE:

Das HINTERLEGEN von FORMELN, um dem Anwender zu ermöglichen, Berechnungs-grundlagen selbst zu editieren, kann relativ einfach realisiert werden:

In einem Tabellenfeld wird ein String a'la '=(dH*v)/(DR*60)' hinterlegt. Diese Formel berechnet die Distanz, die ein Flugzeug für die Überwindung einer gewissen Höhendifferenz zurücklegt, wenn es bei gegebener Geschwindigkeit eine gegebene Sinkrate einhält. Die eingesetzten Variablen entsprechen ihren Namen den Feldern eines Formulares, das sich bei Bedarf auch auf eine Tabelle stützen darf. Eckige Klammern können bei vernünftiger Namensvergabe weggelassen werden.

Leider waren alle Versuche, die Bezüglichkeiten innerhalb der Steuerelement-Eigenschaften zu realisieren, erfolglos.

Daher mußte wie üblich eine verschissene Code-Lösung her:

In Form_Current wird eine Routine ausgeführt, die dem berechneten Steuerelement dist die ControlSource-Eigenschaft '=(dH*v)/(DR*60)' zuweist und obendrein diesen Wert dem Wert eines weiteren, ungebundenen Textfeldes, das einfach nur diese Formel anzeigt.

Jetzt sehen wir in dem berechneten Steuerelement das Ergebnis (sogar in der Datenblatt-Ansicht !!!), während uns das Formelfeld die Formel in editierbarer Form präsentiert (ebenfalls auch in der Datenblattansicht). Wird diese Formel manuell geändert, wird in FormelFeld_AfterUpdate dem berechneten Feld dessen ControlSource neu zugewiesen und auch in der Listendarstellung werden alle Werte aller Datensätze getrennt korrekt neu berechnet. Über eine Wiederherstellen-Schaltfläche ließe sich der ursprünglich hinterlegte Wert wieder zuweisen und somit sind sogar waschechte 'WasWäreWenn'-Anwendungen denkbar !

Let's have a party.

Davon gibts natürlich auch frustrierende Varianten. Ein externes Formular soll statistische Informationen zu den Daten des aktuellen Feldes des aktuellen Formulares anzeigen. Das klappt ansatzweise ganz gut, da der RecordsetClone von Formulardaten sogar die ansonsten per Code nicht greifbare momentane, benutzerdefinierte Filter-/Sortierkriterien 'sieht'. Leider können Ausdrücke a'la 'Min(VK) nicht in Min(Formulare!Test!VK) umgemünzt werden. Nun werden also im zu bearbeitenden Formular diverse Felder (unsichtbar und ohne ControlSource, um die Laufzeit nicht zu beeinflussen) hinterlegt, die dann vom Statistik-Formular aus diese Statistik-felder mit Steuerelementinhalten versehen, eine Berechnung des Formulares erzwingen und die ermittelten Werte dann selbst anzeigen. Aus der Sicht des Anwenders sieht das ganz ordentlich aus. Werden solche Berechnungen jedoch auf der Basis von Text-Werten durchgeführt, klappt das sehr wohl, wenn der Ausdruck Min(Name1) im Entwurfsmodus des Formulares dem Steuerelement zugewiesen wurde. Erfolgt die Zuweisung per Code vom Statistikformular aus, tritt ein 'Ungültiger Typ'-Fehler auf. Was MS-EXZEß wann und wo initialisiert, evaluiert, aktualisiert, berechnet, oder sonst irgendwie versaut und wie die implementierte Programmier-sprache damit etwas zu tun haben soll, liegt und bleibt bis zum nächsten Urknall in den Sternen.

*

Aber aufgepasst werden muß auch hier.

Ein Ausdruck '=[Anzahl]*[Einzelpreis]' zur Summenberechnung führt zu völlig abgedrehten und selbstverständlich falschen Werten. Bei einem einzigen Artikel (1*12,50) einer Auftragspositionen-Tabelle ermittelte Access 200,00 als Ergebnis, was selbst mit dem nachvollziehbarem Effekt, daß Anzahl als deutschsprachige Ausdrucksanweisung interpretiert wird, nicht erklärt werden kann.

Mit '=[Formular]![Anzahl]*[Einzelpreis]' als deutschem Äquivalent zu 'Me!' klappt das schon, obwohl doch die verschissenen '[' den String als Steuerelement-/Feldnamen eindeutig kennzeichnen sollten !

Im Auftragsformular in dmt.mdb sollen Datensatz-bezogene Daten, die selbst berechnete Werte darstellen, angezeigt werden. So enthalten die Unterformulardaten Auftragspositionen, die u.a. aus Anzahl und Einzelpreis von Artikeln bestehen, aus denen sich der Gesamtpreis der Position ergibt. Im Hauptformular soll dann die Gesamtnettosumme der zu diesem Auftrag gehörenden Positionen zu weiteren Berechnungen zur Verfügung stehen. Gelöst wurde es letztendlich durch einen umfangreicheren Domänen-Ausdruck, indem auch die Einzelberechnungen Anzahl * Einzelpreis explizit enthalten sind. Das flutscht ganz ordentlich, da hier wohl interne, SQL-nahe Routinen abgearbeitet werden. Lediglich im Unterformular sollte noch ein Forms!Auftrag.Recalc in AfterUpdate eingefügt werden, damit bei geänderten Positionsdaten auch die Summen im Hauptformular aktualisiert werden.

*

In schönen Anwendungen können ControlSource-Formeln innerhalb editierbarer Vorgabe-Felder als Strings hinterlegt werden; allerdings kommt es dann zu 100%igen Komplikationen mit deutschen und amerikanischen Anführungszeichen. Ein Beispiel, wie es klappt:

=': ' & Titel & ', ' & Utitel

ist genau so eine Zeichenkette, die incl. dem Formelzeichen '=' Teilzeichenketten mit amerikanischen Hochkommata eingrenzt und (hoffentlich) bekannte Feldnamen einfach benennt.
So gesehen ganz einfach, aber der Weg dahin kann SEHR steinig sein.


Steuerelemente checken   Quelle: dmt   Datum: 03.2004   nach oben

STEUERELEMENTE automatisch überprüfen / durchlaufen:

Wenn z.B. ein Suche-Formular eine Reihe von Felder zur Kombination von Suchkriterien zur Verfügung stellt, können diese der Reihe nach überprüft werden, ohne das ein expliziter Vergleich stattfinden muß:

Dim F As Form

Set F = Forms!Ventile_Filter

For i% = 0 To F.Count - 1
    If Not IsNull(F(i%).Tag) Then
       ' Manipulations-Routine
    End If
Next i%

Hier werden alle Steuerelemente des Formulares 'Ventile_Filter' (HP_VHP.MDB) überprüft. Das sind immerhin 111 ! Entsprechend einer sinnvollen Eigenschaft (hier 'Tag', der in diesem Fall gleichzeitig den Operator '=' oder 'LIKE' enthält, der beim Zusammenbauen eines variablen SQL-String's direkt verwendet werden kann.

Beim Zusammenbau eines solchen automatisch erstellten SQL-Strings gibt es natürlich wieder einmal eine Reihe von Problemen:

- Dezimalzahlen, die für den Anwender sowie auch (halb-)intern bei Übergaben und Anzeigen mit ',' als Komma versehen sind, werden bei Verarbeitung in Access-Basic falsch interpretiert. Solche Werte müssen zuerst einer String-Manipulation übergeben werden, die das Komma durch den amerikanischen Dezimal-'.' ersetzt:

    If IsNumeric(ta$) Then
       ta$ = Dezimal_to_US_String(ta$)
    End If

Und hier nun die Funktion selbst:

Function Dezimal_to_US_String (Zahl As Variant) As String

    If IsNumeric(Zahl) Then

       k$ = ","
       s$ = Zahl
       p% = InStr(s$, k$)

       If p% > 0 Then
          Mid$(s$, p%, 1) = "."
       End If

       Dezimal_to_US_String = s$

    Else

       Beep: MsgBox "Das Argument '" + Zahl + "' ist keine gültige Zahl !", 16, "Dezimal_to_US_String"

    End If

End Function

- An einen derartig automatisch erzeugten SQL-String müssen natürlich auch die Namen der Tabellen-Felder übergeben werden. Hier kann es bei Namen wie 'GROUP', die u.U. mit reservierten Wörtern identisch sind, zu Problemen kommen. Um das zu vermeiden, sollte der Feldname mit '[' und ']' eingeschlossen werden !

Wenn wie in HP_VHP.MDB ein solcher String (teilweise) angezeigt werden soll, kann das aber auch Scheisse aussehen. Um diese Zeichen für die Galerie wieder zu entfernen, kann eine Routine wie folgt eingesetzt werden, die ein bestimmtes Zeichen aus einer Zeichenkette entfernt:

    ' Entferne aus r$ alle "[" und "]"

    Do While InStr(r$, "[") > 0
       p% = InStr(r$, "[")
       s1$ = Left$(r$, p% - 1)
       s2$ = Right$(r$, Len(r$) - p%)
       r$ = s1$ + s2$
    Loop

Dieses Beispiel findet sich in allgemeiner und ausgereifter Form in der Funktion ClearStringFrom wieder.

* * * *

Steuerelemente können auch auf ihren Typ (Objekttyp) hin geprüft werden:

Sub Art_des_Steuerelementes (C As Control)

    If TypeOf C Is BoundObjectFrame Then s$ = "BoundObjectFrame: Gebundenes ObjektFeld": Exit Sub
    If TypeOf C Is CheckBox Then s$ = "CheckBox: Kontrollkästchen": Exit Sub
    If TypeOf C Is ComboBox Then s$ = "ComboBox: Kombinationsfeld": Exit Sub
    If TypeOf C Is CommandButton Then s$ = "CommandButton: Befehlsschaltfläche": Exit Sub
    If TypeOf C Is Graph Then s$ = "Graph: Diagramm": Exit Sub
    If TypeOf C Is Label Then s$ = "Label: Bezeichnungsfeld": Exit Sub
    If TypeOf C Is Line Then s$ = "Line: Linie": Exit Sub
    If TypeOf C Is ListBox Then s$ = "ListBox: Listenfeld": Exit Sub
    If TypeOf C Is ObjectFrame Then s$ = "ObjectFrame: Objektfeld": Exit Sub
    If TypeOf C Is OptionButton Then s$ = "OptionButton: Optionsfeld": Exit Sub
    If TypeOf C Is OptionGroup Then s$ = "OptionGroup: Optionsgruppe": Exit Sub
    If TypeOf C Is PageBreak Then s$ = "PageBreak: Seitenumbruch": Exit Sub
    If TypeOf C Is Rectangle Then s$ = "Rectangle: Rechteck": Exit Sub
    If TypeOf C Is SubForm Then s$ = "SubForm: Unterformular": Exit Sub
    If TypeOf C Is SubReport Then s$ = "SubReport: Unterbericht": Exit Sub
    If TypeOf C Is TextBox Then s$ = "TextBox: Textfeld": Exit Sub
    If TypeOf C Is ToggleButton Then s$ = "ToggleButton: Umschaltfläche": Exit Sub

    s$ = "'" + C.Name + "' ist vom Typ " + s$

    MsgBox s$

End Sub

Ebenso wie oben beschrieben können die Felder eines Suche-Formulares auch wieder auf z.B. einen vergebenen Tag zurückgesetzt oder automatisch gelöscht werden:

    For i% = 0 To Me.Count - 1
        If Not IsNull(Me(i%).Tag) Then
           Me(i%) = Me(i%).DefaultValue
        End If
    Next i%

Für das Zurücksetzen ausgefüllter Dialoge kann folgendes verwendet werden:

    On Error Resume Next

    Dim i As Integer

    For i = 0 To Me.Count - 1
        If Not IsNull(Me(i).DefaultValue) Then
           Me(i) = Me(i).DefaultValue
        Else
           Me(i) = Null
        End If
    Next i

    Me!Institution.SetFocus

Der Errorhandler erlaubt wahllos durch alle Steuerelemente zu gehen, Textfelder werden geleert und Optionsgeschichten erhalten wieder ihren Standardwert. Sonderwünsche können bei Bedarf dann immer noch extra verarztet werden.

Um den sprichwörtlichen Vogel wieder mal abzuschießen, kann auch hier noch eine Steigerung stattfinden:

Hier werden die Felder eines Suche-Formulares samt denen eines eingebetteten Unterformulares gelöscht. Die explizite Namensnennung weiter unten muß natürlich angepaßt werden.

    On Error Resume Next

    Dim i As Integer, F As Form

    Set F = Me


Clear_Form_Fields:

    For i = 0 To F.Count - 1
        If Not IsNull(F(i).DefaultValue) Then
           F(i) = F(i).DefaultValue
        Else
           F(i) = Null
        End If
    Next i

    If F.Name = "Suche_UF_Institution" Then
       Me!og_Suche_nach.SetFocus
    Else
       Set F = Me.UF.Form
       GoSub Clear_Form_Fields
    End If


Steuerelemente Reihenfolge   Quelle: dmt   Datum: 03.2004   nach oben

Unterschiedliche REIHENFOLGE VON STEUERELEMENTEN eines Formulares:

Das Layout eines Formulares kann für die Datenblattansicht auch im Nachhinein geändert werden. Interessant ist, das abhängig von der Ansicht CurrentView auch so die Reihenfolge der Steuerelemente verschieden dargestellt und auch die Eigenschaft TabIndex neu gesetzt werden werden kann, so daß sich die Datenblattansicht auch von der optischen sowie der Ablauf-Struktur her anders präsentieren kann als die Formularansicht.


Steuerelemente Werte zuweisen   Quelle: dmt   Datum: 03.2004   nach oben

Werte an gebundene Steuerelemente zuweisen:

Im LaserJob-Manager ergab es sich, daß der Anwender die Formel, nach der eine Rechnungs-stellung erfolgt, selbst editieren kann. Sinnvollerweise stellen ihm zwei Listenfelder die in den Tabellen 'Auftrag' und 'Auftragspositionen' enthaltenen Felder zur Verfügung, damit er sich nicht um die korrekte Schreibweise kümmern muß. Einfaches Auswählen und Bestätigen des jeweiligen Listeneintrages übernimmt diesen Wert und ergänzt den Formel-String dement-sprechend. Bei wiederholtem Wertezuweisen trat der beliebte Fehler 'Wert kann nicht zugewiesen werden' auf (Code: 'Me!Formel = Me!Formel & Wert'). Erst nachdem der bisherige, bereits geänderte Formelfeld-Wert an eine String-Variable übergeben wurde, die um den neuen Wert ergänzt wurde, kann eine Rücküberweisung an das gebundene Formelfeld erfolgen:

    Dim s As String

    s = Me!Formel

    s = s & Wert

    Me!Formel = s

    Me!Formel.SetFocus

    SendKeys "{F2}", True


Steuerelemente, Probleme   Quelle: dmt   Datum: 08.2010   nach oben

Fehlerhafte Platzierung von Feldwerten in einem Formularfeld MS-Access 2

In einem Formularfeld (gesperrt und nicht aktiviert) wollte sich ein Zahlenwert nicht sauber rechtsbündig anordnen lassen, wenn dessen Einstellung für "Dezimalstellen" auf 0 gesetzt wurde.

Mit Dezimalstellen von 1 oder mehr klappt das dann wieder.

Der Fehler tritt auf für die Formate "Festkommazahl" und "Standardzahl".

Dezimalstellen von 0 werden einwandfrei dargestellt, wenn Format gar nicht bzw. als "Allgemeine Zahl" angegeben wird.


Steuerelemente, Pushbuttons   Quelle: dmt   Datum: 03.2004   nach oben

PUSHBUTTONS:

Endlich mal was Erfreuliches: Wenn einem pb ein Symbol zugeordnet wird, überdeckt dieses die Beschriftung der Schaltfläche. Zwar reagiert der pb noch auf den '&'-Hotkey, dieser ist aber ja nicht mehr sichtbar. Bisherige Abhilfe war das Zugeben eines Labels mit der optischen Hotkey-Zuweisung. Noch besser ist es, dieses Label auszuschneiden und bei anschließend markierter Schaltfläche wieder einzufügen. Nun verhält sich die Sache wie bei den an Textfeldern gebundenen Bezeichnungsfelder.


Unterformulare   Quelle: dmt   Datum: 03.2004   nach oben

UNTERFORMULARE / FORM_CURRENT:

werden von Access schnell und zuverlässig verwaltet, solange man Access sich selbst überläßt und nicht versucht, sich mittels FUCKZESS-Basic einzumischen.

Wie schon im Hause Kelkel des öfteren vorgekommen, treten verstärkt Probleme auf, wenn versucht wird, für den aktuellen Datensatz Eigenschaften eines Unterformulares in Abhängigkeit gewisser Bedingungen einzustellen.

So kann es sinnvoll sein, für einen Datensatz einen zugehörigen, einzigartigen Datensatz einer anderen Tabelle anzuzeigen ( z.B. einen externen gespeicherten Leistungsrapport-Datensatz eines Termines. Abhängig davon, ob die Art des Termines geschäftlich oder nicht ist, sollte ein Rapport-Datensatz geführt werden. Auf diese Weise wird für nicht-geschäftliche Termine kein unnötiger Speicher verschwendet, wie bei einer Beziehungs-1:1-referentiellen Lösung, oder wenn man die Felder der Zweittabelle alle in die Ersttabelle einfügt. ). Diese Lösung erfordert einigen programmiertechnischen Aufwand, aber dafür machen wir das ja auch beruflich.

Das nötige Abgechecke muß pro angezeigten Datensatz, also im Ereignis Form_Current erfolgen. Und da fängt der Ärger dann auch schon an.

In oben genanntem Beispiel muß für das Unterformular die Eigenschaft 'DefaultEditing' ab-hängig davon eingestellt werden, ob Zusatzdaten nötig und, wenn ja, evtl. schon vorhanden sind. Das Unterformular wird dementsprechend ein-
oder ausgeblendet und die Eingabe neuer Datensätze ermöglicht oder verboten.

Da ergab es sich, daß das Formular 'Termine' in 'dmt.mdb' jedesmal einen Fehler erzeugte, wenn von der Entwurfsansicht in die Formularansicht gewechselt wurde oder wenn es per DoCmd OpenForm "Termine" aufgerufen wurde.

Verwirrenderweise kam zu diesem Fehler auch noch der Umstand hinzu, daß das Datenblatt-Formular beim Aufruf aus dem Datenbankfenster auch brav als Datenblatt erscheint, aber per DoCmd OpenForm "Termine" in der Formularansicht zu bewundern war. Und genau dann ranzte jede Code-Zeile, die auf Me!UF_Name.Form.Eigenschaft Bezug nahm, gnadenlos ab. Wurde das Formular jedoch in der Datenblattansicht geöffnet und erst dann auf Formularansicht umgeschaltet, erzeugten dieselben Anweisungen keinen Fehler !

Auf der Suche nach vertretbaren Lösungen ( z.B. ein Me.Requery in Form_Current, kommt ganz bitter, da das Ereignis Form_Current, das beim Öffnen genau einmal für Hauptformular eintritt, aber dafür mehr als einmal für eventuell eingebettete Unterformulare, durch das Requery zwar nicht unendlich, aber dafür sehr, sehr oft eintritt. Wahrscheinlich gemäß Anzahl der Datensätze, was aber nicht gerade logisch ist, denn dann müßte ACCESS ja die Datensätze der Reihe nach abnudeln, obwohl nur einer angezeigt wird (Formularansicht !). Bei berechneten Steuerelementen in der Datenblattansicht kennen wir das ja schon.

Kurz vor der Verzweiflung bot sich folgende Lösung an:

Die besagten Anweisungen machen nur in der Formularansicht auch Sinn (CurrentView=1). In der Datenblattansicht würden sie auch einen verständlichen Fehler erzeugen, da Unterformulare dann nicht dargestellt werden können. Die Eigenschaft CurrentView ist immerhin bei Form_Open schon bekannt. Im Deklarationsteil wird eine Objektvariable vereinbart, in Form_Open erfolgt dann die CurrentView-abhängige Objekt-Zuweisung, die dann auch fehlerfrei im ersten Form_Current benutzt werden kann. Natürlich dürfen sämtliche Bezüge auf diese Objektvariable nur in der entsprechenden Ansicht erfolgen.

Der Versuch, diese Variable erst in Form_Current, wo der Hund auch begraben liegt, zu vereinbaren, mißlang nicht nur, sondern erzeugte auch einen wunderschönen Applikations-Absturz. Geil !

Allerdings gab es mit Objektvariablen, die auf die Eigenschaft FORM eines Unterformulares verweisen, schon häufiger Ärger. Beliebt ist der Fehler 2455 'Unzulässige Bezugnahme auf Eigenschaft: 'Form'.', der sehr gerne in nicht nachvollziehbarer Weise auftritt. Allerdings konnte er in einem Fall dingfest gemacht werden. Wenn ein Formular Kopf- und Fußbereiche enthält, führen Anweisungen, die sich auf die Eigenschaft FORM des Unterformulare beziehen, zu obigem Fehler, wenn durch die Datensätze geblättert wird und der Fokus sich in einem Steuerelement ausserhalb des Detailbereiches befindet. Vielleicht hängen die ewigen Probleme im dmt.mdb - Termine-Formular auch damit zusammen. Auf sowas muß man erst mal kommen.

Hier nun ein Lösungsvorschlag für die wie üblich peinlichen Probleme mit Microsoftware:

Sub Form_Current ()

    On Error GoTo err_FC

    Dim RS As Recordset, PC As Control, iFuck As Integer

    Set PC = Screen.PreviousControl
    Set RS = Me!UF_LR.Form.RecordsetClone

    RS.MoveFirst

    Do While Not RS.EOF
       RS.MoveNext
    Loop

    RS.Close

    If iFuck = True Then PC.SetFocus

    Exit Sub


err_FC:

    If Err = 2455 Then
       iFuck = True
       Me!UF_LR.SetFocus
       Resume
    Else
       Fehler "Form_Current"
       Exit Sub
    End If

End Sub

Für Access selbst würde es genügen, mit exit sub dieses traurige Tal der Tränen zu verlassen, aber da ja ein explizites Durchlaufen der Recordsetclone-Datensätze erforderlich ist, muß der Umweg über PreviousControl (ActiveControl macht in Form_Current einen Fehler) und Fokus hin und her gegangen werden.


ÄNDERUNGEN im Entwurfsmodus in Unterformularen können erst dann im Hauptformular getestet werden, wenn die betroffenen Formulare gespeichert, geschlossen und neu aufgerufen wurden. Ansonsten werden die Änderungen nicht wirksam.


UNTERFORMULARE können auch dynamisch eingesetzt werden, in dem ihre Eigenschaft SourceObject kontextbezogen ausgetauscht wird (s. koro.mdb, lib_lj.mda, bestatt.mdb).

Verfeinert wurde das Ganze in bestatt.mdb, wo Adressen-Art-bezogen jeweilige Zusatzdaten wie z.B. Friedhofsdaten, Bankverbindungen etc. in getrennten, kleinen Tabellen hinterlegt sind. Eine Schaltfläche zeigt durch ihre Farbe sogar an, ob für diesen Datensatz entsprechende Zusatzdaten angelegt wurden oder nicht, bzw. wird ausgeblendet, wenn für die aktuelle Adressen-Art keine Zusatzdaten vorgesehen sind. Allerdings mußte nach Öffnen dieses UF_Zusatz und Schließen per Fokuswechsel ein zweimaliges Close ausgeführt werden, da der Fokus sich doch tatsächlich nach Setzen auf Suchname und Ausblenden des UF_Zusatz-Unterformulares unsichtbarer Weise ins Unterformular verirrt hatte. Erst ein zusätzliches Disablen sorgte wieder für klare Verhältnisse.

* * * *

Ärgerlich sind codierte Auswertungsroutinen, die z.B. für ein Formular, daß mehrere Datensätze auf einmal anzeigt (Datenblätter oder Endlosformulare), deren Ergebnisse als Summe über alle Datensätze ausgewertet werden sollten.

Diese Routinen werden nämlich nur für die Datensätze abgearbeitet, die zum jeweils ersten Mal auf dem Bildschirm angezeigt werden.

In der comcon-Formularsammlungs-Anwendung formular.mdb kann im Form_Current-Ereigniscode des Formulares Pflege_ArztFormulare bewundert werden, wie per Basic-Anweisungen

quasi-manuell alle Formular-Datensätze durchlaufen werden.

Am Ende solcher Durchläufe stellt

DoCmd.GoToRecord acActiveDataObject, ,acFirst
wieder den ersten Datensatz als aktuellen ein.

Weniger bewunderswert ist hingegen die Tatsache, daß Code teilweise nicht stapelweise, sondern mit Quasi-rekursiven Sprüngen in sich selbst ausgeführt wird und dann so Sachen passieren, daß Kontroll-Variablenwerte, die in einer Zeile gesetzt wurden (und per Debug auch ihren richtigen Wert besitzen) nach so einem "Geister"-Sprung vom selbigen nichts mehr wissen.


Unterformulare, Probleme   Quelle: dmt   Datum: 02.2009   nach oben


Unterformulare, TabIndex   Quelle: dmt   Datum: 04.2004   nach oben

Erhalten der TABULATOR/RETURN-REIHENFOLGE trotz WECHSEL in ein eingebettetes UNTERFORMULAR / NAVIGATION:

Soweit ich es betrachten kann, ist es perfekt (August 2000).

Es beherrscht in Unterformularen, die evtl. auch mehrere Datensätze darstellen können (incl. des per EOF nicht greifbaren, neuen Formulardatensatzes) alle Navigationstasten und unterscheidet für die Pfeiltasten, ob nicht doch der Editiermodus angesagt ist.

Bisher nur verwendet in meiner eigenen dmt.mdb im Modul Navigate_in_SubForm.

In der Ereignisprozedur Key_Down des jeweils ersten oder letzten Steuerelementes eines mehrere Datensätze enthaltenden Formulares steht

    KeyCode = Navigate_in_MultipleData_SubForm(Me, "first", KeyCode, Shift)

"first" wird beim letzten Steuerelement durch "last" ersetzt.

Und, um noch eins draufzusetzen, jetzt auch mit gefaktem Reihenfolgen-Imitat beim Hingehen in das Unterformular-Steuerelement (vorwärts und rückwärts), welches sich selbst ja den aktiven Unterformular-Datensatz innerhalb eines Mutterformular-Datensatzes merkt. Obendrein wird, wenn Datensätze rückwärts durchlaufen werden, im Unterformular immer der erste Datensatz eingestellt. Diese beiden Eigenschaften müssen geändert werden, damit der Anwender ein konsistentes Anwendungsverhalten sieht. Angepackt wird dieses Problem im Ereignis Beim_Hingehen des Unterformular-Steuerelementes.

Sub UF_Kommunikation_Enter ()

    Enter_MultipleData_SubForm Me!UF_Kommunikation, "Art", "Wert"

End Sub

Die aufgerufene Routine arbeitet jetzt mit Unterfomular-Bookmarks, da das verwendete SendKeys wieder mal Probleme im Wechsel mit Tastatur- und Mausbedienung machte.

Sub Enter_MultipleData_SubForm (C As Control, sC_First As String, sC_Last As String)

    On Error GoTo err_Enter_MultipleData_SubForm

    Dim RS As Recordset, sBM As String

    Set RS = C.Form.RecordSetClone

    If Screen.PreviousControl.TabIndex < C.TabIndex Then
       C.Form(sC_First).SetFocus
       RS.MoveFirst
       sBM = RS.Bookmark
       C.Form.Bookmark = sBM
    Else
       C.Form(sC_Last).SetFocus
       RS.MoveLast
       sBM = RS.Bookmark
       C.Form.Bookmark = sBM
    End If

    Exit Sub


err_Enter_MultipleData_SubForm:

    If Err <> 3021 Then
       Fehler "Enter_MultipleData_SubForm"
    End If

    Exit Sub

End Sub


Function Navigate_in_MultipleData_SubForm (F As Form, sWhich As String, KeyCode As Integer, Shift As Integer)

    On Error GoTo err_Navigate_in_MultipleData_SubForm

    ' **** überwindet in Unterformular-Steuerelementen die ****
    ' **** Navigationshürde, indem im Ereignis KeyDown des ****
    ' **** Steuerelementes die Tasten Pfeil, Tab und Enter ****
    ' **** abgefangen und hier auf (Shift) + Ctrl + Tab um-****
    ' **** geleitet werden.  geht so schon i.O.      ****

    Dim iRet As Integer

    ' **** Wurde eine der Navigationstasten gedrückt ? ****

    If KeyCode = 37 Or KeyCode = 38 Or KeyCode = 39 Or KeyCode = 40 Or KeyCode = 9 Or KeyCode = 13 Then

       iRet = First_Or_Last_FormRecord(F)

       ' **** und wenn ja, welche und sind wir überhaupt am  ****
       ' **** oberen oder unteren Ende der Datensatzgruppe ? ****

       ' **** Ausnahme verarzten ****

       If iRet = 1 And F.RecordSetClone.RecordCount = 0 Then
          If (KeyCode = 37 Or KeyCode = 38 Or (KeyCode = 9 And Shift = SHIFT_MASK)) Then
             iRet = -1
          End If
       End If

       If (KeyCode = 37 Or KeyCode = 38 Or (KeyCode = 9 And Shift = SHIFT_MASK)) And sWhich = "first" And iRet = -1 Then
          If (KeyCode = 37 Or KeyCode = 38) And IsNavigationMode() = False Then
             Navigate_in_MultipleData_SubForm = KeyCode
          Else
             Navigate_in_MultipleData_SubForm = 0
             SendKeys "+^{TAB}", True                      ' **** rückwärts aus Unterformular springen ****
             DoEvents
             DoCmd SelectObject A_FORM, Screen.ActiveForm.Name
          End If
       ElseIf (KeyCode = 39 Or KeyCode = 40 Or (KeyCode = 9 And Shift = 0) Or KeyCode = 13) And sWhich = "last" And iRet = 1 Then
          If (KeyCode = 39 Or KeyCode = 40) And IsNavigationMode() = False Then
             Navigate_in_MultipleData_SubForm = KeyCode
          Else
             Navigate_in_MultipleData_SubForm = 0
             SendKeys "^{TAB}", True                       ' **** vorwärts aus Unterformular springen ****
             DoEvents
             DoCmd SelectObject A_FORM, Screen.ActiveForm.Name
          End If
       Else
          Navigate_in_MultipleData_SubForm = KeyCode
       End If

    Else
       Navigate_in_MultipleData_SubForm = KeyCode
    End If

    Exit Function


err_Navigate_in_MultipleData_SubForm:

    Fehler "Navigate_in_MultipleData_SubForm"
    Exit Function

End Function

First_Or_Last_FormRecord ermittelt, ob ein Formulardatensatz evtl. der erste
(-1) oder der letzte Datensatz (incl. neuer, leerer Formulardatensatz) (1) ist.

Private Function First_Or_Last_FormRecord (F As Form) As Integer

    ' **** stellt fest, ob es sich bei dem aktuellen ****
    ' **** Formulardatensatz um den ersten oder den  ****
    ' **** letzten handelt.                          ****

    ' **** Es muß geprüft werden, ob in dem Formular ****
    ' **** neue Daten eingegeben werden können.      ****

    On Error GoTo err_First_Or_Last_FormRecord

    Dim RS As Recordset, v As Variant
    Dim iNewDataPossible As Integer

    If F.AllowEditing < 3 Then                      ' EOF tritt bereits beim
       iNewDataPossible = True                      ' letzten Datensatz ein, obwohl
    End If                                          ' noch ein neuer im Formular wartet.

    Set RS = F.RecordSetClone

    RS.Bookmark = F.Bookmark
    RS.MovePrevious

    If RS.BOF Then
        First_Or_Last_FormRecord = -1               ' erster Datensatz
    End If

    RS.MoveNext
    RS.MoveNext

    If RS.EOF And iNewDataPossible = False Then
        First_Or_Last_FormRecord = 1                ' letzter Datensatz ohne neue Daten
    End If

    Exit Function


err_First_Or_Last_FormRecord:

    If Err = 3021 And iNewDataPossible = True Then
       If F.Dirty = True Then
          First_Or_Last_FormRecord = -1             ' Neueingabe im ersten Datensatz
       Else
          First_Or_Last_FormRecord = 1              ' letzter Datensatz mit neuen Daten ohne Eingabe
       End If
    Else
       Fehler "First_Or_Last_FormRecord"
    End If

    Exit Function

End Function

Beinahe ein Kleinod: IsNavigationMode tut genau das, was es auch verspricht, nämlich den Navigationsmodus vom Editiermodus unterscheiden.

Private Function IsNavigationMode ()

    On Error GoTo err_IsNavigationMode

    Dim C As Control

    Set C = Screen.ActiveControl

    If C.SelLength = Len(C) Then
       IsNavigationMode = True
    End If

    Exit Function


err_IsNavigationMode:

    If Err = 94 Then
       IsNavigationMode = True
    Else
       Fehler "IsNavigationMode"
    End If

    Exit Function

End Function

nach oben
zur Startseite dieses Webangebotes zur infobase-Hauptseite