Event-Loop

Aus Wiki des Deutschsprachige Xbaseentwickler e. V.
Zur Navigation springen Zur Suche springen

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 aufgebau 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.