Event-Loop
Ereignis vs. Tastatur
Während bei "reinem" Clipper die einzige Interaktion mit der Tastatur war (es gab Zusatzbibliotheken wie die Frankie.LIB, mit der auch Mausfunktionen verfügbar wurden), gibt es unter Windows jede Menge Events (Ereignisse), die an ein Programm übermittelt werden, und auf die das Programm reagieren kann oder muss.
Ein Ereignis kann ein Tastatur-Anschlag oder ein Mausklick sein, ob der Moment, wo der Mauszeiger einen bestimmten Bereich betritt, oder das Vergrössern eines Fensters.
Jedem dieser Ereignisse sind eigene Codes zugeordnet, und Objekte, auf die sich die Ereignisse beziehen.
Ähnlich dem InKey() unter Clipper "erfragt" eine Xbase-Anwendung ein Ereignis vom System. Der Einfachheit halber gehen wir davon aus, dass wir es hier erst einmal nur mit einem Thread zu tun haben.
Der Event-Loop
LOCAL nEvent, mp1, mp2, oXbp nEvent := xbe_None WHILE nEvent <> xbeP_Close nEvent := AppEvent(@mp1, @mp2, @oXbp) oXbp:handleEvent(nEvent, mp1, mp2) END
Der einfachste Event-Loop benötigt vier Variablen. Eine Variable für die Entgegennahme des aktuellen Event, zwei Variablen für zusätzliche Informationen (Message Parameters), sowie eine Variable für das Objekt, in dessen Kontext das Event fällt.
Damit die Funktion AppEvent() mehr als eine Information zurückgeben kann, müssen die Parameter by Reference übergegeben werden.
Jedes Xbase-Part hat eine Methode :handleEvent(), die in der Lage ist, mit den übergebenen Parametern auf das Event zu reagieren. Das betroffene Objekt muss nicht als Parameter a :handleEvent() übergeben werden, da es aus dem Kontext von :handleEvent() über self ansprechbar ist.
zeitgesteuerter Event-Loop
LOCAL nEvent, mp1, mp2, oXbp, nWait nEvent := xbe_None nWait := 1000 WHILE nEvent <> xbeP_Close nEvent := AppEvent(@mp1, @mp2, @oXbp, nWait) IF nEvent = xbe_None // ELSE oXbp:handleEvent(nEvent, mp1, mp2) ENDIF END
AppEvent() akzeptiert einen vierten Parameter. Dieser Parameter gibt an, nach wievielen Millisekunden ohne Event das Warten auf ein Event beendet werden soll. Nach Ablauf der Zeitspanne ohne jedes Event wird xbe_None generiert und - in unserem Beispiel - abgefragt mit der Option, hier bestimmte Aktionen auszuführen. Andernfalls wird das Event wie bisher verarbeitet.
Bei Einsatz dieses Parameters muss immer berücksichtigt werden, dass selbst die Mausbewegung über einem Fenster des Programms ein Event auslöst und (nach Abarbeiten von :handleEvent()) die Wartezeit neu beginnt.
Erweiterungen des Event-Loop
Angenommen, wir haben den Event-Loop eines Browse. Mit Drücken der Taste F5 soll der Browse aktualisiert werden, und mit Drücken der Enter-Taste soll ein Satz zur Bearbeitung angezeigt werden:
LOCAL nEvent, mp1, mp2, oXbp nEvent := xbe_None WHILE nEvent <> xbeP_Close nEvent := AppEvent(@mp1, @mp2, @oXbp) DO CASE CASE nEvent = xbeP_Keyboard DO CASE CASE mp1 = xbeK_ENTER EditBrowseRecord(oXbp) CASE mp1 = xbeK_F5 BrowseRefresh(oXbp) OTHERWISE oXbp:handleEvent(nEvent, mp1, mp2) ENDCASE OTHERWISE oXbp:handleEvent(nEvent, mp1, mp2) ENDCASE END
Nachdem ein Event empfangen wurde, wird geprüft, ob es sich um ein Tastatur-Event handelt. Wenn nicht (und das dürfte die Mehrzahl der Fälle betreffen), wird in den OTHERWISE-Zweig verzweigt und das Event nach Vorgabe verarbeitet.
Wichtig ist auch, dass oXbp:handleEvent() zweimal vorkommt: einmal im OTHERWISE-Zweig des äusseren DO CASE (immer dann, wenn kein Tastatur-Event aufgetreten ist), und einmal im OTHERWISE-Zweig des inneren DO CASE: dort behandeln wir zwei Spezialfälle, F5 und ENTER. Alle anderen Tastatur-Eingaben würden "übersehen" ohne das oXbp:handleEvent() im OTHERWISE-Zweig.
Da der Event-Loop das "Herz" eines Thread darstellt, muss darauf geachtet werden, dass hier ein schneller Ablauf möglich ist. Dazu gehört auch, dass die Abfragen entsprechend aufgebaut werden, dass ihre Abarbeitung schnell vorgenommen werden kann.
Man könnte den obigen Ablauf auch so formulieren:
DO CASE CASE nEvent = xbeP_Keyboard .AND. mp1 = xbeK_ENTER EditBrowseRecord(oXbp) CASE nEvent = xbeP_Keyboard .AND. mp1 = xbeK_F5 BrowseRefresh(oXbp) OTHERWISE oXbp:handleEvent(nEvent, mp1, mp2) ENDCASE
Es sollte klar sein, dass die zweite Variante zeitaufwändiger ist, denn in der Mehrzahl aller Fälle müssen zwei Abfragen ausgeführt werden, ehe ein Event, das nicht F5 oder ENTER ist, bearbeitet werden kann.
Granularität der Events
An dem vorhergehenden Beispiel erkennen wir, dass Events auch in Gruppen gemeldet werden (xbeP_Keyboard): nicht jede Taste hat ein eigenes Event, sondern die Taste wird als Message Parameter 1 zurückgemeldet. Dies gilt für eine Vielzahl von Events, so dass gegebenenfalls die Dokumentation befragt werden muss, wenn der Event-Loop erweitert werden soll.