代码之家  ›  专栏  ›  技术社区  ›  Harriv

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

  •  12
  • Harriv  · 技术社区  · 16 年前

    我想在加载应用程序时显示初始屏幕。但是,一些第三方组件在初始化过程中会阻塞主线程几秒钟,这会导致所有表单都不更新。有没有可能有带自己线程的启动屏幕,这样当主线程忙时它也会更新?

    应用程序是win32和delphi版本2007。

    编辑:我试图避免“未绘制的初始屏幕”效果,如果其他窗口(来自其他应用程序)位于初始屏幕的顶部,例如切换到其他应用程序并返回。

    6 回复  |  直到 16 年前
        1
  •  9
  •   mghie    15 年前

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

    如果你走这条路(我认为你不应该走这条路,因为这是为了微利而做的大量工作),一定要遵守从多个线程访问Windows API的规则。例如,谷歌的“用户界面线程”获取更多信息。

    编辑:

    我以前不知道,但实际上有一个组件实现了 Threaded Splashscreen for Delphi 在codeCentral上。使用这个组件可能(还没有尝试过)很容易将启动屏幕设置在不同的线程中,但是对从辅助线程访问VCL的警告仍然存在。

        2
  •  4
  •   Fr0sT    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_* 定义。我必须添加它们,因为Delphi7对这些常量一无所知。 常数的定义( Dlg.inc )

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

    (我使用自动生成包含文件的RADASM资源编辑器)。

    我们的经验

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

        3
  •  3
  •   Jim McKeeth    16 年前

    首先在DPR中创建启动屏幕,但不要使用 应用程序.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,您可能希望联系第三方组件供应商,让他们停止阻塞您的主线程。

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

    这将与 应用程序.mainformontaskbar 也设置为“真”。

        4
  •  0
  •   mj2008    16 年前

    我用always-on-top设置在启动代码中创建splash,然后在适当的地方使用frmsplash.update以确保它可见并更新。创建的主窗体就是这样一个调用它的地方。

    问题是,Delphi2007假定第一个表单现在是主表单,并且在核心代码中无法替换主表单,所以splashes不再那么好了。也许旧的VisualBasic解决方案拥有一个快速的小喷溅应用程序,然后运行主应用程序,实际上可能会更好!

        5
  •  0
  •   Toon Krijthe Paul    16 年前

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

    如果启动屏幕没有改变,这不是问题。

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

        6
  •  0
  •   Mason Wheeler    16 年前

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

    将表格单位尽可能靠近.dpr的顶部。(但不是在需要先去的事情之前,比如fastmm)。将代码显示在该单元的初始化部分。并确保没有任何具有长初始化周期的单元是您的启动屏幕使用的(或使用它的单元使用的是…或者依赖树中的任何地方),然后希望它能工作。

    但是,如果直到初始初始化堆栈完成后才开始出现减速问题,那么按照Jim所说的进行操作。