Delphi MessageBoxEx停止更新操作
我使用Delphi7,我的项目有几个非模态可见表单。问题是,如果其中一个调用了MessageBoxEx,则在关闭MessageBoxEx表单之前,应用程序的所有操作都不会更新。在我的项目中,它会破坏应用程序的业务逻辑 当MessageBoxEx的窗口显示时,不会调用TApplication.HandleMessage方法,因此它不会调用DoActionIdle,也不会更新操作 我认为我需要的是在应用程序空闲时捕获它的状态,并更新所有操作的状态 首先,我实现了TApplication。OnIdle处理程序:Delphi MessageBoxEx停止更新操作,delphi,delphi-7,Delphi,Delphi 7,我使用Delphi7,我的项目有几个非模态可见表单。问题是,如果其中一个调用了MessageBoxEx,则在关闭MessageBoxEx表单之前,应用程序的所有操作都不会更新。在我的项目中,它会破坏应用程序的业务逻辑 当MessageBoxEx的窗口显示时,不会调用TApplication.HandleMessage方法,因此它不会调用DoActionIdle,也不会更新操作 我认为我需要的是在应用程序空闲时捕获它的状态,并更新所有操作的状态 首先,我实现了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);
状态更新似乎比通常要频繁得多(我将使用profiler找出哪里有问题)
此外,当鼠标指针不在应用程序的窗口上时,CPU的使用率会严重增加(在我的双核奔腾上,CPU的使用率约为25%)
你对我的问题和我解决问题的方法有什么看法?使用钩子是个好主意还是有更好的方法来捕获应用程序空闲状态?在设置挂钩时,我是否需要使用WH_CALLWNDPROCRET
为什么MessageBoxEx会阻止TApplication.HandleMessage?有没有办法防止这种行为?我尝试使用MB_APPLMODAL、MB_SYSTEMMODAL、MB_TASKMODAL标志调用它,但没有任何帮助。MessageBox/Ex()
是一个模式对话框,因此它在内部运行自己的消息循环,因为调用线程的正常消息循环被阻止MessageBox/Ex()
接收调用线程的消息队列中的任何消息,并将它们正常地分派到目标窗口(因此基于窗口的计时器仍然工作,例如TTimer
),但其模式消息循环没有VCL特定消息的概念,例如操作更新,并将丢弃它们TApplication.HandleMessage()
仅由主VCL消息循环、TApplication.ProcessMessages()
方法和TForm.ShowModal()
方法调用(这就是为什么modal VCL窗体窗口不会遇到此问题的原因),在运行MessageBox/Ex()
时,不会调用这些方法(对于任何操作系统模式对话框也是如此)
要解决您的问题,您有两个选择:
MessageBox/Ex()
之前,通过SetWindowsHookEx()
设置线程本地消息挂钩,然后在MessageBox/Ex()
退出后立即释放挂钩。这允许您查看MessageBox/Ex()发出的每条消息
根据需要将它们接收并发送到VCL处理程序。不要在消息挂钩内调用peek消息()
、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;
谢谢你的回答!对我来说,第一种解决方案看起来更吸引人。不幸的是,他们都认为我需要找到MessageBox的所有调用并替换。但我认为这不会太难。什么是Message?它是来自任何旧版本的Delphi的方法吗?嗯。我认为第一种方法不正确,是吗?t应用程序ion在队列中没有消息(没有任何VCL消息来更新操作)时调用DoActionIdle(更新操作),当MessageBox的窗口接收到一些消息时执行钩子时,应用程序变为空闲。我猜这与此不同。
IsPreProcessMessage()
允许MDI MainForm或当前关注的TWinControl
在处理消息时获得第一个DIB。它是在D2005中引入的。至于VCL的空闲处理,为了在第一个解决方案中调用它,您可以调用publicTApplication.DoApplicationIdle()
方法。诀窍在于确定何时调用它,因为您无法检测模式对话框的内部消息循环何时空闲。我可能会使用一个短的一次性计时器,其中每个接收到的消息(重新)设置计时器,然后在它消失时调用DoApplicationIdle()
。
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;
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;