I have an app that takes a few seconds to load (lots of initializations). The GUI freezes during startup. So I want to create a splash screen that fades in and out as the application loads. I use TBackgroundWorker component to do the animation in a background thread.
However, something strange happens when I use this component: when it signals "work complete" (see BackgroundWorkerWorkComplete) a message dialog that I open meanwhile is automatically closed.
procedure TMainForm.ButtonStartSplashClick(Sender: TObject);
VAR
frmSplash: TfrmSplash;
begin
frmSplash:= TfrmSplash.Create(NIL);
frmSplash.StartAnimation;
//MessageBox(Handle, 'Hi', nil, MB_OK); // This remains on screen
Application.MessageBox(PChar('Hi'), PChar('Box'), MB_ICONINFORMATION); // This is automatically closed when the background thread is done
end;
And this is the splash screen:
procedure TfrmSplash.StartAnimation;
begin
Show;
BackgroundWorker.Execute;
end;
procedure TfrmSplash.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:= caFree;
end;
procedure TfrmSplash.BackgroundWorkerWork(Worker: TBackgroundWorker);
VAR i: Integer;
begin
for i:= 1 to 255 DO
begin
AlphaBlendValue:= i; // do not access GUI directly from thread
Sleep(30);
end;
end;
procedure TfrmSplash.BackgroundWorkerWorkComplete(Worker: TBackgroundWorker; Cancelled: Boolean);
begin
Close; // At this point, the msg box will be closed also
end;
What I find strange is that MessageBox remains on screen while Application.MessageBox does not (is automatically closed).
Why closing TfrmSplash will close the message box also?
TApplication.MessageBox is a wrapper around the WinAPI MessageBox function. The code for the former shows you how it's called :
function TApplication.MessageBox(const Text, Caption: PChar; Flags: Longint): Integer;
var
ActiveWindow, TaskActiveWindow: HWnd;
MBMonitor, AppMonitor: HMonitor;
MonInfo: TMonitorInfo;
Rect: TRect;
FocusState: TFocusState;
WindowList: TTaskWindowList;
begin
ActiveWindow := ActiveFormHandle;
if ActiveWindow = 0 then
TaskActiveWindow := Handle
else
TaskActiveWindow := ActiveWindow;
{ ... }
try
Result := Winapi.Windows.MessageBox(TaskActiveWindow, Text, Caption, Flags);
finally
Notice that the HWND passed to the WinAPI call is TaskActiveWindow
, which is taken as the active window at the time the call is made (unless there is none, in which case the application's handle is used instead). Since you've just created your TFrmSplash, it will be the active window and the message box will be disposed when its parent (your splash window) is closed.
When you simply call MessageBox directly :
MessageBox(Handle, 'Hi', nil, MB_OK); // This remains on screen
You are passing Handle
, which is implicitly the handle of the form from which you're calling the code, in this case your TMainForm
, so the main form becomes the owner in this case and has no relation to the splash screen.