MessageBoxEx停止更新操作

tik*_*kit 3 delphi delphi-7

我使用Delphi 7,我的项目有几个非模态可见窗体.问题是如果在其中一个中调用了MessageBoxEx,则在MessageBoxEx的表单关闭之前,应用程序的所有操作都不会更新.在我的项目中,它可以打破应用程序的业务逻辑.

永远不会调用TApplication.HandleMessage方法,同时显示MessageBoxEx的窗口,因此它不会调用DoActionIdle,并且不会更新Actions.

我认为我需要的是在空闲时捕获应用程序的状态并更新所有操作的状态.

首先,我实施了TApplication.OnIdle处理程序:

procedure TKernel.OnIdle(Sender: TObject; var Done: Boolean);
begin
  {It’s only to switch off the standard updating from TApplication.Idle. It's to make the CPU usage lower while MessageBoxEx's window isn't shown }
 Done := False;
end;

implementation

var
  MsgHook: HHOOK;

{Here is a hook}
function GetMsgHook(nCode: Integer; wParam: Longint; var Msg: TMsg): Longint; stdcall;
var
  m: TMsg;
begin
  Result := CallNextHookEx(MsgHook, nCode, wParam, Longint(@Msg));
  if (nCode >= 0) and (_instance <> nil) then
  begin
    {If there aren’t the messages in the application's message queue then the application is in idle state.}
    if not PeekMessage(m, 0, 0, 0, PM_NOREMOVE) then
    begin
      _instance.DoActionIdle;
      WaitMessage;
    end;
  end;
end;

initialization
    MsgHook := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgHook, 0, GetCurrentThreadID);

finalization
  if MsgHook <> 0 then
    UnhookWindowsHookEx(MsgHook);
Run Code Online (Sandbox Code Playgroud)

这是一种更新应用程序的所有操作的状态的方法.它只是TApplication.DoActionIdle的修改版本:

type
  TCustomFormAccess = class(TCustomForm);

procedure TKernel.DoActionIdle;
var
  i: Integer;
begin
  for I := 0 to Screen.CustomFormCount - 1 do
    with Screen.CustomForms[i] do
      if HandleAllocated and IsWindowVisible(Handle) and
        IsWindowEnabled(Handle) then
        TCustomFormAccess(Screen.CustomForms[i]).UpdateActions;
end;
Run Code Online (Sandbox Code Playgroud)

似乎状态的更新经常发生(通常我会找出使用探查器的问题).

此外,当鼠标的光标没有超过应用程序的窗口时(在我的DualCore Pentium上大约25%),CPU使用率会严重增加.

您如何看待我的问题以及我尝试解决它的方式?使用钩子是一个好主意还是有更好的方法来捕获应用程序空闲状态?在设置挂钩时我是否需要使用WH_CALLWNDPROCRET?

为什么MessageBoxEx会阻止TApplication.HandleMessage?有办法防止这种行为吗?我试图用MB_APPLMODAL,MB_SYSTEMMODAL,MB_TASKMODAL标志调用它,但它没有帮助.

Rem*_*eau 8

MessageBox/Ex()是一个模态对话框,因此它在内部运行自己的消息循环,因为调用线程的正常消息循环被阻止. MessageBox/Ex()接收调用线程的消息队列中的任何消息,并将它们正常地发送到目标窗口(所以像基于窗口的定时器之类的东西仍然可以工作,例如TTimer),但是它的模态消息循环没有特定于VCL的消息的概念,比如行动升级,并将丢弃它们. TApplication.HandleMessage()仅由主VCL消息循环,TApplication.ProcessMessages()方法和TForm.ShowModal()方法调用(这就是为什么模态VCL窗体窗口不会遇到此问题),在MessageBox/Ex()运行时都不会调用它们(对于任何操作系统模式都是如此)对话).

要解决您的问题,您有几个选择:

  1. SetWindowsHookEx()在调用之前通过右边设置一个线程本地消息钩子MessageBox/Ex(),然后在MessageBox/Ex()退出后立即释放钩子.这允许您根据需要查看MessageBox/Ex()接收和分发给VCL处理程序的每条消息. 不要打电话PeekMessage(),GetMessage()或者WaitMessage()在消息钩内!

    type
      TApplicationAccess = class(TApplication)
      end;
    
    function GetMsgHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
    var
      Msg: TMsg;
    begin
      if (nCode >= 0) and (wParam = PM_REMOVE) then
      begin
        Msg := PMsg(lParam)^;
        with TApplicationAccess(Application) do begin
          if (not IsPreProcessMessage(Msg))
            and (not IsHintMsg(Msg))
            and (not IsMDIMsg(Msg))
            and (not IsKeyMsg(Msg))
            and (not IsDlgMsg(Msg)) then
          begin
          end;
        end;
      end;
      Result := CallNextHookEx(MsgHook, nCode, wParam, lParam);
    end;
    
    function DoMessageBoxEx(...): Integer;
    var
      MsgHook: HHOOK;
    begin
      MsgHook := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgHook, 0, GetCurrentThreadID);
      Result := MessageBoxEx(...);
      if MsgHook <> 0 then UnhookWindowsHookEx(MsgHook);
    end;
    
    Run Code Online (Sandbox Code Playgroud)
  2. MessageBox/Ex()调用移动到单独的工作线程,以便调用线程可以正常处理消息.如果您需要等待结果MessageBox/Ex(),例如在提示用户输入时,则可以使用MsgWaitForMultipleObjects()等待线程终止,同时允许等待线程Application.ProcessMessages()在有待处理的待处理消息时进行调用.

    type
      TMessageBoxThread = class(TThread)
      protected
        procedure Execute; override;
        ...
      public
        constructor Create(...);
      end;
    
    constructor TMessageBoxThread.Create(...);
    begin
      inherited Create(False);
      ...
    end;
    
    function TMessageBoxThread.Execute;
    begin
      ReturnValue := MessageBoxEx(...);
    end;
    
    function DoMessageBoxEx(...): Integer;
    var
      Thread: TMessageBoxThread;
      WaitResult: DWORD;
    begin
      Thread := TMessageBoxThread.Create(...);
      try
        repeat
          WaitResult := MsgWaitForMultipleObjects(1, Thread.Handle, False, INFINITE, QS_ALLINPUT);
          if WaitResult = WAIT_FAILED then RaiseLastOSError;
          if WaitResult = WAIT_OBJECT_0 + 1 then Application.ProcessMessages;
        until WaitResult = WAIT_OBJECT_0;
        Result := Thread.ReturnVal;
      finally
        Thread.Free;
      end;
    end;
    
    Run Code Online (Sandbox Code Playgroud)