如何从 Thread 显示 "Please wait " 模态表单并在工作完成后释放它?

How to show a "Please wait " modal form from a TThread and free it when the job is done?

我已经工作了一段时间,试图制作一个模态表单来通知用户等待作业结束。 这是我正在尝试做的一个简单示例:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
  TLoader = class(TThread)
    private
      FStrings: TStrings;
      procedure ShowWait;
      procedure EndsWait;
    public
      Constructor Create(AStrings: TStrings);
      Destructor Destroy; override;
      procedure Execute; override;
  end;
var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var
  List: TStrings;
begin
  List := TStringList.Create;
  try
    // Load Some Data here
    TLoader.Create(List);
  finally
    List.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin

end;

{ TLoader }

constructor TLoader.Create(AStrings: TStrings);
begin
  inherited Create;
  FreeOnTerminate:= True;
  FStrings:= TStringList.Create;
  FStrings.AddStrings(AStrings);
end;

destructor TLoader.Destroy;
begin
  FStrings.Free;
  inherited;
end;

procedure TLoader.EndsWait;
begin
  TForm(Application.FindComponent('FWait')).Free;
end;

procedure TLoader.Execute;
begin
  inherited;
  Synchronize(ShowWait);
  // Do Some Job while not terminated
  Sleep(1000);
  // Free Wait Form
  // This part is not working
  Synchronize(EndsWait);
end;

procedure TLoader.ShowWait;
begin
  With TForm.Create(Application) do
    begin
      // Some code
      Name:= 'FWait';
      ShowModal;
    end;
end;

end.

除了没有关闭和释放模式窗体的 Synchronize(EndsWait); 之外,一切都如我所料。

如何在 TThread 为 运行 时显示模态表单并在 TThread 终止时释放它?


更新:

我尝试按照 Remy 的建议执行以下操作:

type
  TForm2 = class(TForm)
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
  TLoader = class(TThread)
    protected
      procedure DoTerminate; override;
      procedure DoCloseModal;
    public
      constructor Create;
      procedure Execute; override;
  end;
var
  Form2: TForm2;

implementation

{$R *.dfm}

{ TLoader }

constructor TLoader.Create;
begin
  inherited Create;
  FreeOnTerminate:= True;
end;

procedure TLoader.DoCloseModal;
begin
  Form2.ModalResult:= mrOk;
end;

procedure TLoader.DoTerminate;
begin
  inherited DoTerminate;
  Synchronize(DoCloseModal);
end;

procedure TLoader.Execute;
begin
  inherited;
  Sleep(200);
end;

procedure TForm2.FormShow(Sender: TObject);
begin
  TLoader.Create;
end;

end.

主窗体按钮点击事件处理程序:

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TForm2.Create(nil) do
    try
      ShowModal;
    finally
      Free;
    end;
end;

您有两个选择:

  1. 一开始不要使用模态形式。 TThread.Synchronize() 阻塞您的线程,直到同步方法退出,但 TForm.ShowModal() 阻塞该方法,直到表单关闭。使用 TThread.Synchronize()(或更好,TThread.Queue())到 Create()+Show()(不是 ShowModal())等待表单,然后 return 到线程和让它根据需要完成它的工作,然后 Synchronize()/Queue() 再次(或者,使用线程的 OnTerminate 事件)到 Close()+Free() Wait Form when完成。

  2. 或者,如果您想使用模态 Wait Form,则根本不要让线程管理 Wait Form。让你的按钮 OnClick 处理程序 Create()+ShowModal()(不是 Show())等待表单,并在 ShowModal() 退出时 Free() 它。当等待表单显示和关闭时,分别让等待表单在内部创建和终止线程。如果线程在 Wait Form 关闭之前结束,线程的 OnTerminate 处理程序可以 Close() Form(它只是设置 Form 的 ModalResult)以便 ShowModal() 将在OnClick 处理程序。

过了一段时间,我做了一个uWaitForm.pas单元:

unit uWaitForm;

interface

uses
  System.Classes, Vcl.Controls, Vcl.Forms;

type
  TWaitForm = class(TForm)
    private
      FThread: TThread;
    protected
      procedure Activate; override;
      procedure DoClose(var Action: TCloseAction); override;
    public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
  end;
  TWaitThread = class(TThread)
    private
      FForm: TWaitForm;
      FModalResult: TModalResult;
    protected
      procedure Execute; override;
      procedure DoSetModalResult;
    public
      constructor Create(AForm: TWaitForm);
      destructor Destroy; override;
  end;

implementation

{ TWaitForm }

procedure TWaitForm.Activate;
begin
  inherited;
  FThread:= TWaitThread.Create(Self);
end;

constructor TWaitForm.Create(AOwner: TComponent);
begin
  inherited CreateNew(AOwner);
  Name:= 'WaitForm';
  BorderStyle:= bsDialog;
  Caption:= 'Please wait...';
  Width:= 200;
  Height:= 150;
  Position:= poDesktopCenter;
end;

destructor TWaitForm.Destroy;
begin
  if Assigned(FThread) then
    FThread.Terminate;
  inherited;
end;

procedure TWaitForm.DoClose(var Action: TCloseAction);
begin
  inherited;
  if Assigned(FThread) then
    begin
      TWaitThread(FThread).FModalResult:= mrCancel;
      FThread.Terminate;
    end;

end; 


{ TWaitThread }

constructor TWaitThread.Create(AForm: TWaitForm);
begin
  inherited Create;
  FreeOnTerminate:= True;
  FForm:= AForm;
  FModalResult:= mrOk;
end;

destructor TWaitThread.Destroy;
begin
  if Assigned(FForm) then
    Synchronize(nil, DoSetModalResult);
  inherited;
end;

procedure TWaitThread.DoSetModalResult;
begin
  FForm.ModalResult:= FModalResult;
end;

procedure TWaitThread.Execute;
begin
  inherited;
  // Do the work while not terminated
  // You can check for Terminated here and set FModalResult accordingly to receive it from ShowModal
  // By default it's mrOk (Job is done, the thread doesn't cancled)
  Sleep(200);
end;

end.

那就用吧

  with TWaitForm.Create(nil) do
    try
      //Get ModalResult here and do something accordingly
      // mrCancel means the dialog form closed and the the Thread job aborted
      // mrOk The job is done and the form closed
      ShowModal;
    finally
      Free;
    end;