Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Application.ProcessMessages als Warteschleife benutzen - ok?


galagher - Mo 25.05.15 21:16
Titel: Application.ProcessMessages als Warteschleife benutzen - ok?
Hallo!

Ich öffne ein Form mit Show, nicht mit ShowModal, weil man noch auf MainForm zugreifen können soll. Solange dieses sichtbar ist, soll das Programm gewissermassen anhalten, in einer Art Warteschleife verharren:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Form5.Show;
while Form5.Visible do
  Application.ProcessMessages;
//Nach dem Schliessen von Form5 dann weiter:
Application.MessageBox[...]
[...]

Gibt es eine elegantere Möglichkeit oder ist das in Ordnung so? Es funktioniert jedenfalls!


Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Mo 25.05.2015 um 21:59


Delphi-Laie - Mo 25.05.15 21:33

Sog. pollen ist nie elegant, dafür ressourcenfressend und im Zeitalter der Ereignissteuerung auch allermeistens unnötig.

Warum reagierst Du nicht auf das Schließen des Formulares Nr.5 (=Ereignis)?


jaenicke - Mo 25.05.15 22:54

Genau, am besten in Form eines eigenen Ereignisses wie zum Beispiel OnZoom, wenn das Formular z.B. einen Zoom einstellt.

Auf diese Weise kann man Änderungen auch schon während der Anzeige des zweiten Formulars weitergeben, wenn man das möchte. Für das erste Fenster macht das aber keinen Unterschied.


galagher - Di 26.05.15 19:01

user profile iconDelphi-Laie hat folgendes geschrieben Zum zitierten Posting springen:
Warum reagierst Du nicht auf das Schließen des Formulares Nr.5 (=Ereignis)?

Im Form5.OnClose? Was nützt das, der Code in Form1 läuft bei Form5.Show ja trotzdem weiter! Oder meinst du, ich soll den ganzen Code, der auf Form5.Show folgt, in Form5.OnClose reinpacken?

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Genau, am besten in Form eines eigenen Ereignisses wie zum Beispiel OnZoom, wenn das Formular z.B. einen Zoom einstellt.

Hier stelle ich nun die selbe Frage wie oben!


galagher - Di 26.05.15 19:19

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconDelphi-Laie hat folgendes geschrieben Zum zitierten Posting springen:
Warum reagierst Du nicht auf das Schließen des Formulares Nr.5 (=Ereignis)?

Im Form5.OnClose? Was nützt das, der Code in Form1 läuft bei Form5.Show ja trotzdem weiter! Oder meinst du, ich soll den ganzen Code, der auf Form5.Show folgt, in Form5.OnClose reinpacken?

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Genau, am besten in Form eines eigenen Ereignisses wie zum Beispiel OnZoom, wenn das Formular z.B. einen Zoom einstellt.

Hier stelle ich nun die selbe Frage wie oben!


//Edit: HandleMessage - besser?


Delete - Di 26.05.15 19:37

Irgendwie verstehe ich das nicht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Form5.Show;
while Form5.Visible do
  Application.ProcessMessages;
//Nach dem Schliessen von Form5 dann weiter:
Application.MessageBox[...]
[...]

Wenn du Form5 nichtmodal aufrufst und dann anschließend im aufrufenden Form (aF) wartest, bis das Form5 wieder verschwunden ist, kommt das doch auf's gleiche raus, als wenn du Form5 modal aufrufen würdst, oder nicht? Was passiert, wenn der Anwender in aF irgend ein Ereignis, z.B. einen Buttonklick auslöst? Das ganze Konzept scheint mir – zumindest anhand der mageren Infos – etwas merkwürdig :gruebel:

Und weil das Ereignis OnClose bereits existiert, kannst du in aF diesem Ereignis eine Methode von aF zuweisen: Form5.Close := AfterCloseOfForm5;
In AfterCloseOfForm5 schreibst du das rein, womit bei deiner jetzigen Version gewartet werden soll, bis Form5 geschlossen wird.


jaenicke - Di 26.05.15 20:06

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Oder meinst du, ich soll den ganzen Code, der auf Form5.Show folgt, in Form5.OnClose reinpacken
Ja, genau. So funktioniert ereignisorientierte Programmierung. Der Code kommt immer dahin, wo etwas passiert ist.
Wenn du einen Button gedrückt hast, kommt der Code dahin. Wenn in einem Formular etwas passiert ist, das ein Ereignis auslöst, kommt der Code im aufrufenden Formular dort rein.


galagher - Di 26.05.15 20:14

user profile iconPerlsau hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du Form5 nichtmodal aufrufst und dann anschließend im aufrufenden Form (aF) wartest, bis das Form5 wieder verschwunden ist, kommt das doch auf's gleiche raus, als wenn du Form5 modal aufrufen würdst, oder nicht?

Ich warte nicht einfach das Schliessen von Form5 ab, sondern mache was in aF! Währenddessen soll Form5 aber offen bleiben, weil ich auch da etwas machen können soll.
Also: aF öffnet Form5, dabei sollen beide auf Ereignisse (ButtonClick, Texteingabe usw.) reagieren können, ohne dass der Code, der in aF nach Form5.Show steht, weiterläuft. Das soll erst geschehen, nachdem Form5 geschlossen wurde.

user profile iconPerlsau hat folgendes geschrieben Zum zitierten Posting springen:
Und weil das Ereignis OnClose bereits existiert, kannst du in aF diesem Ereignis eine Methode von aF zuweisen: Form5.Close := AfterCloseOfForm5;
In AfterCloseOfForm5 schreibst du das rein, womit bei deiner jetzigen Version gewartet werden soll, bis Form5 geschlossen wird.

Gut, aber währenddessen läuft in aF der Code ja weiter! Genau das will ich ja nicht!

Zu Application.ProcessMessages und Application.HandleMessage: Wenn man im Taskmanager schaut, ist letzteres besser: 51% CPU-Auslastung zu 25%.


galagher - Di 26.05.15 20:20

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Ja, genau. So funktioniert ereignisorientierte Programmierung. Der Code kommt immer dahin, wo etwas passiert ist.
Wenn du einen Button gedrückt hast, kommt der Code dahin.

Ist zu überdenken, allerdings stehen dann in Form5 auschliesslich Anweisungen, die sich alle auf Form1 (die aufrufende Form) beziehen.

Ok, hier der wesentliche Teil des entsprechenden Codes:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
    //Form1
    case MessageDialog(
           'Irgendein Text ...',
           'Hinweis',
           mtINFORMATION,
           [mbOK, mbCancel, mbIGNORE], '',
           bsDialog, [biSystemMenu], clBtnFace, clWindowText, -1, -1,
           '',
           0'')
      of
      mrOK:
        begin
          Form5.Show;  //Sowohl Form1 als auch Form5 sollen benutzbar sein!
          while Form5.Visible do
            Application.HandleMessage;  {Warten, bis Form5 geschlossen ist, dann --> ...}
        end;
      mrCancel:
        begin
          exit;
        end;  
    end;
  end;  {case}

  //Form1
  {... --> hier weiter}
  case Application.MessageBox(
         'Irgendein Text ...'),
         'Bestätigen',
         mb_YESNO or mb_ICONQUESTION or mb_DEFBUTTON1)
    of
    mrNO:
      begin
        exit;
      end;
  end;  {case}

//Hier kommt dann noch mehr Code


Delete - Di 26.05.15 20:31

- Nachträglich durch die Entwickler-Ecke gelöscht -


Xion - Di 26.05.15 20:32

Mit deiner jetzigen Konstruktion kannst du sehr unschöne Effekte erzeugen. Im Prinzip kann dein Programm nun multithreaded erscheinen. Ein Beispiel:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure Button1Click(Sender: TObject);
begin
  if CheckBox1.checked then
    begin
      Form5.Show;
      while Form5.Visible do
        Application.ProcessMessages;
      CheckBox1.checked := not CheckBox1.checked;
    end;
end;


Man sollte meinen, dieser Code setzt in Zeile 8 die Checkbox auf false. Das muss aber nicht sein, denn der Nutzer kann in Zeile 7 "eingreifen". Noch wilder wird es, wenn der Nutzer ein weiteres mal auf Button1 klickt. Dann wird die ganze Funktion ein weiteres mal aufgerufen, wobei z.B. Zeile 5 keinen Effekt hat. Wird Form5 dann geschlossen, so wird Zeile 8 zweimal ausgeführt!

Natürlich sehe ich aber auch den Nachteil von ShowModal, nämlich dass man dann im alten Fenster etwa nichts mehr herauskopieren oder nachschauen kann.
Es gibt sicher Situationen, in denen dein Ansatz Sinn macht (z.B. bei einer sehr rechenintensiven Operation, die pausiert und durch Form5 parametrisiert werden soll). Überlege aber gut, wo du die while-Schleife platzierst und ob Eingriffe vom Nutzer keinen Schaden anrichten können. Im obigen Beispiel könnte man auch einen Boolean nutzen, um den Eintritt in die Funktion zu verbieten, sollte diese bereits in Ausführung sein.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure Button1Click(Sender: TObject);
begin
  if not Button1ClickRunning then
    begin
      Button1ClickRunning  := true;
      if CheckBox1.checked then
        begin
          Form5.Show;
          while Form5.Visible do
            Application.ProcessMessages;
        //ungünstige Operation: CheckBox1.checked := not CheckBox1.checked;
          Button1ClickRunning   := false;
        end;
    end;
end;


In den allermeisten Fällen aber ist, wie schon gesagt wurde, ereignisgesteuerte Programmierung besser geeignet. Du teilst deine jetzige Funktion an der Stelle, an der du die Warte-Schleife einbauen wolltest, auf. Den ersten Teil (vor dem while) belässt du an der Stelle, den zweiten Teil rufst du z.B. in Form5.OnClose auf. Dann muss nicht aktiv gewartet werden, der Programmfluss wird nicht blockiert und es können keine scheinbaren Nebenläufigkeiten entstehen (z.B. dass, wie im obigen Beispiel, die Funktion zweimal Zeile 8 durchläuft). Auch ist es dem Entwickler zu einem späteren Zeitpunkt noch klar, dass zwischen den beiden Funktionen Nutzerinteraktionen auftreten können.


Xion - Di 26.05.15 20:38

In deiner konkreten Anwendung würde ich so vorgehen:

Mit Anzeigen von Form5 wird eine Variable gesetzt. Diese enthält Informationen, was nach dem Beenden von Form5 geschehen soll.

Im einfachsten Fall etwa so:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
      mrOK:
        begin
          Form5.JobForOnClose := ocjDummyJob;
          Form5.Show;  //Sowohl Form1 als auch Form5 sollen benutzbar sein!
        end;
[...]
procedure Form5.OnClose();
begin
  if JobForOnClose = ocjDummyJob then
     doSomething();
end;


Man könnte nun auch noch Callback-Funktionen definieren, wenn man es ganz sauber haben möchte. Die Benutzung sähe dann in etwa so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
      mrOK:
        begin
          Form5.JobForOnClose := @MyDummyJob;
          Form5.Show;  //Sowohl Form1 als auch Form5 sollen benutzbar sein!
        end;
[...]
procedure MyDummyJob();
begin
   doSomething();
end;


galagher - Di 26.05.15 20:42

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
Mit deiner jetzigen Konstruktion kannst du sehr unschöne Effekte erzeugen. Im Prinzip kann dein Programm nun multithreaded erscheinen.



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure Button1Click(Sender: TObject);
begin
  if CheckBox1.checked then
    begin
      Form5.Show;
      while Form5.Visible do
        Application.ProcessMessages;
      CheckBox1.checked := not CheckBox1.checked;
    end;
end;


user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
Noch wilder wird es, wenn der Nutzer ein weiteres mal auf Button1 klickt.

Ja, das habe ich nicht abgesichert! Kann ich aber mit wenig Aufwand nachrüsten.

user profile iconXion hat folgendes geschrieben Zum zitierten Posting springen:
In den allermeisten Fällen aber ist, wie schon gesagt wurde, ereignisgesteuerte Programmierung besser geeignet. Du teilst deine jetzige Funktion an der Stelle, an der du die Warte-Schleife einbauen wolltest, auf.

Diese Option halte ich mir immer nach wir vor offen!


Delete - Di 26.05.15 21:53

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Ich warte nicht einfach das Schliessen von Form5 ab, sondern mache was in aF! Währenddessen soll Form5 aber offen bleiben, weil ich auch da etwas machen können soll. Also: aF öffnet Form5, dabei sollen beide auf Ereignisse (ButtonClick, Texteingabe usw.) reagieren können, ohne dass der Code, der in aF nach Form5.Show steht, weiterläuft. Das soll erst geschehen, nachdem Form5 geschlossen wurde.

Und genau aus diesem Grund nimmst du das, was in aF nicht mehr weiterlaufen soll, aus der Methode, in der Form5 aufgerufen wird, heraus und packst es in eine eigene Methode. Dann ist in der erstgenannten Methode der letzte Befehl der, der Form5 aufruft.

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconPerlsau hat folgendes geschrieben Zum zitierten Posting springen:
Und weil das Ereignis OnClose bereits existiert, kannst du in aF diesem Ereignis eine Methode von aF zuweisen: Form5.Close := AfterCloseOfForm5; In AfterCloseOfForm5 schreibst du das rein, womit bei deiner jetzigen Version gewartet werden soll, bis Form5 geschlossen wird.

Gut, aber währenddessen läuft in aF der Code ja weiter! Genau das will ich ja nicht!

Nein. Wenn du den Code, der in aF nicht nach dem Öffnen von Form5 weiterlaufen soll, in eine eigene Methode packst. Diese Methode rufst du erst dann auf, wenn Form5 geschlossen wird, und zwar ereignisgesteuert.

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Zu Application.ProcessMessages und Application.HandleMessage: Wenn man im Taskmanager schaut, ist letzteres besser: 51% CPU-Auslastung zu 25%.

Bei meiner Methode hättest du gar keine CPU-Auslastung, so lange der Anwender kein Ereignis auslöst. So sollte es auch sein, es sei denn, es laufen noch weitere Threads in deinem Programm.

Ich vermute mal, dir ist die Vorstellung von Ereignissen, die man in einer Unit2 deklarieren kann und in einer Unit1 einer Methode zuweist, nicht geläufig. Deshalb erkläre ich's dir noch einmal ganz ausführlich. Zuerst die Unit5, die Form5 enthält:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
unit Unit5;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls;

// oder ab XE oder so
// uses
//   Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
//   Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;


Type
  TCloseFormEvent = Procedure of Object// hier deklarierst du einen neuen Typ, das ist dein Event-Typ

type
  TForm5 = class(TForm)

    procedure FormClose(Sender: TObject; var Action: TCloseAction);

  private { Private-Deklarationen }
    fCloseFormEvent : TCloseFormEvent;

  public { Public-Deklarationen }
    Property OnCloseFormEvent  : TCloseFormEvent read fCloseFormEvent write fCloseFormEvent;
  end;

var
  Form5: TForm5;

implementation

{$R *.dfm}

procedure TForm5.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  // Hier löst du das Ereignis aus
  If Assigned(fCloseFormEvent) Then fCloseFormEvent;
end;

end.


Und nun zu aF, bei mir UnitMain bzw. FormMain:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
unit UnitMain;

interface

uses
  ..., Unit5;

type
  TFormMain = class(TForm)
    ...
    procedure Btn_Form5Click(Sender: TObject);
    procedure FormShow(Sender: TObject);


  private { Private-Deklarationen }
    Procedure AfterCloseOfForm5;

  public  { Public-Deklarationen  }
  end;

var
  FormMain: TFormMain;

implementation
{$R *.dfm}
{ TFormMain }

// hier platzierst du das, was erst nach dem Schließen von Form5 ausgeführt werden soll
procedure TFormMain.AfterCloseOfForm5;
begin
  ShowMessage('Form5 wurde soeben geschlossen');
end;

// Hier weist du dem Event aus Form5 die obige Methode zu
procedure TFormMain.FormShow(Sender: TObject);
begin
  Form5.OnCloseFormEvent := AfterCloseOfForm5;
end;

// Hier rufst du Form5 auf (ja ich weiß, überflüssiger Kommentar)
procedure TFormMain.Btn_Form5Click(Sender: TObject);
begin
  Form5.Show;
end;

end.


galagher - Di 26.05.15 22:11

user profile iconPerlsau hat folgendes geschrieben Zum zitierten Posting springen:
Deshalb erkläre ich's dir noch einmal ganz ausführlich. Zuerst die Unit5, die Form5 enthält:

Sieht so aus, als würde ich das glatt so machen! :mrgreen: Gefällt mir besser, der Code von aF bleibt vor allem auch in aF! So soll's sein! Ich seh's mir morgen an!


jaenicke - Di 26.05.15 22:16

Genau so meinte ich das, ja. Ein Beispiel konnte ich am Handy leider nicht schreiben. Aber da war ja Perlsau zur Stelle. ;-)


Delete - Di 26.05.15 22:19

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
... Aber da war ja Perlsau zur Stelle. ;-)

... der diesmal offenbar keine Perlen vor die Säue warf :D


Delete - Mi 27.05.15 10:42

user profile icongalagher hat folgendes geschrieben Zum zitierten Posting springen:
Hallo!

Ich öffne ein Form mit Show, nicht mit ShowModal, weil man noch auf MainForm zugreifen können soll. Solange dieses sichtbar ist, soll das Programm gewissermassen anhalten, in einer Art Warteschleife verharren:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
Form5.Show;
while Form5.Visible do
  Application.ProcessMessages;
//Nach dem Schliessen von Form5 dann weiter:
Application.MessageBox[...]
[...]

Gibt es eine elegantere Möglichkeit oder ist das in Ordnung so? Es funktioniert jedenfalls!


Besser ist es, noch ein Sleep(10) einzufügen:
Ohne Sleep(10) ist die CPU-Last bei meiner QUAD-CPU 13%,
mit Sleep(10) ist die CPU-Last 0%.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
...
while Form5.Visible do
begin
Sleep(10); Application.ProcessMessages;
end;
...


Narses - Mi 27.05.15 13:23

Moin!

Von der Logik her ist der Thread hier an sich schon ein Design-Fehler: in der Ereignisorientierung kann es keine Warteschleifen geben, "Warten" ist kein Ereignis. :nut:

Aber ganz speziell darüber hinaus:
user profile iconhathor hat folgendes geschrieben Zum zitierten Posting springen:
Besser ist es, noch ein Sleep(10) einzufügen:
Es gibt keinen nachvollziehbaren Grund in einer ereignisorientierten Anwendung überhaupt Sleep() einzusetzen, einzige Ausnahme: in einem Thread. :nixweiss: Es ist IMHO ein ganz krasser Designfehler, wenn man ohne sowas nicht auskommt (oder man hat das Konzept hinter Ereignisorientierung nicht verstanden). :!:

cu
Narses