2

我正在运行一个长时间的操作,我想出一个向用户展示它的好方法是使用一个使用该IProgressDialog对象的系统进度对话框。

我只找到了几个用法示例,这是我的实现。我遇到的问题是应用程序仍然没有响应(我知道我可能需要使用线程),而且取消按钮根本不起作用(这可能是第一个问题的连续性。)

我在 Windows 8.1 下使用 Delphi XE。

编辑:我Application.ProcessMessages在评估之前添加了一个调用,HasUserCancelled但它似乎没有多大帮助(对话框仍然没有处理单击取消按钮。)

var
  i, procesados: Integer;
  IDs: TList<Integer>;
  pd: IProgressDialog;
  tmpPtr: Pointer;
begin
    procesados := 0;
    try
      tmpPtr := nil;
      CoCreateInstance(CLSID_ProgressDialog, nil, CLSCTX_INPROC_SERVER,
        IProgressDialog, pd);
      // also seen as  pd := CreateComObject(CLSID_ProgressDialog) as IProgressDialog;

      pd.SetTitle('Please wait');
      pd.SetLine(1, PWideChar(WideString('Performing a long running operation')),
        false, tmpPtr);
      pd.SetAnimation(HInstance, 1001); // IDA_OPERATION_ANIMATION ?
      pd.Timer(PDTIMER_RESET, tmpPtr);
      pd.SetCancelMsg(PWideChar('Cancelled...'), tmpPtr);
      pd.StartProgressDialog(Handle, nil, PROGDLG_MODAL or
        PROGDLG_NOMINIMIZE, tmpPtr);

      pd.SetProgress(0, 100);
      IDs := GetIDs; // not relevant, returns List<Integer>
      try
        for i in IDs do
        begin
          try
            Application.ProcessMessages;
            if pd.HasUserCancelled then 
              Break;  // this never happens
            Inc(procesados);
            pd.SetProgress(procesados, IDs.Count);

            LongRunningOp(id);
          except
            // ?
          end;
        end;
      finally
        IDs.Free;
      end;
    finally
      pd.StopProgressDialog;
      // pd.Release; doesn't exist
    end;
  end;
end;
4

2 回答 2

3

您将需要使用您希望应用程序响应的线程。您的取消按钮不起作用的原因是您的循环中没有处理消息。将 Application.ProcessMessages 之类的东西放在循环中将让它响应单击取消按钮,但线程仍然是更好的选择。

您应该将带有 LongRunninOp(id) 的循环放在一个线程中,然后使用 Synchronize 反馈给 UI。像这样的东西:

procedure TMyThread.Execute;
var
  i: Integer;
begin
  for i in IDs do
  begin
    try
      // If pd.HasUserCancelled is thread safe then this will work
      // if Terminated or pd.HasUserCancelled then 
      //   Break;
      // If pd.HasUserCancelled is not thread safe then you will need to do something like this
      Synchronize(
        procedure
        begin
          if pd.HasUserCancelled then 
            Terminate():
        end);
      if Terminated then 
        Break;
      Synchronize(
        procedure
        begin
          MainForm.pd.SetProgress(I, IDs.Count);             
        end);

      LongRunningOp(id);
    except
      // ?
    end;
  end;
end;

对于线程,您需要确保您没有从主线程和后台线程访问诸如 ID 之类的东西。我也不喜欢 MainForm.pd.SetProgress 类型的调用,但我把它放在那里是为了向您展示正在发生的事情。在您调用的主窗体上有一个方法要好得多。

在上面的代码中,当从主线程调用 Terminated 时,对 Terminated 的检查将返回 true MyThread.Terminate()。这是您应该为取消按钮放入事件处理程序的内容。这表明它应该关闭线程。理想情况下,这个检查也应该在 LongRunningOp 调用中进行,以防止调用时延迟响应Terminate

正如 Remy 所指出的,您可以使用线程OnTerminate事件来告诉主窗体线程何时完成,无论是何时终止,还是线程在完成时结束。

我刚刚使用以下代码进行了测试,它按预期工作:

var
  iiProgressDialog: IProgressDialog;
  pNil: Pointer;
begin
  pNil := nil;
  iiProgressDialog := CreateComObject(CLASS_ProgressDialog) as IProgressDialog;
  iiProgressDialog.SetTitle('test');
  iiProgressDialog.StartProgressDialog( Handle, nil, PROGDLG_NOMINIMIZE, pNil);
  repeat
    Application.ProcessMessages;
  until iiProgressDialog.HasUserCancelled > 0;
  iiProgressDialog.StopProgressDialog;
end;

按取消将终止循环。您没有看到相同效果的原因是您的 LongRunningOp 花费的时间太长。在该例程内部,没有任何东西可以处理消息。如果您想在单个线程中运行,那么您也需要在该例程中定期调用 Application.ProcessMessages。

以下组件也可以帮助您:

http://www.bayden.com/delphi/iprogressdialog.htm

它将 IProgressDialog 包装成一个 Delphi 组件。它创建一个线程来监视取消单击,如果单击该按钮将触发一个事件。

于 2014-05-16T20:09:06.933 回答
0

Windows 通过在窗口之间传递消息来工作。与某些操作系统不同,这些消息不是注入的,而是必须从消息队列中轮询。这就是 Application.ProcessMessages 调用的作用。

这有点像合作多任务处理。因此,在您的线程中创建和操作的任何窗口控件(如此对话框)都不会在处理循环运行时处理事件。

有2种方式。首先是为您的处理创建一个线程并等待它返回。这可能有点复杂,并且可能需要检查跨线程问题。

第二种方法是在您的处理循环中调用 Application.ProcessMessages - 这会强制处理消息并且您可以获得点击事件等。

但是,您正在使用的对象可以在循环内被销毁,因为事件可能在循环内发生。例如,如果您的处理循环由于单击表单上的按钮而正在运行,并且用户关闭该表单并将其销毁(例如自动释放表单) - 那么表单对象及其所有控件不再有效对象 - 引用它们将导致内存访问冲突和异常。最简单的解决方案是使用标志并且在处理循环期间不要让表单关闭。

无论哪种方式,您都有责任管理所有事物的生命周期,并且每种方法都有必须由您处理的问题。

于 2014-05-16T21:04:56.170 回答