how to "pause" a delphi application ?

In a delphi application, somewhere I have a loop like this
var
 S:TStringList;
.../...
begin
.../...
for i := 0 to (S.Count - 1) do
 begin
  >> Processing of the S[I] data ...
  >> Display some result adding a line to a TRichEdit on the screen ...
 end;
Now I want to be able to "pause " after each "Display"
If I had a call to the MessageDlg() function, after the Display, of course it pauses but I can't browse into the result on the screen (should it be TRichEdit, TMemo or another component) because the MessageDlg is modal, which is normal
Is there a (completely different, I presume) another way to have my code "waiting" the user after each Display in the loop ?
I think about an instruction after Display that would be "waiting for an event" ... and when the user click on a button on the form, that event occurs and the loop continues for one cycle...
LVL 1
LeTayAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Emmanuel PASQUIERFreelance Project ManagerCommented:
call WaitForEvt in your loop, and SignalEvt in each of the events corresponding to user action you want to resume the loop. You might want to add it also in a timer event
Var
 _Evt:Boolean;

procedure WaitForEvt;
begin
 _Evt:=False;
 Repeat
  Application.HandleMessage;
 until _Evt;
end;

procedure SignalEvt;
begin
 _Evt:=True;
end;

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Geert GOracle dbaCommented:
differenct approach
using a timer and without actually "Pausing" the app

/* just typed in browser, didn't compile in delphi ... :) */

type
  TForm1 = class(TForm)
    ProcessTimer: TTimer;
    btnProcess1: TButton;
    procedure OnProcessTimer(Sender: TObject);
    procedure OnClickbtnProcess1(Sender: TObject);
  private
    fCyclesToGo: integer;
    fCurrentCycle: Integer;
    fProcessData: TStringList;
    procedure ProcessCycle;
    procedure StartProcess;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TForm1.Create(AOwner: TComponent);
begin
  fCyclesToGo := 0;
end;

procedure TForm1.OnProcessTimer(Sender: TObject);
begin
  while fCyclesToGo > 0 do
  begin
    ProcessCycle;
    Dec(fCyclesToGo);
  end;
end;

procedure OnClickbtnProcess1(Sender: TObject);
begin
  // you could provide a slider to set the number of cycles to do :)
  fCyclesToGo := 1;
end;

procedure TForm1.StartProcess;
var
  .../...
begin
  fCurrentCycle := -1;
  .../...
  // put all data to process in fProcessData instead of S
  ProcessTimer.Enabled := True;
end;

procedure TForm1.ProcessCycle;
begin
  Inc(fCurrentCycle);
  if fCurrentCycle < fProcessData.Count then
  begin
    >> Processing of the fProcessData[fCurrentCycle] data ...
    >> Display some result adding a line to a TRichEdit on the screen ...
  end else
    ProcessTimer.Enabled := False;
end;
0
LeTayAuthor Commented:
Hello epasquier
Will the loop
 Repeat
  Application.HandleMessage;
 until _Evt;
not burn all the CPU of the PC ?
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

Ferruccio AccalaiSenior developer, analyst and customer assistance Commented:
I'd use the epasquier way, but using processmessages instead of handlemessages

Form delphi help:

"If the application goes idle, HandleMessage may take a long time to return. Therefore, do not call HandleMessage when waiting for something message-based while priority actions are also being processed. Instead, call ProcessMessages when processing more than just messages."

Using ProcessMessages you will wait just for your switch letting Windows go on with any other thread.
That's prevent also the CPU lack
0
Geert GOracle dbaCommented:
yup it will burn a lot of CPU
and it's of no use with other threads running and returning info for the main thread to process
i wouldn't use ProcessMessages if you have other threads running


with my approach you have a timer doing a sporadic check (you set this, for example to 1 second)
and it only checks a variable

0
Emmanuel PASQUIERFreelance Project ManagerCommented:
> Will the loop [...] not burn all the CPU of the PC ?
> yup it will burn a lot of CPU

Not at all. Application.HandleMessage is going idle if no message is waiting for treatment for the main application thread. That is precisely the right way to have a waiting loop

For Geert : note that I didn't use ProcessMessages but HandleMessage. ProcessMessages IS eating CPU and cause problems with other threads. HandleMessage do NOT.

as stated above, HandleMessage will treat ONE message only at a time, and if none are waiting then the thread will go Idle until one such event needs to be treated. No CPU loss, the thread is suspended until something needs to be done, exactly like in Application.Run (the main loop). Besides, when you call SignalEvt in one of the events, the WaitForEvt loop cease the instant after, not after an undetermined number of ms like when using timers. Therefore your application will have the best responsiveness you can hope for.
0
Geert GOracle dbaCommented:
ok,
but what happens if you call a synchronized procedure from a thread ?
in taht same app
0
Emmanuel PASQUIERFreelance Project ManagerCommented:
@Ferruccio :
"If the application goes idle, HandleMessage may take a long time to return. Therefore, do not call HandleMessage when waiting for something message-based while priority actions are also being processed. Instead, call ProcessMessages when processing more than just messages."

True, if the waiting in Idle is not precisely what you want. What is stated in Delphi Help means that thus HandleMessage CAN go Idle, one such call could take an undetermined time to return (if no events occur). If your application just needs to be sure that all waiting messages are treated (such as components re-paint, timers etc... ) after and before a few lengthy calculations INSIDE a single event, then you put some ProcessMessages along the way to provide a minimum of user interaction within this event. But the thread is not going idle because the purpose is to run the event code in the shortest possible time. So ProcessMessages should not be called in a loop, if that loop is only waiting for a flag to finish, otherwise the CPU will only burn checking again and again the message queue and this flag, doing nothing useful.

@LeTay : just try it, you will see that it's what you wanted :
> I think about an instruction after Display that would be "waiting for an event" ...
> and when the user click on a button on the form, that event occurs and the loop continues for one cycle...
0
Emmanuel PASQUIERFreelance Project ManagerCommented:
> but what happens if you call a synchronized procedure from a thread ?
> in taht same app
I think it will be treated all the same by HandleMessage
See the code of Synchronize, it is just sending a message to the main thread window

and in the TApplication.Run , it is calling HandleMessage in a loop just the same
procedure TThread.Synchronize(Method: TThreadMethod);
begin
  FSynchronizeException := nil;
  FMethod := Method;
  SendMessage(ThreadWindow, CM_EXECPROC, 0, Longint(Self));
  if Assigned(FSynchronizeException) then raise FSynchronizeException;
end;

procedure TApplication.Run;
begin
  FRunning := True;
  try
    AddExitProc(DoneApplication);
    if FMainForm <> nil then
    begin
      case CmdShow of
        SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
        SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
      end;
      if FShowMainForm then
        if FMainForm.FWindowState = wsMinimized then
          Minimize else
          FMainForm.Visible := True;
      repeat
        HandleMessage
      until Terminated;
    end;
  finally
    FRunning := False;
  end;
end;

Open in new window

0
Ferruccio AccalaiSenior developer, analyst and customer assistance Commented:
epasquier, I ever been sure of the opposite.
Thanks for have clarified it to me ;-)
0
Geert GOracle dbaCommented:
well ...
i haven't got to your part yet epasquier, but i rebuilt my approach in delphi :)

i added form1 (unit1) with my approach and a thread which returns messages in the main memo
Unit1.pas
Unit1.dfm
0
Geert GOracle dbaCommented:
ps: i know i didn't put anything in to kill the created threads :)
0
LeTayAuthor Commented:
A very short comment : simple is beautiful !
0
Geert GOracle dbaCommented:
o wow, didn't even wait for the test result

i added epasquier approach in my sample

and ....

you can not close the application until you process everything.
seems that HandleMessage doesn't handle "everything"

this is my sample in the code
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    pnlOptions: TPanel;
    btnProcess: TButton;
    tbCycles: TTrackBar;
    memInfo: TMemo;
    btnStart: TButton;
    timerProcess: TTimer;
    btnStartThread: TButton;
    btnStartEpa: TButton;
    btnNext: TButton;
    procedure btnStartClick(Sender: TObject);
    procedure timerProcessTimer(Sender: TObject);
    procedure btnProcessClick(Sender: TObject);
    procedure btnStartThreadClick(Sender: TObject);
    procedure btnStartEpaClick(Sender: TObject);
    procedure btnNextClick(Sender: TObject);
  private
    fCurrentCycle: integer;
    fCyclesToProcess: integer;
    fProcessData: TStrings;
    fProcessing: boolean;
    procedure ProcessCycle;
    procedure ClearProcess;
    procedure AddMsg(Msg: string);
    procedure CallbackMsg(Msg: String);
    procedure StartThread;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TCallbackEvent = procedure (Msg: string) of object;

  TXThread = class(TThread)
  private
    fMsg: string;
    fOnCallback: TCallbackEvent;
    procedure Callback;
  protected
    procedure Execute; override;
  end;

{ TXThread }

procedure TXThread.Callback;
begin
  if Assigned(fOnCallback) then
    fOnCallback(fMsg);
end;

procedure TXThread.Execute;
var n: integer;
begin
  n := 1;
  while not Terminated and (n < 1000) do
  begin
    fMsg := 'Thread (' + IntToStr(Handle) + ') message ' +  IntToStr(n);
    Synchronize(Callback);
    Sleep(1000);
    Inc(n);
  end;
end;

{ TForm1 }

procedure TForm1.btnProcessClick(Sender: TObject);
begin
  fCyclesToProcess := tbCycles.Position;
  AddMsg(Format('Processing %d cycle(s) of data', [fCyclesToProcess]));
  timerProcess.Enabled := True;
end;

procedure TForm1.ClearProcess;
begin
  fProcessData.Clear;
end;

procedure TForm1.btnStartClick(Sender: TObject);
var
  I: Integer;
begin
  ClearProcess;
  for I := 1 to 20 do
    fProcessData.Add('Process data ' + IntToStr(I));
  btnProcess.Click;
end;

procedure TForm1.btnStartThreadClick(Sender: TObject);
begin
  StartThread;
end;

constructor TForm1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fProcessData := TStringList.Create;
  fCurrentCycle := -1;
  fCyclesToProcess := 0;
end;

destructor TForm1.Destroy;
begin
  FreeAndNil(fProcessData);
  inherited Destroy;
end;

procedure TForm1.timerProcessTimer(Sender: TObject);
begin
  timerProcess.Enabled := False;
  while fCyclesToProcess > 0 do
  begin
    ProcessCycle;
    Dec(fCyclesToProcess);
  end;
  timerProcess.Enabled := fCurrentCycle < fProcessData.Count;
end;

procedure TForm1.AddMsg(Msg: string);
begin
  memInfo.Lines.Add(Msg);
end;

procedure TForm1.ProcessCycle;
begin
  if not fProcessing then
  try
    fProcessing := True;
    Inc(fCurrentCycle);
    if fCurrentCycle < fProcessData.Count then
    begin
      AddMsg(Format('Processing of the "%s"', [fProcessData[fCurrentCycle]]));
      AddMsg(Format('Done processing "%s"', [fProcessData[fCurrentCycle]]));
    end;
  finally
    fProcessing := False;
  end;
end;

procedure TForm1.StartThread;
var X: TXThread;
begin
  X := TXThread.Create(True);
  X.fOnCallback := CallbackMsg;
  X.Resume;
end;

procedure TForm1.CallbackMsg(Msg: String);
begin
  AddMsg(Msg);
end;

var _Evt:Boolean;

procedure WaitForEvt;
begin
  _Evt := False;
  repeat
    Application.HandleMessage;
  until _Evt;
end;

procedure SignalEvt;
begin
  _Evt := True;
end;

procedure TForm1.btnStartEpaClick(Sender: TObject);
var
  I: Integer;
begin
  ClearProcess;
  for I := 1 to 20 do
    fProcessData.Add('Process data ' + IntToStr(I));
  for I := 0 to fProcessData.Count - 1 do
  begin
    AddMsg(Format('Processing of the "%s"', [fProcessData[I]]));
    AddMsg(Format('Done processing "%s"', [fProcessData[I]]));
    WaitForEvt; // click on btnNext
  end;
end;

procedure TForm1.btnNextClick(Sender: TObject);
begin
  SignalEvt;
end;

end.

Open in new window

0
LeTayAuthor Commented:
Hello friends, the discussion between you is nice but with the simple code of epaquier, I am plenty satisfied in the context of my application
0
Geert GOracle dbaCommented:
you tested closing the application ?
0
Emmanuel PASQUIERFreelance Project ManagerCommented:
> you can not close the application until you process everything.
true.
problem is that there is still a process going on in an event

Calling SignalEvt in onCloseQuery AND putting a breaking flag as well to true would be necessary , to abort the inner loop and quit the first message, to return to the Application.Run loop & quit the application.
IF that is what is wanted.

Var
 _ClosingApp:Boolean; // False at startup

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
 _ClosingApp := canClose; // or True
end;

// in your process event :
for i := 0 to (S.Count - 1) do
 begin
// check the 'terminate' flag
  if _ClosingApp Then break;

  >> Processing of the S[I] data ...
  >> Display some result adding a line to a TRichEdit on the screen 
  WaitForEvt;
 end;

OR, if you want to finish the process BUT not wait anymore at each step before closing :

// in your process event :
for i := 0 to (S.Count - 1) do
 begin
  >> Processing of the S[I] data ...
  >> Display some result adding a line to a TRichEdit on the screen 
  if Not _ClosingApp Then WaitForEvt;
 end;

Open in new window

0
Emmanuel PASQUIERFreelance Project ManagerCommented:
I forgot the SignalEvt

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
 SignalEvt;
 _ClosingApp := canClose; // or True
end;

Open in new window

0
Emmanuel PASQUIERFreelance Project ManagerCommented:
in the end, WaitForEvt / SignalEvt method is only the core of a pause functionality that can be used in many cases, but you need to add a few other things to make your app doing what you want regarding the 'breaking' of the main use case.

You might even want to use the Application.Terminated as the _ClosingApp flag, in which case the WaitForEvt is this (which should not hurt whatever you want)
procedure WaitForEvt;
begin
 _Evt:=False;
 Repeat
  Application.HandleMessage;
 until _Evt Or Application.Terminated;
end;

Open in new window

0
Geert GOracle dbaCommented:
>>epasquier, LeTay,
for the HandleMessage
i admit it is in a easy approach

i only tested this for 5 minutes and found 1 problem
are you sure this is the only message it doesn't handle ?
have you tried shutting down windows, with the app running  ?

it's nice to have a discussion like this
we get some pros and cons worked out like this for 2 approaches
0
Emmanuel PASQUIERFreelance Project ManagerCommented:
> are you sure this is the only message it doesn't handle ?
it's not that it's not handling messages, it's just that since you are never returning to the main application loop until the job is done, you won't quit the application until you manage the terminated state 'properly' - this depending on how you see fit.

This would let the process finish if closing the application (main form), just displaying - Closing at the end of the form's title to show the command has been taken into account.
I also put the test in WaitForEvt before the call to HandleMessage, to not let the application going idle if the application is already trying to terminate
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
 if Not _ClosingApp Then
  begin
   _ClosingApp := canClose; // or True
   if _ClosingApp Then Caption:=Caption+' - Closing';
  end;
 SignalEvt;
end;

procedure WaitForEvt;
begin
 _Evt:=False;
 While Not (_Evt Or Application.Terminated) do
  Application.HandleMessage;
end;

Open in new window

0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.