主线程繁忙时在Delphi中显示启动画面

时间:2008-12-23 09:42:12

标签: multithreading delphi delphi-2007 splash-screen

我想在加载应用程序时显示启动画面。但是,某些第三方组件在启动期间会阻塞主线程几秒钟,这会导致所有表单都不更新。是否可以使用自己的线程启动屏幕,这样当主线程忙时它也会更新?

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

编辑:我正在尝试避免“未绘制的启动画面”效果,如果某些其他窗口(来自其他应用程序)位于启动画面的顶部,例如alt-tabbing到另一个应用程序并返回,则会发生这种情况。

6 个答案:

答案 0 :(得分:9)

您可以在另一个线程中运行启动画面,但是您需要使用原始Windows API调用或实现类似VCL类的第三方库(如Key Objects Library)。但是,不要从启动线程访问VCL内容。

如果你走这条路线(我认为你不应该这样做,因为这是一项很少有收获的工作),请务必遵守多线程的Windows API访问规则。谷歌例如为“用户界面线程”提供更多信息。

修改

我以前没有意识到这一点,但实际上有一个组件在CodeCentral上实现Threaded Splashscreen for Delphi。使用这个组件它可能(没有尝试过)实际上很容易将启动画面放在不同的线程中,但是对辅助线程的VCL访问的警告仍然存在。

答案 1 :(得分:4)

实际上,只要您使用对话框资源,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技巧(表单创建的顺序等)相比,这种方式更好的是,当您的应用需要一些时间思考时,您可以多次使用它。

答案 2 :(得分: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.

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

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

(您在表单上调用Release而不是Free,并将其分配为nil,以便DRP不会再次调用release。在DRP中发布以防万一您的mainform无法创建。)

由于您的启动窗体是 FormStyle:= fsStayOnTop ,因此当主线程阻塞时,它不应该是一个没有获取绘制消息的问题。然后当主线程解除阻塞时,你发送一条更新消息(更改进度条等)虽然我同意Gamecat你可能想联系你的第三方组件供应商并让他们停止阻止你的主线程。

或者你可以在一个单独的线程中创建你的第三方组件(前提是它们不是可视的,因为这样会有点困难。)

这也适用于 Application.MainFormOnTaskBar 也设置为true。

答案 3 :(得分:0)

我在启动代码中创建启动画面,始终在顶部设置,然后在适当的位置使用frmSplash.Update以确保它可见并更新。主要形式创建是一个这样称呼它的地方。

问题是Delphi 2007假设第一个表单现在是主表单,并且没有办法替换核心代码中的主表单,因此splashes不再那么好了。也许旧的视觉基本解决方案有一个快速的小飞溅应用程序然后运行主应用程序可能实际上更好!

答案 4 :(得分:0)

通过在单独的线程中运行启动画面无法解决阻塞主线程的问题,因为它需要主线程来进行屏幕更新。

它的启动画面没有变化,这不是问题。

也许你应该联系你的第三方组件供应商,因为这样的长块是一个真正的问题。

答案 5 :(得分:0)

Jim McKeeth在那里有一个好主意,但他没有解决一个可能或可能不是问题的事情。您谈到组件需要很长时间才能初始化。那么,你的意思是初始化部分,或者稍后发生的事情,比如在创建表单时?因为所有初始化部分都在DPR中的任何代码运行之前运行。这部分花了很长时间,你将不得不做一些棘手的事情让你的闪屏出现在所有的前面:

尽可能将表单的单位放在靠近.DPR顶部的位置。 (但不是在需要先行的事情之前,比如FastMM)。将代码显示在该单元的初始化部分中的启动画面。并确保没有任何具有较长初始化周期的单元,即启动画面使用(或使用它的那些单元使用...或依赖树中的任何位置。)然后希望它有用。

如果在初始初始化堆栈完成之后才开始减速问题,那么请继续使用Jim说的话。