Event-Loop

Eigentlich ist mir die Frage peinlich, aber es kann sonst niemand helfen ... :)

Moderator: Moderatoren

Antworten
Benutzeravatar
Jan
Marvin
Marvin
Beiträge: 14651
Registriert: Fr, 23. Sep 2005 18:23
Wohnort: 49328 Melle
Hat sich bedankt: 21 Mal
Danksagung erhalten: 88 Mal
Kontaktdaten:

Event-Loop

Beitrag von Jan »

Mal 'ne richtige Anfängerfrage: Wo und in welchen Fällen setzt man in einer GUI diese allseits bekannte Schleife ein?

Code: Alles auswählen

DO WHILE nEvent <> xbeP_Close
   nEvent := AppEvent(@mp1, @mp2, @oXbp)
   oXbp:handleEvent(nEvent, mp1, mp2)
ENDDO
Was exakt macht die eigentlich? Normalerweise sieht die ja immer so aus, aber ich habe auch schon erweiterte gesehen.

Jan
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15695
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Hallo Jan,

das ist die Schleife, die dein Programm steuert.
Unter Clipper wäre da inkey(0) gestanden ...

nEvent speichert den aufgetretenen Event, den AppEvent() zurückgibt.
do while nEvent .... enddo läuft also in einer Schleife, bis der Event xbeP_Close auftritt (z.B. weil das kleine X oben rechts angeklickt wurde)
In der Schleife wartet das Programm in dieser Zeile nEvent := AppEvent(@mp1, @mp2, @oXbp) (kein 4. Parameter, warten bis Event) bis ein Ereignis aufgetreten ist. Das kann eine Mausbewegung, ein Tastendruck, ein Mausklick oder sonst was sein.
Das ermittelte Ereignis steht dann in nEvent, das auslösende Control/Fenster wird an oXbp übergeben und eventuelle Parameter stehen in mp1 und mp2. Daher auch die @ Parameterübergabe per Referenz.
Die folgende Zeile nun oXbp:handleEvent(nEvent, mp1, mp2) beauftragt das auslösende Controll seinen Eventhandler aufzurufen, d.h. darauf zu reagieren.

Wenn nun bei dieser Reaktion ein sehr lange laufende Funktion oder Prozedur aufgerufen wird, wartet die Ereignisschleife bis diese abgearbeitet ist, bevor der nächste Event verarbeitet wird.

Dieser Zustand eines Programmes nennt man einfrieren, es reagiert auf nichts mehr. Daher sollte man lange laufende Programmteile möglichst in eigene Threads auslagern und die aufrufende Funktion schnell beenden.

Lange Druck- oder Suchaufgaben können dazu gehören, wobei manchmal die Aufteilung nicht möglich ist. Dann muss man in Schleifen ab und zu die Eventverarbeitung aufrufen.

Pro Thread sollte man normalerweise nur eine EventLoop haben, ich habe diese deshalb in eine Funktion gelegt und kann diese einfach aufrufen.
Wenn es irgendwie geht, sollten Funktionen in einem GUI Programm möglichst kurz sein und schnell die Kontrolle zurückgeben.
Gruß
Hubert
Benutzeravatar
Jan
Marvin
Marvin
Beiträge: 14651
Registriert: Fr, 23. Sep 2005 18:23
Wohnort: 49328 Melle
Hat sich bedankt: 21 Mal
Danksagung erhalten: 88 Mal
Kontaktdaten:

Beitrag von Jan »

Moin Hubert,

danke für Deine ausführliche Antwort.

Das meiste davon war mir schon irgendwie klar. Insbesondere das Auswerten aller Ereignisse.

Stutzig macht mich allerdings Deine Aussage, das es "normalerweise" nur einen EventLoop je Thread geben sollte. Was heißt "normalerweise"? Bzw. wo liegen die Ausnahmen?

Nicht wirklich verstehe ich Deine Ausage, man sollte in langen Such- oder Druckroutinen ab und an die Eventverarbeitung aufrufen. Wenn ich Dich richtig verstanden habe unterbreche ich damit kurz den Vorgang und gebe einem anderen Ereignis mal Luft zum Laufen. Aber wie rufe ich die Eventverarbeitung mal eben so zwischendurch auf?

Und in welchen Fällen erweitert man die Schleife durch eigene Ergänzungen? Oder ist das prinzipiell schlechter Stil und man sollte das anders lösen?

Jan
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15695
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Jan hat geschrieben:Was heißt "normalerweise"? Bzw. wo liegen die Ausnahmen?
Ich habe gelesen, dass man sich viel Ärger einhandeln kann, wenn das Programm in mehreren Eventloops hängt. Allerdings nutze ich z.B. die About-Box von Alaskas Beispielen oder andere MODALE Eingabe Hilfsbildschirme. Bei Modalen Hilfsbildschirmen kann man - nach meiner Erfahrung - eine eigene Eventloop genau auf die Bedürfnisse dieses Fenster einbauen und somit auch das restliche Programm automatisch blockieren (was bei MODALEN Fenstern ja gewünscht ist).
Allgemein würde ich es so formulieren. Lass es, außer du weißt genau was du tust und warum du es so brauchst.
Jan hat geschrieben:Nicht wirklich verstehe ich Deine Ausage, man sollte in langen Such- oder Druckroutinen ab und an die Eventverarbeitung aufrufen. Wenn ich Dich richtig verstanden habe unterbreche ich damit kurz den Vorgang und gebe einem anderen Ereignis mal Luft zum Laufen.
Genau so ist es, aber natürlich muss man alle Controlls des Programms blockieren (:disable() ), die nicht gleichzeitig laufen dürfen, bevor man diese Verarbeitung aufruft !
Was bleibt, sind z.B. der Refresh von Inhalten auf der Hauptseite oder z.B. Menüaufrufe um Drucker zu wechseln etc.
Jan hat geschrieben:Aber wie rufe ich die Eventverarbeitung mal eben so zwischendurch auf?
Ich mache das so...

Code: Alles auswählen

im Hauptprogramm statt der do while Schleife die Funktion ohne Parameter starten ...

DoEventLoop()  // Endlose Ausführung ...

in langen Schleifen, z.B. 
    Do while ! eof()
         DoEventLoop(0.01)  // 1/100 Sekunde reicht für viele Aktionen...

Wenn ich in einem Programm einfach nur etwas stoppen will, aber die Oberfläche arbeiten soll
     DoEventLoop(1)  // z.B. geänderte Captions anzeigen ...

*-----------------------------------------------------------------------------
FUNCTION DoEventLoop(nSeconds)               // For Next Schleifen unterbrechen und Events verarbeiten !
   local nBisSeconds, nEvent, mp1, mp2, oXbp

   DEFAULT nSeconds to 0                     // Standard ist endlose Ausführung

   nBisSeconds := seconds() + nSeconds

   DO WHILE .T.
      nEvent := AppEvent( @mp1, @mp2, @oXbp, nSeconds)
      do case
         case nEvent = xbe_None
            * nichts tun ist hier Standard
         case nEvent = xbeP_Keyboard .and. mp1 == xbeK_F1
                       * xbeP_HelpRequest erscheinen für jede Xbp Instanz, also zu oft !
                       MsgBox("Die Hilfe ist für dieses Programm noch nicht erstellt.")
         otherwise
              oXbp:handleEvent( nEvent, mp1, mp2 )
      endcase
      if nSeconds > 0 .and. nBisSeconds < seconds() // nur wenn keine endlose Ausführung
         exit
      endif
   ENDDO
return nil
Nochmals der Hinweis, wenn es irgendwie geht, sollte man solche langen Programmteile in eigene Threads auslagern, dann kümmert sich das BS um diese Probleme !
Jan hat geschrieben:Und in welchen Fällen erweitert man die Schleife durch eigene Ergänzungen? Oder ist das prinzipiell schlechter Stil und man sollte das anders lösen?
In meinem Beispiel oben habe ich der Taste F1 meine eigene Hilfsfunktion zugeordnet (anders als es Windows normalerweise macht). Auch könnte man spezielle Hotkeys belegen.

Es ist erlaubt !

Aber gefährlich, wenn man vergisst das diese Schleife viele Millionen Mal je Sekunde durchlaufen wird. Eine saubere Do Case Abfrage mit einigen Möglichkeiten merkt man nicht, wenn aber hier zu viel steht, wird das Programm ausgebremst. Wie gesagt, jeder Mausklick, jede Mausbewegung, jeder Tastaturanschlag muss hier durch !!!
Gruß
Hubert
Benutzeravatar
brandelh
Foren-Moderator
Foren-Moderator
Beiträge: 15695
Registriert: Mo, 23. Jan 2006 20:54
Wohnort: Germersheim
Hat sich bedankt: 65 Mal
Danksagung erhalten: 33 Mal
Kontaktdaten:

Beitrag von brandelh »

Eine Sache sollte man vielleicht nochmal deutlich herausstellen:

Windowsprogramme (eigentlich alle GUI Programme) verhalten sich ereignisorientiert (event driven). Das heist, dass der Programmierer nur alle nötigen Funktionen zur Verfügung stellen kann. Diese sollten möglichst schnell fertig sein oder in eigenen Threads liegen. Was wann ausgeführt wird, wird nicht in diesen Programmteilen geregelt (mal abgesehen, dass man Plausis prüfen kann).
Der Aufruf eines Programmteiles wird normalerweise einem Tastendruck oder Mausklick oder einem sonstigen Ereignis zugeordnet (z.B. Activate-Codeblock). Der Benutzer entscheidet wann er wo draufklickt.

Falls nun aber Aktionen voneinander abhängen oder sich gegenseitig ausschließen, muss der Programmierer diese Controls disablen(), die gerade nicht erlaubt sind. Er muss also schon den Aufruf blockieren.

Zumindest in der Theorie. Ab und zu führt es - besonders bei komplexen Abhängigkeiten - zu Frust, wenn ich als Anwender zwar weiß, dass ich nun den Button XYZ drücken müsste, dieser aber immer disabled oder noch schlimmer versteckt (hide) ist. Auch kann man es bei TabControls kaum verhindern, dass der Anwender wild von einer Seite auf die andere klickt.

Daher lasse ich in solchen Situationen auch den Click auf einen Button zu, prüfe in der Funktion ob alle Plausis erfüllt sind und gebe im Fehlerfalle eine msgbox mit der Fehlermeldung aus, warum es zur Zeit nicht geht.

Hier ist auch ein bischen Einfühlungsvermögen in die Anwender der Anwendung gefragt.
Gruß
Hubert
Benutzeravatar
Rolf Ramacher
Der Entwickler von "Deep Thought"
Der Entwickler von "Deep Thought"
Beiträge: 1930
Registriert: Do, 09. Nov 2006 10:33
Wohnort: Bergheim
Danksagung erhalten: 3 Mal
Kontaktdaten:

Maskengestaltung

Beitrag von Rolf Ramacher »

Hallo Jan,


ich mache dies in ähnlicher Form. Wenn ich aus einer Maske eine Funktion
aufrufe, wird das vorherige Fenster (parent) gesperrt - disable().

Aber du kannst ja auch wenn du es möchtes dann das Parent-Fenster schliessen mit PostAppEvent(xbep_Close). Wenn du es nach dem Beenden
der Funktion wieder benutzen möchtest und die hattest es disable().

dann wieder enable() und SetAppFocus setzen.
Gruß Rolf

Mitglied der Gruppe XUG-Cologne
www.xug-cologne.de
Antworten