In Delphi XE7 (or XE8), put a TjvProgressDialog (from JVCL) on a form and name it dlgProgress1. And a TButton and name it btnProgressDialogTest.
This code first starts Notepad from a separate thread (ShellExecAndWaitTask), then it opens the progress dialog (dlgProgress1) with an infinite progress loop:
var
ShellExecAndWaitTask: System.Threading.ITask;
procedure TForm1.btnProgressDialogTestClick(Sender: TObject);
begin
ShellExecAndWaitTask := TTask.Create(
procedure
begin
JclShell.ShellExecAndWait('notepad'); // BTW, is this thread-safe?
CodeSite.Send('Notepad has been closed');
end);
ShellExecAndWaitTask.Start;
dlgProgress1.Caption := 'ProgressDialog Test';
dlgProgress1.Text := 'Close Notepad to automatically close this progress dialog';
dlgProgress1.Tag := 0;
dlgProgress1.Position := 0;
dlgProgress1.ShowCancel := True;
dlgProgress1.ShowModal;
CodeSite.Send('Progress dialog has been closed');
end;
procedure TForm1.dlgProgress1Progress(Sender: TObject; var AContinue: Boolean);
begin
if dlgProgress1.Tag = 0 then
begin
if dlgProgress1.Position < dlgProgress1.Max then
dlgProgress1.Position := dlgProgress1.Position + 1;
if dlgProgress1.Position = dlgProgress1.Max then
dlgProgress1.Tag := 1;
end
else
begin
if dlgProgress1.Position > 0 then
dlgProgress1.Position := dlgProgress1.Position - 1;
if dlgProgress1.Position = 0 then
dlgProgress1.Tag := 0;
end;
AContinue := Assigned(ShellExecAndWaitTask); // why this never becomes false?
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(ShellExecAndWaitTask) then
ShellExecAndWaitTask.Cancel;
end;
When you close Notepad, shouldn't Assigned(ShellExecAndWaitTask)
in the dlgProgress1Progress
event handler become false and close the progress dialog by setting AContinue
to false? Instead it stays always true although the ShellExecAndWaitTask
task has been terminated! Why?
EDIT:
Following David's advice I changed the code. Now it works, but is it thread-safe?
var
ShellExecAndWaitTask: System.Threading.ITask;
ShellExecAndWaitTaskTerminated: Boolean;
procedure TForm1.btnProgressDialogTestClick(Sender: TObject);
begin
ShellExecAndWaitTask := TTask.Create(
procedure
begin
JclShell.ShellExecAndWait('notepad'); // BTW, is this thread-safe?
CodeSite.Send('Notepad has been closed');
TThread.Queue(TThread.CurrentThread,
procedure
begin
ShellExecAndWaitTaskTerminated := True;
end);
end);
ShellExecAndWaitTaskTerminated := False;
ShellExecAndWaitTask.Start;
dlgProgress1.Caption := 'ProgressDialog Test';
dlgProgress1.Text := 'Close Notepad to automatically close this progress dialog';
dlgProgress1.Tag := 0;
dlgProgress1.Position := 0;
dlgProgress1.ShowCancel := True;
dlgProgress1.ShowModal;
CodeSite.Send('Progress dialog has been closed');
end;
procedure TForm1.dlgProgress1Progress(Sender: TObject; var AContinue: Boolean);
begin
if dlgProgress1.Tag = 0 then
begin
if dlgProgress1.Position < dlgProgress1.Max then
dlgProgress1.Position := dlgProgress1.Position + 1;
if dlgProgress1.Position = dlgProgress1.Max then
dlgProgress1.Tag := 1;
end
else
begin
if dlgProgress1.Position > 0 then
dlgProgress1.Position := dlgProgress1.Position - 1;
if dlgProgress1.Position = 0 then
dlgProgress1.Tag := 0;
end;
AContinue := not ShellExecAndWaitTaskTerminated;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(ShellExecAndWaitTask) then
ShellExecAndWaitTask.Cancel;
end;
When you close Notepad, shouldn't Assigned(ShellExecAndWaitTask) in the dlgProgress1Progress event handler become false and close the progress dialog by setting AContinue to false?
No, that's never how things work. For instance you could have many different variables that reference the task. How could you expect all of those variables to be set to nil
.
No, the interface reference variable remains assigned until it leaves scope, or you set it to nil
. As an aside, I seriously question your choice of using a global variable for this. I don't know why you made that decision, but it seems to be the wrong decision.
The simplest thing for you to do is to send a message when the call to ShellExecAndWait
returns.
ShellExecAndWaitTask := TTask.Create(
procedure
begin
JclShell.ShellExecAndWait('notepad');
CodeSite.Send('Notepad has been closed');
// notify the main thread that the external process has completed
end);
You could use, for instance, PostMessage
or TThread.Queue
or TThread.Synchronize
to notify the main thread.