Why does a MessageBox not block the Application on a synchronized thread?
Asked Answered
T

3

9

As far as I understand and know the method of the TThread Class, if you synchronize your code, it actually get's executed in the main Application Thread (just like a timer/buttonclick/etc.) I've been playing around and noticed that a MessageBox DOES NOT block the main application, however sleep does just as expected. Why is that?

type
  TTestThread = class(TThread)
  private
    procedure SynchThread;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
  end;

procedure TTestThread.SynchThread;
begin
 MessageBoxA (0, 'Hello', 'Test', 0);
end;

procedure TTestThread.Execute;
begin
 Synchronize (SynchThread)
end;

constructor TTestThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FreeOnTerminate := True;
end;

procedure StartThread;
var
 TestThread : TTestThread;
begin
 TestThread := TTestThread.Create (FALSE);
end;
Therapist answered 29/3, 2013 at 3:29 Comment(2)
Explain what you mean by "block the main app"Nims
related: delphigroups.info/2/11/544013.htmlManhattan
T
13

There are two parts to this answer.

Part 1 is nicely explained in If MessageBox()/related are synchronous, why doesn't my message loop freeze?. The MessageBox function is not blocking, it merely creates a dialog box with its own message loop.

Part 2 is explained in the MessageBox documentation.

hWnd: A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.

When you display a modal dialog, Windows disables its owner, but if you pass 0 for the first parameter, there is no owner and nothing to disable. Therefore, your program will continue to process messages (and react to them) while the message box is displayed.

To change this behaviour, pass form's handle as a first parameter. For example:

procedure TTestThread.SynchThread;
begin
  MessageBoxA (Form1.Handle, 'Hello', 'Test', 0);
end;
Toluidine answered 29/3, 2013 at 7:31 Comment(1)
Your part 2 is not accurate. Having no owner doesn't change which message loop pulls off messages. MessageBox is still synchronous, and until it returns, its message loop pulls off the queued messages. When you pass an owner window to a modal dialog, the modal dialog disables the owner, its owner and so on.Nims
N
12

I suspect that the question boils down to what you mean when you say:

A message box does not block the main application.

What I take this to mean is that when you show the message box, your VCL form can still be interacted with. The issue here is unrelated to threads and I suggest we remove them from the equation. Your understanding of what Synchronize does is sound.

The issue is entirely related to the concept of a window's owner, and how modal dialog windows behave with respect to their owners. Note that by owner, I don't mean the Delphi property TComponent.Owner, but I mean the Windows API meaning of owner.

Create a VCL app and drop two buttons on the form. Add the following OnClick handlers.

procedure TForm1.Button1Click(Sender: TObject);
begin
  MessageBox(0, 'Not owned', nil, MB_OK);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  MessageBox(Handle, 'Owned by the VCL form', nil, MB_OK);
end;

Now observe what happens when you click on Button1. The message box shows, but you can still click on the VCL form. And compare with Button2. When it shows the message box, the VCL form cannot be interacted with.

When a modal dialog window is shown, the dialog window disables its owner. In the case of Button2, the owner is the VCL form. And once the form is disabled, you cannot interact with it. In the case of Button1, there is no owner and so the modal dialog window does not disable any other window. That's why the VCL form can be interacted with.

Raymond Chen has a long series on modality at his Old New Thing blog:

Nims answered 29/3, 2013 at 9:0 Comment(0)
B
4

Synchronize will execute the code in the Mainthread.
A good explanation can be found here Synchronization in Delphi TThread class

You just will have to prevent user from interacting with the forms of your application, eg. by

procedure TTestThread.SynchThread;
begin
MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);      
end;

using MessageBoxA as you did, won't prevent the Mainthread from reacting on those events triggerd by ueser interaction with your forms, just try

procedure TForm4.Button2Click(Sender: TObject);
begin
    MessageBoxA (0, 'Hello', 'Test', 0);
   // vs
   //  MessageBoxA (0, 'Hello', 'Test', MB_TASKMODAL);
end;

MessageBoxA

that synchronize will be executed in the main thread can be shown (IMHO) by

type
  TTestThread = class(TThread)
  private
    FSync:Boolean;
    FCalled:TDateTime;
    procedure SynchThread;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean;sync:Boolean);
  end;

procedure TTestThread.SynchThread;
begin
 MessageBox (0,PChar(DateTimeToStr(FCalled)+#13#10+DateTimeToStr(Now)),'Hello' , 0);
end;

procedure TTestThread.Execute;
begin
 sleep(100); // give Caller Time to fell asleep
 if Fsync then Synchronize (SynchThread) else SynchThread;
end;

constructor TTestThread.Create(CreateSuspended: Boolean;sync:Boolean);
begin
  inherited Create(CreateSuspended);
  FSync := Sync;
  FCalled :=Now;
  FreeOnTerminate := True;
end;

procedure StartThread(sync:Boolean);
var
 TestThread : TTestThread;
begin
 TestThread := TTestThread.Create (FALSE,sync);
end;

procedure TForm4.RunUnsynchronizedClick(Sender: TObject);
begin
   StartThread(false);// no sync
   Sleep(5000);       // Stop messageloop
end;

procedure TForm4.RunSynchronizedClick(Sender: TObject);
begin
   StartThread(true); // sync
   Sleep(5000);       // Stop messageloop
end;
Bobker answered 29/3, 2013 at 7:3 Comment(6)
So why is this being downvoted, seems to me that the MB_TASKMODAL param is a good hint. @Bobker why not passing the mainform's window handle?Jeannajeanne
@Jeannajeanne before my first edit I had a call with application.handle, too. The downvote will be caused by different points of view about "blocking".Bobker
Anyone out there know how MB_TASKMODAL works? Does it disable all top level windows owned by the calling process?Nims
@DavidHeffernan that's how I would understand the MSDN, but you would not ask if I'm wrong in my point of view?Bobker
@Bobker No, I'm not trying to set any traps for you? I ask for the plain and simple reason that I do not know the answer! :-)Nims
@DavidHeffernan msdn docs are quite clear: all the top-level windows belonging to the current thread are disabledJeannajeanne

© 2022 - 2024 — McMap. All rights reserved.