13

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.

menjaraz
  • 7,551
  • 4
  • 41
  • 81
Harriv
  • 6,029
  • 6
  • 44
  • 76

6 Answers6

9

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.

mghie
  • 32,028
  • 6
  • 87
  • 129
7

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.

Fr0sT
  • 2,959
  • 2
  • 25
  • 18
3

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.

Jim McKeeth
  • 38,225
  • 23
  • 120
  • 194
  • > 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.
    I disagree, alt-tabbing to another application and back will cause the splash screen to invalidate.
    – mghie Dec 23 '08 at 13:28
  • I really need "live" screen, so getting those paint messages is important. – Harriv Dec 23 '08 at 14:07
  • @mghie Technically true I guess. – Jim McKeeth Dec 23 '08 at 20:25
0

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!

mj2008
  • 6,647
  • 2
  • 38
  • 56
  • Splash forms work the same in Delphi 2007 as in previous versions, when you create them with a line SplashForm := TSplashForm.Create(nil); and let them free itself. The first Application.CreateForm() sets Application.MainForm, but that has always been working that way, AFAIK. – mghie Dec 23 '08 at 13:17
  • 1
    The problem is that frmSplash.Update doesn't help much if main thread is blocked for a long period of time. I need the splash screen on screen to communicate that "yes, this program is running, please wait" – Harriv Dec 23 '08 at 14:02
0

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.

Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
  • 1
    Using a secondary thread for the splash would solve the problem. Unfortunately using the VCL is out of the question in that case. But secondary user interface threads with their own message pump are possible using raw Windows API calls. – mghie Dec 23 '08 at 13:30
  • I need "live" splash screen to indicate that the application is really running. – Harriv Dec 23 '08 at 14:04
0

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.

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • The initialization section is not a problem, the long wait happens when I set the component active in FormCreate (or later). – Harriv Dec 24 '08 at 22:47