Displaying splash screen in Delphi when main thread is busy Displaying splash screen in Delphi when main thread is busy multithreading multithreading

Displaying splash screen in Delphi when main thread is busy


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.


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 dialogconstructor 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 marqueeend;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 0x00000008IDD_WAITDLG DIALOGEX 10,10,162,33STYLE WS_POPUP|WS_VISIBLE|WS_DLGFRAME|DS_CENTEREXSTYLE WS_EX_TOPMOSTBEGIN  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,9END

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.


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.