函数在后台运行时加载表单作为覆盖

Loading form as overlay while function runs in background

当我调用一个函数并且它 "runs"(最多可能需要 3 秒 - 刷新函数从 api 服务器获取数据)我想将加载表单显示为 Ajax 加载指示器作为主窗体上方的叠加层。

我之前的尝试都失败了。我曾尝试更改在 Main 创建后直接显示的 Create LoadingForm。然后我尝试了 LoadingForm.Show/Showmodal。在模态序列中停止并仅在我关闭表单并显示 window 尽管 .

不关闭时继续

我也遇到过表格打开但是gif没有显示的情况,应该是白色的地方一直是白色-没有图像没有动画

有什么想法吗?

下面的代码在其 Execute 方法中使用线程来模拟长时间 运行 块,并使用 OnProgress "callback" 通知表单已完成的百分比改变了。

这是一个非常简单的示例,但它可以向您展示我认为的正确方向之一。
请注意,当前未执行任何错误检查或异常处理。


Unit1.pas主窗体和线程class

unit Unit1;

interface

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

type
  TMyRun = class(TThread)
    protected
      procedure Execute; override;
    public
      OnProgress: TProgressEvent;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FProgressForm: TfrmProgress;
    procedure myRunProgress(Sender: TObject; Stage: TProgressStage;
        PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
    procedure myRunTerminate(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TMyRun.Execute;
var
  i: Integer;
  r: TRect;
begin
  for i := 1 to 100 do begin
    if Terminated then
      Break;

    Sleep(50);//simulates some kind of operation

    if Assigned(OnProgress) then
      Synchronize(procedure
          begin
            OnProgress(Self, psRunning, i, False, r, '');
          end);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FProgressForm := TfrmProgress.Create(nil);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FProgressForm.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TMyRun.Create do begin
    FreeOnTerminate := True;
    OnProgress := myRunProgress;
    OnTerminate := myRunTerminate;
  end;
  FProgressForm.ShowModal;
end;

procedure TForm1.myRunProgress(Sender: TObject; Stage: TProgressStage;
  PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
begin
  FProgressForm.ProgressBar1.Position := PercentDone;
end;

procedure TForm1.myRunTerminate(Sender: TObject);
begin
  FProgressForm.Close;
end;

end.

Unit1.dfm

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 81
  ClientWidth = 181
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poDesktopCenter
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 48
    Top = 24
    Width = 91
    Height = 25
    Caption = 'Run the thread'
    TabOrder = 0
    OnClick = Button1Click
  end
end

Unit2.pas 进度对话框

unit Unit2;

interface

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

type
  TfrmProgress = class(TForm)
    ProgressBar1: TProgressBar;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmProgress: TfrmProgress;

implementation

{$R *.dfm}

end.

Unit2.dfm

object frmProgress: TfrmProgress
  Left = 0
  Top = 0
  BorderStyle = bsSizeToolWin
  Caption = 'frmProgress'
  ClientHeight = 51
  ClientWidth = 294
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object ProgressBar1: TProgressBar
    Left = 16
    Top = 16
    Width = 265
    Height = 17
    TabOrder = 0
  end
end

参考评论指出长时间 运行 操作需要访问主窗体中的网格,以避免阻塞该对象上的 VCL 线程:

  1. 避免从线程访问 VCL 数据 - 如果必须在例程中重用已修改的数据,这是首选方法
    • 将网格数据的副本传递给线程 - 比如在构造函数中
    • 更新文案
    • 在线程完成后使用编辑后的数据副本更新网格 - 即在 ShowModal returns.
    • 之后
  2. 从线程访问表单对象 - 如果以非常短的时间间隔访问表单对象,则可以这样做
    • 使用synchronized block从网格中获取数据
    • 在线程的同步回调中​​更新网格 - 即在 myRunProgressmyRunTerminate 方法中

对于不同的用例,如果您的例程没有,混合方法 也可能有意义(在线程同步块中的 constructor/update 网格中传递副本)不考虑已经更改的数据:选择最适合您需要的方法。

如果另一个外部线程更新了网格,thread1 可以读取数据然后填充表单的私有队列 - 比如 TThreadListTCriticalSection 块中的另一个集合 - 并且通知 thread2 在队列中执行作业,但我希望这可能不需要完成您的工作。

创建对话框表单设置:

BorderIcons = []
BorderStyle = bsDialog
FormStyle = fsStayOnTop
Position = poScreenCenter

在调用函数时在主窗体中写入:

procedure TFormMain.Button1Click(Sender: TObject);
begin
    Enabled:=false;
    try
        FormDialog.Show;
        FormDialog.Refresh;

        MyLongRunProcedure; // calls your procedure here

    finally        
        Enabled:=true;
        FormDialog.Close;
    end;
end;

它应该有效..