为什么在显示弹出菜单时不会调用Application.OnMessage方法?

9

我使用一个Application.OnMessage事件处理程序来处理程序中其他线程发送的消息(通知)。但是当弹出菜单处于激活状态(打开)时,这个事件处理程序不会被调用。以下是测试代码(没有线程,但原理相同):

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnMessage := ApplicationEvents1Message;
end;

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  if Msg.message = WM_USER then
    Beep();
end;

procedure TForm1.tmr1Timer(Sender: TObject);
begin
  PostThreadMessage(GetCurrentThreadId, WM_USER, 0, 0);
end;
1个回答

8

OnMessage 是从主线程的消息循环中调用的。这个消息循环是在Delphi的VCL库代码中实现的。因此,这个库代码有机会调用OnMessage事件处理程序。

弹出菜单是通过调用Win32函数TrackPopupMenuEx来显示的。该函数实现了一个模态消息循环来运行菜单的跟踪UI。由于这个消息循环是在Win32代码中实现的,所以VCL代码没有机会触发OnMessage事件。Win32代码对VCL一无所知,并运行一个普通的消息循环。消息被服务和分派,但不能执行任何VCL特定的代码。

这是为什么应避免使用PostThreadMessage的完美例子。只有当你控制每个消息循环时才能使用它。其他故障点包括系统消息对话框、拖放模态循环、窗口移动/大小模态循环。

你应该停止使用PostThreadMesaage。相反,在主线程中使用AllocateHWnd创建一个窗口句柄。从你的工作线程向该窗口发送消息。


感谢您详细的回复。请允许我澄清一下:在次要线程中(它们有自己的消息队列),安全地接收通过PostThreadMessage发送的消息,只有当从次要线程发送消息到程序的主线程时,最好在单独窗口的窗口过程中处理消息? - Dmitro25
如果次要线程没有用户界面(UI),正如它们不应该有的那样,并且除了您的代码之外,没有其他东西运行消息循环,则线程消息在那里是安全的。为了保持一致性,您可能会发现使用窗口接收消息更加清晰。但是,由于VCL的长期破损设计,AllocateHWnd不能在主线程之外调用。因此,您必须用自己的版本替换它,这很容易做到,但很烦人。 - David Heffernan
线程消息被模态循环吞噬 - Remy Lebeau
一个稍微简单的方法,使AllocateHWnd线程安全,可以使用一个线程,在消息到达时与主线程同步。我想...也许不是因为我不知道前者需要什么。 - Sertac Akyuz
@sertac,你如何向线程发送消息,因为你不能在线程中调用AllocateHWnd? - David Heffernan
@David - 一个带有 HWnd 0 的线程消息。我刚刚测试了一个概念验证,该线程只调用 MsgWaitForMultipleObjects(0, PHandle(nil)^, False, INFINITE, QS_POSTMESSAGE)。它接收由 PostThreadMessage 发送的任何消息,因为模态仅影响主线程。然后,它与主线程同步没有问题,因为同步不受主线程模态状态的影响。 - Sertac Akyuz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接