Delphi中如何在主线程忙碌时显示启动画面

13

我想在应用程序加载时显示闪屏。但是,一些第三方组件在初始化期间会阻止主线程几秒钟,这导致所有窗体都无法更新。是否可能使用自己的线程来实现闪屏,以便在主线程忙碌时也能更新?

该应用程序是Win32和Delphi版本2007。

编辑:我正在尝试避免“未绘制闪屏”效果,如果其他应用程序的窗口(例如通过alt-tab切换到另一个应用程序并返回)位于闪屏的顶部,则会出现此效果。

6个回答

9
您可以在另一个线程中运行启动画面,但是这时您需要使用原始的Windows API调用或第三方库(例如实现类似VCL的类的Key Objects Library)。但请勿从启动线程访问VCL内容。
如果您选择这条路线(我认为您不应该这样做,因为这需要付出很多努力却获益甚少),请确保遵守有关从多个线程访问Windows API的规则。例如,可以通过谷歌搜索“用户界面线程”来获取更多信息。
编辑:
我之前并不知道,但实际上有一个组件实现了Delphi的线程化启动画面。使用此组件(尚未尝试),可能会轻松地将启动画面放在不同的线程中,但对于从辅助线程访问VCL的警告仍然存在。

7

实际上,只要使用对话框资源,WinApi的方式就非常简单。请检查以下代码(甚至适用于D7和XP):

type
  TDlgThread = class(TThread)
  private
    FDlgWnd: HWND;
    FCaption: string;
  protected
    procedure Execute; override;
    procedure ShowSplash;
  public
    constructor Create(const Caption: string);
  end;

{ TDlgThread }

// Create thread for splash dialog with custom Caption and show the dialog
constructor TDlgThread.Create(const Caption: string);
begin
  FCaption := Caption;
  inherited Create(False);
  FreeOnTerminate := True;
end;

procedure TDlgThread.Execute;
var Msg: TMsg;
begin
  ShowSplash;
  // Process window messages until the thread is finished
  while not Terminated and GetMessage(Msg, 0, 0, 0) do
  begin
    TranslateMessage(Msg);
    DispatchMessage(Msg);
  end;
  EndDialog(FDlgWnd, 0);
end;

procedure TDlgThread.ShowSplash;
const
  PBM_SETMARQUEE = WM_USER + 10;
  {$I 'Dlg.inc'}
begin
  FDlgWnd := CreateDialogParam(HInstance, MakeIntResource(IDD_WAITDLG), 0, nil, 0);
  if FDlgWnd = 0 then Exit;
  SetDlgItemText(FDlgWnd, IDC_LABEL, PChar(FCaption));           // set caption
  SendDlgItemMessage(FDlgWnd, IDC_PGB, PBM_SETMARQUEE, 1, 100);  // start marquee
end;

procedure TForm1.Button3Click(Sender: TObject);
var th: TDlgThread;
begin
  th := TDlgThread.Create('Connecting to DB...');
  Sleep(3000); // blocking wait
  th.Terminate;
end;

当然,你必须准备好对话框资源(Dlg.rc)并将其添加到你的项目中:
#define IDD_WAITDLG 1000
#define IDC_PGB 1002
#define IDC_LABEL 1003

#define PBS_SMOOTH  0x00000001
#define PBS_MARQUEE 0x00000008

IDD_WAITDLG DIALOGEX 10,10,162,33
STYLE WS_POPUP|WS_VISIBLE|WS_DLGFRAME|DS_CENTER
EXSTYLE WS_EX_TOPMOST
BEGIN
  CONTROL "",IDC_PGB,"msctls_progress32",WS_CHILDWINDOW|WS_VISIBLE|PBS_SMOOTH|PBS_MARQUEE,9,15,144,15
  CONTROL "",IDC_LABEL,"Static",WS_CHILDWINDOW|WS_VISIBLE,9,3,144,9
END

请注意这些PBS_*定义。我必须添加它们,因为Delphi 7不知道这些常量。 以及常量的定义(Dlg.inc
const IDD_WAITDLG = 1000;
const IDC_PGB = 1002;
const IDC_LABEL = 1003;

我使用RadAsm资源编辑器,它会自动生成包含文件。
在XP下我们获得了什么,如下图所示: 与VCL技巧(表单创建的顺序等)相比,这种方法的优点是当您的应用程序需要一些时间来思考时,您可以多次使用它。

3

首先在DPR中创建启动界面,但不要使用 Application.CreateForm 方法。以下是一些简单的代码:

begin
  Application.Initialize;
  SplashForm := TSplashForm.Create(nil);
  try
    SplashForm.FormStyle := fsStayOnTop;
    SplashForm.Show;
    Application.ProcessMessages;
    Application.CreateForm(TForm14, Form14);
    // Other Form Creation here . . . .
    Application.Run;
  finally
    if assigned(SplashForm) then
      SplashForm.Release;
  end;
end.

然后将以下代码放在您的MainFrom(在本例中为Form14)的Show事件处理程序中(或稍后 - 当您的初始化完成时):

SplashForm.Close;
SplashForm.Release;
SplashForm := nil;

您在释放窗体时调用Release而不是Free,并将其分配给nil,以便DRP不再调用release。 DRP中的释放只是为了防止主窗体创建失败。

由于您的闪屏窗体是FormStyle:=fsStayOnTop,因此当您的主线程阻塞时,它不会收到绘图消息也不会有问题。然后,当主线程解除阻塞时,您发送更新消息(以更改进度条等)。尽管我同意Gamecat的观点,您可能需要联系第三方组件供应商,让他们停止在主线程上阻塞。

或者,您可以在单独的线程中创建第三方组件(如果它们不是视觉化的话,那可能会更加困难。)这种方法也适用于设置为true的Application.MainFormOnTaskBar。


由于您的启动窗体是FormStyle:= fsStayOnTop,因此当主线程阻塞时,它不会收到绘制消息并不应该成为问题。<br>我不同意,切换到另一个应用程序再切回来将导致闪屏无效。 - mghie
我真的需要“实时”屏幕,因此获取那些绘制消息非常重要。 - Harriv
@mghie 从技术上讲,我想是这样的。 - Jim McKeeth

0

我在启动代码中创建了闪屏,设置了始终置顶,并在适当的位置使用frmSplash.Update来确保它可见且更新。主窗体的创建是其中一个调用它的地方。

问题在于Delphi 2007假定第一个窗体现在是主窗体,在核心代码中没有办法替换主窗体,因此闪屏不再那么好用了。也许旧的Visual Basic解决方案——拥有一个快速的小闪屏应用程序,然后运行主应用程序——实际上可能更好!


在Delphi 2007中,启动窗体的工作方式与以前的版本相同,当您使用一行SplashForm:= TSplashForm.Create(nil)创建它们并让它们自由。第一个Application.CreateForm()设置Application.MainForm,但是这一直以来都是这样工作的,据我所知。 - mghie
1
问题在于,如果主线程被长时间阻塞,frmSplash.Update 帮助不大。我需要在屏幕上显示闪屏来通知用户“是的,程序正在运行,请耐心等待”。 - Harriv

0

在单独的线程中运行启动屏幕并不能解决主线程阻塞的问题,因为它需要主线程进行屏幕更新。

如果启动屏幕不会改变,那么这就不是一个问题。

也许你应该联系你的第三方组件供应商,因为像这样长时间的阻塞确实是一个真正的问题。


1
使用一个次要线程来处理启动画面可以解决这个问题。不幸的是,在这种情况下使用VCL是行不通的。但是,使用原始的Windows API调用可以使用具有自己消息泵的次要用户界面线程。 - mghie
我需要一个“动态”的启动画面,以表明应用程序正在真正运行。 - Harriv

0
Jim McKeeth提出了一个很好的想法,但他没有解决一个可能是问题的事情。您谈到组件初始化需要很长时间。这是指初始化部分,还是后来发生的事情,比如在创建表单时?因为所有初始化部分都在DPR中的任何代码运行之前运行。如果那部分需要很长时间,您将不得不做一些棘手的事情才能使您的闪屏显示在所有内容的前面:
尽可能将表单单元放在.DPR的顶部。(但不要放在需要先进行的事物之前,例如FastMM)。将显示闪屏的代码放在该单元的初始化部分中。确保没有任何具有长时间初始化期间的单元使用您的闪屏(或者使用它的单元使用...或者在依赖树中的任何地方)。然后希望它有效。
如果减速问题直到初始初始化堆栈完成后才开始,则按照Jim所说的去做。

初始化部分没有问题,长时间等待发生在我在FormCreate(或之后)设置组件为活动状态时。 - Harriv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接