0

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;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
user1580348
  • 5,721
  • 4
  • 43
  • 105
  • You mean a variable which is set when Notepad closes? This wouldn't be very elegant, and would it be thread-safe? – user1580348 Apr 22 '15 at 12:57

1 Answers1

1

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks for the information. In the EDIT I now use `TThread.Queue`. Is this correct? I use a global variable `ShellExecAndWaitTask`, so in `FormDestroy` I can check whether the thread is still running and cancel it in the case it is still running. (Wouldn't it be a bad thing to let the thread running when the application closes?) – user1580348 Apr 22 '15 at 13:19
  • `TThread.Queue` seems like a reasonable approach to me. Personally I would not bother with a task for something as simple as this. A plain old fashioned thread would be my weapon of choice here. As for the global variables, you don't need to use one. You should not. If you need state associated with the form, store it in a form member. – David Heffernan Apr 22 '15 at 13:22
  • What would be nice her: If Delphi had a method to automatically cancel ALL threads started from the main thread (so this could be used when the application closes). In this case I wouldn't need to use a global variable for the task. – user1580348 Apr 22 '15 at 13:49
  • You don't need to use a global variable – David Heffernan Apr 22 '15 at 14:15
  • How could I cancel a running task when the application closes without a global variable? You didn't mention this. – user1580348 Apr 22 '15 at 14:23
  • I think I answered the question that you asked. You disagree. Why do you need a global variable? Why can't is be a member of the form class? And what if the user presses the button again before the first task has been destroyed? – David Heffernan Apr 22 '15 at 14:36
  • Sorry David, but you didn't answer my question about canceling the task at app termination. And of course the variable could be a form class member, the variable declaration in the question code is just a simplified example, I didn't explicitly exclude a form class variable. And the user cannot press the button again while the task is running because the progress dialog is modal. – user1580348 Apr 22 '15 at 14:54
  • You asked how to have the main thread notified when the task ends. That's what I answered. – David Heffernan Apr 22 '15 at 14:58
  • Thanks, David, as always your expert answers are very much appreciated! – user1580348 Apr 22 '15 at 15:51