Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Semi-Modalen Dialog erstellen (https://www.delphipraxis.net/209042-semi-modalen-dialog-erstellen.html)

BigAl 18. Okt 2021 06:50

Semi-Modalen Dialog erstellen
 
Hallo zusammen,

ich benötige immer wieder einen Fortschrittsdialog wie "Aktion läuft, bitte warten" etc. Dafür habe ich ein Formular erstellt bei dem ich bei Bedarf einen Fortschrittsbalken einblenden oder einen Abbruch-Button anzeigen kann. Das ganze wird dann über einen managed record gewrappt, damit der Dialog einfach verwendet werden kann. Im einfachsten Fall einfach den Dialog als lokale Variable definieren und die Updates aufrufen. Beim verlassen des Gültigkeitsbereich wird dann automatisch aufgeräumt...

Bei den Updates (Fortschrittstext, Fortschrittsbalken) wird dann jeweils ein "Application.ProcessMessages" durchgeführt, damit z.B. der Abbruch-Button aktualisiert wird und auch Windows mitbekommt, dass die Anwendung noch am Leben ist. Hier kommt aber schon das Problem. Der Dialog sollte eigentlich den Rest der Applikation sperren, also Modal sein. Allerdings soll das Programm, welches den Dialog nutz weiter ausgeführt werden. ShowModal fällt also flach, da dann die Kontrolle an den Dialog geht und das aufrufende Programm dann warten muss bis der Dialog beendet ist.

Wie löst ihr sowas in VCL?

Alex

hoika 18. Okt 2021 06:57

AW: Semi-Modalen Dialog erstellen
 
Hallo,
benutze die Suchfunktion des Forums.

https://www.delphipraxis.net/79498-i...showmodal.html

BigAl 18. Okt 2021 07:25

AW: Semi-Modalen Dialog erstellen
 
Zitat:

Zitat von hoika (Beitrag 1496200)
Hallo,
benutze die Suchfunktion des Forums.

https://www.delphipraxis.net/79498-i...showmodal.html

Hast ja recht :-) Habe aber entweder die falschen Suchparameter eingegeben oder war zu dämlich... Damit dieser Thread aber nicht ganz nutzlos bleibt hier die Lösung die ich mir jetzt zusammengesucht habe:

TfrmProgress:
Delphi-Quellcode:
  TfrmProgress = class(TForm)
  private
    FWindowList: Pointer;
  protected
    procedure DoShow; override;
    procedure DoHide; override;
  end;
Und dann implementiert wie folgt:
Delphi-Quellcode:
procedure TfrmProgress.DoShow;
// Make the dialog "semi modal".
begin
  FWindowList := DisableTaskWindows(Application.Handle);
  inherited;
end;

procedure TfrmProgress.DoHide;
// Ensure the application gets back the control...
begin
  inherited;
  if Assigned(FWindowList) then
  begin
    EnableTaskWindows(FWindowList);
    FWindowList := nil;
  end;
end;
Funktioniert hervorragend. Genau was ich wollte.

BerndS 18. Okt 2021 07:42

AW: Semi-Modalen Dialog erstellen
 
Ich habe das bei mir so gelöst, dass ich einen modalen Dialog anzeige, während die Arbeit in einen Thread gemacht wird.
Der Thread wird dem Dialog übergeben um über den Abbrechen- Schalter den Thread terminieren zu können.
Gestartet wird den Thread im OnShow.
Der Thread selber schließt den Dialog über OnTerminate.

Der Thread selber führt im Execute eine Klassenmethode aus.
Der Vorteil ist, dass die Anwendung nicht blockiert, wenn es mal etwas länger dauert.
Gerade wenn die nicht klar ist, wie schnell z.B. eine Antwort von einem Server kommt oder es zu einem Timeout kommen kann, ist das von Vorteil.

Das ist sicher etwas aufwendiger als dir vorherige Lösung, zumal hier auch eine eigene Fehlerbehandlung gemacht werden muss.

BigAl 18. Okt 2021 07:48

AW: Semi-Modalen Dialog erstellen
 
Hallo Bernd,

Du hast natürlich recht. Solch eine Lösung habe ich auch, aber die ist immer mit mehr Aufwand verbunden. Diese nutze ich ich eigentlich immer nur dann, wenn ich vom übergeordneten Programm kein zyklisches Update sicherstellen kann. Das ist aber sehr selten der Fall, deshalb die zweite Lösung. Hat halt den Vorteil, dass das ganze - speziell mit dem managed record - sehr einfach in der Handhabung ist:

Delphi-Quellcode:
var
  Progress: TProgress;
begin
  for var I := 0 to 1000 do
  begin
    Progress.UpdateProgress(Format('Progress: %.1f', [I / 10]), I / 1000);
    if Progress.Canceled then
      Break;
    Sleep(10);
  end;
end;
Und das könnte man noch weiter optimieren wenn man z.B. das "UpdateProgress" als Funktion macht, welche den Status von "Canceled" zurück gibt... Die managed records waren für mich wirklich ein riesiger Sprung nach vorne und lassen mich C++ doch etwas weniger vermissen :-).

BerndS 18. Okt 2021 08:07

AW: Semi-Modalen Dialog erstellen
 
Hallo Alex,
ich habe das auch nicht überall konsequent so gemacht.
Durch das gelegenliche aufrufen von Application.ProcessMessages wirk dann die Animation von TProgessbar etwas hackelig.

BigAl 18. Okt 2021 08:19

AW: Semi-Modalen Dialog erstellen
 
Das mit dem "hakelig" hängt von der Anwendung ab, bzw. wie oft man es aufruft. Aber es lässt sich halt sehr gut der tatsächlich Fortschritt anzeigen, da man absolut synchron zu der Verarbeitung ist. Üblicherweise sind die Update-Raten bei mir auch sehr hoch. Da wirke ich dann normalerweise damit entgegen, dass ich nur alle 250ms oder so eine tatsächliche Aktualisierung durchführe. Ansonsten verbraucht der Dialog selbst wieder zu viel Rechenzeit.

Eine Idee wäre natürlich nur das "ProcessMessages" intern Thread-gesteuert zu machen. Dann wäre der Dialog immer gut bedienbar, auch wenn mal eine Sekunde kein Update vom Hauptprogramm kommt. Modal ist er ja jetzt, was keine unerwünschten Nebeneffekte mehr zulassen sollte. Auch sollte man das nicht zu oft machen (s.o.), da "ProcessMessages" tweilweise doch relativ viel Rechenzeit in Anspruch nimmt. Allerdings ist das "Cancel" bei mir eh nur für Ausnahmesituationen, welche hoffentlich nie benötigt werden...

dummzeuch 18. Okt 2021 08:37

AW: Semi-Modalen Dialog erstellen
 
Nur so am Rande bemerkt: Wenn man Application.ProcessMessages zu oft aufruft, kann das die Verarbeitung extrem verlangsamen.

Ein ex-Kollege von mir (Hi Daniel) hat das mal in eine TimedProcessMessages Funktion gekapselt, die sich merkt, wie lange der letzte Application.ProcessMessages Aufruf her ist und nur dann, wenn er mehr als n Millisekunden (200?) her ist, diese Methode wirklich aufruft. Die Umstellung auf diese Aufrufe hat bei einigen Programmen die Verarbeitung enorm beschleunigt (Faktor 5 bis 10).

Über eine ähnliche Vorgehensweise, und warum man das machen oder nicht machen sollte, habe ich mal geblogt in "Calling Application.ProcessMessages in a Delphi program"
(2018? Unglaublich, dass das schon wieder so lange her ist.)

BigAl 18. Okt 2021 08:44

AW: Semi-Modalen Dialog erstellen
 
Zitat:

Zitat von dummzeuch (Beitrag 1496206)
Nur so am Rande bemerkt: Wenn man Application.ProcessMessages zu oft aufruft, kann das die Verarbeitung extrem verlangsamen.

Das ist ja dass, was ich oben geschrieben habe. ProcessMessages (sowie die gesamte Aktualisierung der Ausgabe) wird bei mir nur in maximal vorgegebenen Intervallen aufgerufen. Üblicherweise habe ich das 250 ms oder 500 ms drin. Glücklicherweise speichert ja Windows die Botschaften in einer Queue, was dann das Drücken der Taste trotzdem erfasst, auch wenn im ersten Moment kein visuelles Feedback kommt...

BerndS 18. Okt 2021 08:57

AW: Semi-Modalen Dialog erstellen
 
Ich leite alle Dialoge immer von einer eigenen Basisklasse ab.
Hier mal ein vereinfachtes Beispiel für diesem Zweck:
Delphi-Quellcode:
 
TBasicForm = class(TForm)
private
  FStart: Int64;
public
  procedure AntiFreeze; // ruft nur alle 50ms Application.ProcessMessages;
end;

...
procedure TBasicForm .AntiFreeze;
begin
  if FStart = 0 then
    FStart := GetTickCount
  else
    if GetTickCount - FStart > 50 then
    begin
      FStart := GetTickCount;
      Application.ProcessMessages;
    end;
end;
50 Millisekunden habe ich gewählt, damit TProgressbar noch einigermaßen flüssig bleibt.

Bei allem was ich neu schreibe, verwendet das aber nicht mehr.


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:09 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz