multithreadingdelphibackgroundworkersplash-screentform

Why TApplication.MessageBox closes automatically?


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?


Solution

  • 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.