首页 > 解决方案 > 显示异步“等待”窗口

问题描述

在 Delphi VCL 应用程序中,我想创建一个“等待”消息窗口,因为这是一个耗时的过程(本例中的一个大循环)。

在开始耗时的过程之前,我尝试了以下要执行的操作。

- 创建一个包含消息的简单窗口的新形式。

- 使用 messagedlg 创建一条消息。

- 甚至更改主表单上的 TLabel.Caption(执行耗时过程的表单)。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  popUpMessage;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;

    procedure Button1Click(Sender: TObject);
  end;

var
  Form1: TForm1;
  dialog : TForm;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
    var i, j, k :LongInt;
begin


     {1}
     popUpMessage.Form2 := TForm2.Create(nil); //also tried with Create(self)
     Form2.show;

     {2}
     dialog := CreateMessageDialog ('Wait',TMsgDlgType.mtWarning, mbYesNoCancel);
     dialog.Show;


     {3}
     messagedlg('Wait',mtError, mbOKCancel, 0);

     {4}
      Label1.Caption := 'Wait';


     //Time consuming process
       for i := 0  to 200000 do
         for j := 0  to 20000do
           k := i-j;

   end;
end.

在 {1} 和 {2} 的情况下,弹出表单出现在耗时的过程开始之前,但它们的组件仅在完成后才绘制。

情况 {3} 保持执行,直到模式对话框关闭。

如果{4}在耗时的过程完成后标题会发生变化。

如何异步创建消息,使其完全绘制而不等待其父进程?

标签: delphiasynchronousnon-modal

解决方案


这是一个有点宽泛的问题,因此我将提供一个简短的示例来演示如何将长时间运行的任务移至线程并安全地向 UI 提供反馈以了解进度和完成情况。

我不会为第二个进度表单而烦恼,对于这个例子,我只是将一个按钮 (Button1) 和一个进度条 (ProgressBar1) 添加到一个新的 TForm 中。

在下面的方法中,我们启动一个匿名线程来执行长时间运行的操作,并以定期的进度间隔定期通知主线程。我们还会在工作完成时通知。为了扩展它,您还可以执行错误检查/处理,并且还可以通知操作未能完成,但为了简洁起见,我将示例限制为简单的东西。

procedure TForm1.Button1Click(Sender: TObject);
begin
  ProgressBar1.Position := 0;
  Button1.Enabled := false;           
  Button1.Caption := 'Calculating...';
  TThread.CreateAnonymousThread(
    procedure
        procedure PerformProgressStep;
        begin
          TThread.Queue(nil, procedure
                             begin
                               ProgressBar1.StepIt;
                             end);
        end;
        procedure NotifyComplete;
        begin
          TThread.Queue(nil, procedure
                             begin
                               ShowMessage('done');
                               Button1.Enabled := true;
                               Button1.Caption := 'Start Task';
                             end);
        end;
    var
      i, j, k : integer;
    begin
      for i := 0  to 199999 do begin
        for j := 0  to 20000 do k := i-j;
        if (i mod 20000) = 0 then PerformProgressStep;
      end;
      NotifyComplete;
    end).Start;
end;

这里所有的 UI 操作都使用TThread.Queue. .Synchronize如果您必须等待主线程处理工作才能继续线程中的操作,这也是一种选择,但您必须注意避免在这种情况下出现死锁情况。

此代码中没有任何错误处理,请注意 - 这只是为了演示如何将您的工作转移到后台线程中。还有许多其他方法可以做到这一点,使用匿名线程(如上),如果您有更重的实现要封装,则使用自定义 TThread,使用任务和 RTL 线程池,使用Threading单元中的并行循环操作等。无论哪种您决定使用将取决于您的特定应用程序的要求。


对于 Delphi 中的多线程更深入的探索,您可以随时跟进Martin Harvey 的优秀文章。这是一篇相当老的文章,因此它在较低级别上涉及多线程主题,并使用基本的 RTL 和 WinAPI 结构,并且不包含更现代版本的 Delphi 中包含的任何新语言功能。然而,它具有很强的指导性,是复习基础知识的绝佳读物。


推荐阅读