0

在 Delphi XE7(或 XE8)中,在表单上放置一个TjvProgressDialog(来自 JVCL)并将其命名为dlgProgress1。还有一个 TButton 并将其命名为btnProgressDialogTest

此代码首先从单独的线程 (ShellExecAndWaitTask) 启动记事本,然后打开带有无限进度循环的进度对话框 (dlgProgress1):

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;

当您关闭记事本时,不应该Assigned(ShellExecAndWaitTask)在事件处理程序中变为假并通过设置为假来dlgProgress1Progress关闭进度对话框吗?相反,尽管任务已终止AContinue,但它始终保持真实!ShellExecAndWaitTask为什么?

编辑:

按照大卫的建议,我更改了代码。现在它可以工作了,但它是线程安全的吗?

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;
4

1 回答 1

1

当您关闭记事本时,dlgProgress1Progress 事件处理程序中的 Assigned(ShellExecAndWaitTask) 是否不应该变为 false 并通过将 AContinue 设置为 false 来关闭进度对话框?

不,事情从来都不是这样的。例如,您可以有许多不同的变量来引用任务。你怎么能期望所有这些变量都设置为nil.

不,接口引用变量保持分配状态,直到它离开范围,或者您将其设置为nil. 顺便说一句,我严重质疑您为此使用全局变量的选择。我不知道你为什么做出这个决定,但这似乎是一个错误的决定。

您要做的最简单的事情是在调用ShellExecAndWait返回时发送一条消息。

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);

例如,您可以使用PostMessageorTThread.QueueTThread.Synchronize通知主线程。

于 2015-04-22T13:13:23.350 回答