我使用Delphi 7,我的项目有几种非模态可见形式。问题是如果在其中一个中调用了MessageBoxEx,则在MessageBoxEx的表单关闭之前,应用程序的所有操作都不会更新。在我的项目中,它可以打破应用程序的业务逻辑。
在显示MessageBoxEx的窗口时,永远不会调用TApplication.HandleMessage方法,因此它不会调用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);
这是一种更新应用程序所有操作状态的方法。它只是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;
似乎状态的更新经常发生(通常我会找出使用探查器的问题)。
此外,当鼠标的光标没有超过应用程序的窗口时(在我的DualCore Pentium上大约25%),CPU使用率会严重增加。
您如何看待我的问题以及我尝试解决的问题?使用钩子是一个好主意还是有更好的方法来捕获应用程序空闲状态?在设置挂钩时我是否需要使用WH_CALLWNDPROCRET?
为什么MessageBoxEx会阻止TApplication.HandleMessage?有办法防止这种行为吗?我试图用MB_APPLMODAL,MB_SYSTEMMODAL,MB_TASKMODAL标志来调用它,但它没有帮助。
答案 0 :(得分:8)
MessageBox/Ex()
是一个模态对话框,因此它在内部运行自己的消息循环,因为调用线程的正常消息循环被阻止。 MessageBox/Ex()
接收调用线程的消息队列中的任何消息,并将它们正常地发送到目标窗口(所以像基于窗口的定时器之类的东西仍然可以工作,例如TTimer
),但它的模态消息循环没有VCL特定消息的概念,例如动作提升,并将丢弃它们。 TApplication.HandleMessage()
仅由主VCL消息循环,TApplication.ProcessMessages()
方法和TForm.ShowModal()
方法调用(这就是为什么模态VCL窗体窗口不会遇到此问题),其中没有一个在MessageBox/Ex()
运行时调用(对于任何操作系统模式对话框都是如此)。
要解决您的问题,您有几个选择:
在调用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;
将MessageBox/Ex()
调用移动到单独的工作线程,以便调用线程可以正常处理消息。如果您需要等待MessageBox/Ex()
的结果,例如在提示用户输入时,则可以使用MsgWaitForMultipleObjects()
等待线程终止,同时允许等待线程调用{{1每当有待处理的待处理消息时。
Application.ProcessMessages()