我想在应用程序加载时显示启动画面。然而,一些第 3 方组件在初始化期间阻塞了主线程几秒钟,这导致所有表单不更新。是否可以使用自己的线程进行启动画面,以便在主线程忙时也会更新?
该应用程序是win32和Delphi 2007版。
编辑:我试图避免“未绘制的启动画面”效果,如果其他一些窗口(来自其他应用程序)位于启动画面的顶部,例如 alt-tabbing 到另一个应用程序并返回,就会发生这种情况。
我想在应用程序加载时显示启动画面。然而,一些第 3 方组件在初始化期间阻塞了主线程几秒钟,这导致所有表单不更新。是否可以使用自己的线程进行启动画面,以便在主线程忙时也会更新?
该应用程序是win32和Delphi 2007版。
编辑:我试图避免“未绘制的启动画面”效果,如果其他一些窗口(来自其他应用程序)位于启动画面的顶部,例如 alt-tabbing 到另一个应用程序并返回,就会发生这种情况。
您可以在另一个线程中运行启动画面,但随后您将需要使用原始 Windows API 调用或实现类似 VCL 的类的第三方库(如Key Objects Library )。但是不要从启动线程访问 VCL 内容。
如果你走那条路(我认为你不应该这样做,因为它做了大量工作却收效甚微),请务必遵守有关从多个线程访问 Windows API 的规则。谷歌例如“用户界面线程”以获取更多信息。
编辑:
我以前不知道,但实际上有一个组件在CodeCentral 上为 Delphi 实现了一个线程启动画面。使用这个组件,它可能(还没有尝试过)实际上很容易在不同的线程中显示启动画面,但是对从辅助线程访问 VCL 的警告仍然存在。
其实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 资源编辑器)。
与 VCL 技巧(表单创建顺序等)相比,这种方式更好的是,当您的应用程序需要一些时间思考时,您可以多次使用它。
首先在 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 中的发布是为了以防您的 mainform 创建失败。)
由于您的初始表单是FormStyle := fsStayOnTop,因此当您的主线程阻塞时它没有收到绘制消息应该不是问题。然后,当主线程解除阻塞时,您向它发送一条更新消息(更改进度条等)。尽管我同意 Gamecat 的观点,您可能希望联系您的第 3 方组件供应商并让他们停止阻塞您的主线程。
或者,您可以在单独的线程中创建您的 3rd 方组件(前提是它们不是可视的,因为这会更困难一些。)
这也适用于将Application.MainFormOnTaskBar设置为 true 的情况。
我在启动代码中创建启动代码,始终设置在顶部,然后在适当的位置使用 frmSplash.Update 以确保它可见和更新。主窗体 create 就是这样一种称呼它的地方。
问题是Delphi 2007假定第一个窗体现在是主窗体,在核心代码中没有办法替换主窗体,所以飞溅不再那么好。也许拥有一个快速的小飞溅应用程序然后运行主应用程序的旧视觉基本解决方案实际上可能更好!
通过在单独的线程中运行启动屏幕并不能解决阻塞主线程的问题,因为它需要主线程来进行屏幕更新。
如果启动画面没有改变,这不是问题。
也许您应该联系您的第 3 方组件供应商,因为这样的长块是一个真正的问题。
吉姆·麦基思(Jim McKeeth)有一个好主意,但他没有解决一件可能是也可能不是问题的事情。您谈到需要很长时间才能初始化的组件。你的意思是初始化部分,还是稍后发生的事情,比如在创建表单时?因为所有初始化部分都在 DPR 中的任何代码运行之前运行。这部分需要很长时间,你必须做一些棘手的事情才能让你的启动画面出现在所有它的前面:
将表单的单元尽可能靠近 .DPR 的顶部。(但不是在需要先做的事情之前,比如 FastMM)。将代码显示在该单元的初始化部分中以显示启动画面。并确保您的初始屏幕不使用任何具有较长初始化周期的单元(或使用它的单元使用......或依赖关系树中的任何位置。)然后希望它有效。
但是,如果在初始初始化堆栈完成之后才开始出现减速问题,那么请按照 Jim 所说的进行。