Почему MessageBox не блокирует приложение в синхронизированном потоке?

насколько я понимаю и знаю метод класса TThread, если вы синхронизируете свой код, он фактически выполняется в основном потоке приложения (так же, как таймер/buttonclick/etc.) Я играл и заметил, что MessageBox не блокирует основное приложение, однако сон делает так, как ожидалось. Почему так?

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;

3 ответов


в этом ответе есть две части.

часть 1 хорошо объясняется в если MessageBox () / related синхронны, почему мой цикл сообщений не замерзает?. Функция MessageBox не блокирует, она просто создает диалоговое окно со своим собственным циклом сообщений.

часть 2 объясняется в документация MessageBox.

hWnd: дескриптор окна владельца создаваемого окна сообщения. если этот параметр имеет значение NULL, окно сообщения не имеет окна владельца.

при отображении модального диалога Windows отключает его владельца, но если вы передадите 0 для первого параметра, нет владельца и нечего отключать. Поэтому ваша программа будет продолжать обрабатывать сообщения (и реагировать на них), пока отображается окно сообщения.

чтобы изменить это поведение, передайте дескриптор формы в качестве первого параметра. Например:

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

Я подозреваю, что вопрос сводится к тому, что вы имеете в виду, когда говорите:

окно сообщения не блокирует основное приложение.

Я понимаю это так, что, когда вы показываете окно сообщения, ваша форма VCL все еще может взаимодействовать. Проблема здесь не связана с потоками, и я предлагаю удалить их из уравнения. Ваше понимание чего Synchronize делает звук.

вопрос полностью связан с концепцией а окна, а как модальные диалоговые окна ведут себя по отношению к своим хозяевам. Обратите внимание, что под владельцем я не имею в виду свойство Delphi TComponent.Owner, но я имею в виду значение Windows API владелец.

создайте приложение VCL и нажмите две кнопки на форме. Добавьте следующее OnClick обработчики.

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;

теперь посмотрите, что происходит, когда вы нажимаете на Button1. Окно сообщения отображается, но вы все равно можете нажать на форму VCL. И сравните с Button2. Когда он показывает окно сообщения, с формой VCL нельзя взаимодействовать.

при отображении модального диалогового окна его владелец отключается. В случае Button2, владельцем является форма VCL. И как только форма отключена, вы не можете взаимодействовать с ней. В случае Button1, нет владельца, и поэтому модальное диалоговое окно не отключает любое другое окно. Вот почему с формой VCL можно взаимодействовать.

Raymond Chen имеет длинную серию на модальность в своем старом блоге New Thing:


Synchronize выполнит код в Mainthread.
Хорошее объяснение можно найти здесь Синхронизация в классе Delphi TThread

вам просто нужно будет запретить пользователю взаимодействовать с формами вашего приложения, например. by

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

используя MessageBoxA, как вы это сделали, не помешает Mainthread реагировать на эти события triggerd взаимодействием ueser с вашими формами, просто попробуй!--7-->

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

MessageBoxA

что синхронизация будет выполнена в основном потоке, можно показать (IMHO) с помощью

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;