当主线程繁忙时在Delphi中显示启动屏幕

发布于 2024-07-10 21:50:33 字数 232 浏览 6 评论 0原文

我想在加载应用程序时显示启动屏幕。 然而,某些第三方组件在初始化期间会阻塞主线程几秒钟,这会导致所有表单无法更新。 是否可以在启动屏幕上使用自己的线程,以便在主线程繁忙时它也会更新?

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

编辑:我试图避免“未绘制的启动屏幕”效果,如果其他一些窗口(来自其他应用程序)位于启动屏幕的顶部,例如使用alt-tab键切换到另一个应用程序,则会发生这种情况然后回来。

I'd like to display splash screen while the application is loading. However some 3rd party components block main thread during initilization for several seconds, which causes all forms not to update. Is it possible to have splash screen with own thread so it would update also when main thread is busy?

The application is win32 and Delphi version 2007.

Edit: I'm trying to avoid "undrawn splash screen" effect, which happens if some other windows (from other applications) are on the top of splash screen, eg alt-tabbing to another application and back.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

[浮城] 2024-07-17 21:50:33

您可以在另一个线程中运行启动屏幕,但随后您将需要使用原始 Windows API 调用或第三方库(例如 实现类似 VCL 类的关键对象库)。 但是不要从启动线程访问 VCL 内容。

如果您走这条路(我认为您不应该这样做,因为这是大量工作却收效甚微),请务必遵守有关从多个线程访问 Windows API 的规则。 例如,Google 搜索“用户界面线程”以获取更多信息。

编辑:

我之前没有意识到,但实际上有一个组件实现了 CodeCentral 上的 Delphi 线程启动屏幕。 使用此组件(尚未尝试过)实际上可能很容易在不同的线程中显示启动屏幕,但针对从辅助线程访问 VCL 的警告仍然存在。

You can run the splash screen in another thread, but then you will need to use raw Windows API calls or a third-party library (like Key Objects Library) that implements VCL-like classes. Do however not access VCL stuff from splash thread.

If you go that route (which I don't think you should, as it is a lot of work for little gain), be sure to observe the rules about Windows API access from multiple threads. Google for example for "user interface threads" for more information.

Edit:

I wasn't aware of it before, but there is actually a component implementing a Threaded Splashscreen for Delphi on CodeCentral. Using this component it may (haven't tried it) actually be easy to have the splash screen in a different thread, but the warning against VCL access from secondary threads remains.

三生殊途 2024-07-17 21:50:33

其实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下我们能得到什么
< /a>

与 VCL 技巧(表单创建的顺序等)相比,这种方式更好的是,当您的应用程序需要一些时间思考时,您可以多次使用它。

Actually WinApi way is quite simple as long as you use dialog resources. Check this (working even on D7 and 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;

Of course you must prepare dialog resource (Dlg.rc) and add it to your project:

#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

Note these PBS_* defines. I had to add them because Delphi 7 knows nothing of these constants.
And definition of constants (Dlg.inc)

const IDD_WAITDLG = 1000;
const IDC_PGB = 1002;
const IDC_LABEL = 1003;

(I use RadAsm resource editor which generates include file automatically).

What we get under XP

What is better in this way comparing to VCL tricks (ordering of forms creation and so n) is that you can use it multiple times when your app needs some time to think.

悲欢浪云 2024-07-17 21:50:33

首先在 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,以防万一您的主窗体无法创建。)

由于您的启动表单是 FormStyle := fsStayOnTop 它不应该。当您的主线程阻塞时,它不会收到绘画消息,这是一个问题。 然后,当主线程解除阻塞时,您向其发送更新消息(以更改进度条等)。尽管我同意 Gamecat 的观点,但您可能需要联系第 3 方组件供应商并让他们停止阻塞您的主线程。

或者,您可以在单独的线程中创建第 3 方组件(前提是它们不是可视的,因为这会更困难一些。)

这也适用于将 Application.MainFormOnTaskBar 设置为 true 的情况。

Create you splash screen in the DPR first, but don't use the Application.CreateForm method for it. Here is some simple code:

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.

Then place the following code in the Show event handler (or later - when your initialization is done) for your MainFrom (in this case Form14):

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

(You call Release on a form instead of Free, and you assign it to nil so that the DRP doesn't call release again. The release in the DRP is just in case your mainform fails to create.)

Since your splash form is FormStyle := fsStayOnTop it shouldn't be an issue that it isn't getting paint messages when your main thread blocks. Then when the main thread unblocks you send it an update message (to change the progress bar, etc.) Although I agree with Gamecat that you might want to contact your 3rd party component vendors and get them to stop blocking the main thread on you.

Alternatively you could create your 3rd party components in a separate thread (provided they aren't visual, as that would be a little more difficult.)

This will work with Application.MainFormOnTaskBar set to true as well.

陌若浮生 2024-07-17 21:50:33

我在启动代码中创建启动画面,并设置为始终位于顶部,然后在适当的位置使用 frmSplash.Update 以确保其可见并更新。 主窗体 create 就是这样一个调用它的地方。

问题是Delphi 2007假设第一个窗体现在是主窗体,并且没有办法在核心代码中替换主窗体,所以启动画面不再那么好了。 也许旧的视觉基本解决方案,即拥有一个快速的小型启动应用程序,然后运行主应用程序,实际上可能会更好!

I create the splash in the startup code, with always on top set, and then use the frmSplash.Update at appropriate places to ensure it is visible and updated. The main form create is one such place to call it.

The problem is that Delphi 2007 assumes that the first form is now the main form, and there is no way to replace the main form in the core code, so splashes are not so good any more. Perhaps the old visual basic solution of having a fast little splash app which then runs the main app might actually be better!

爱的故事 2024-07-17 21:50:33

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

如果启动画面没有改变,这不是问题。

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

The problem of a blocking main thread is not solved by running the splash screen in a seperate thread because it will need the main thread for screen updates.

It the splash screen does not change, this is not a problem.

Maybe you should contact your 3rd party component vendor, because a long block like that is a real problem.

我的奇迹 2024-07-17 21:50:33

吉姆·麦基思(Jim McKeeth)提出了一个好主意,但他没有解决一件可能会或可能不会成为问题的事情。 您谈到组件需要很长时间才能初始化。 您的意思是初始化部分,还是稍后发生的事情,例如在创建表单时发生的事情? 因为所有初始化部分都在 DPR 中的任何代码运行之前运行。 如果该部分需要很长时间,您将必须执行一些棘手的操作才能使启动屏幕显示在所有部分的前面:

将表单的单元尽可能靠近 .DPR 的顶部。 (但不是在需要先处理的事情之前,例如 FastMM)。 将显示启动屏幕的代码放在该单元的初始化部分中。 并确保您的启动屏幕使用的任何单元(或者使用它的单元使用......或依赖关系树中的任何位置)都没有任何具有较长初始化周期的单元。然后希望它能起作用。

不过,如果直到初始初始化堆栈完成后才开始出现速度减慢问题,那么就按照吉姆所说的去做。

Jim McKeeth has got a great idea there, but he doesn't address one thing that may or may not be an issue. You talk about components taking a long time to initialize. By that, do you mean the initialization section, or something that happens later on, like while your forms are being created? Because all the initialization sections run before any code in the DPR gets run. It that part's taking a long time, you'll have to do some tricky stuff to get your splash screen to show up in front of all of it:

Put the form's unit as close to the top of the .DPR as you can. (But not before things that need to go first, like FastMM). Put the code to show the splash screen in the initialization section of that unit. And make sure that there aren't any units with long initialization periods that your splash screen uses (or that the ones that use it use... or anywhere in the dependency tree.) And then hope that works.

If the slowdown problems don't start until after the initial initialization stack is finished, though, then go with what Jim said.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文