将所有 windows 消息捕获到应用程序
Capturing all windows messages to an application
是否有一个应用程序中所有 windows 的全局 WinProc 或在短时间内将所有绘制消息捕获到应用程序的方法?我们使用的一些第三方库将它们自己的 wndproc 添加到控件(例如 DevExpress ribbon、Docking Manager)。
我们有一些代码应该 运行 在后台线程中,但目前不可能。此代码可能需要很长时间才能 运行 并且显然应用程序在此期间变得无响应。我们不能使用像 processmessages 这样的东西,因为那会导致重新输入代码。为了解决这个问题,我认为应该可以创建我们自己的 ProcessMessages 版本来查找特定消息。我的想法是,我会创建一个带有进度条和取消按钮的小面板,然后只允许取消按钮的 windows 句柄的消息。
处理单个消息的简化代码如下所示:
begin
if PeekMessage(aMsg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (aMsg.hwnd = 0) or IsWindowUnicode(aMsg.hwnd);
if Unicode then
MsgExists := PeekMessageW(aMsg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(aMsg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if aMsg.hwnd = aButtonWindowsHandle then
begin
TranslateMessage(aMsg);
if Unicode then
DispatchMessageW(aMsg)
else
DispatchMessageA(aMsg);
end
else
begin
// WM_PAINT is a special message. If you don't handle a WM_PAINT
// then windows will just stick it back in the queue again.
if (aMsg.message = WM_PAINT) and (aMsg.hwnd <> 0) then
begin
// Queue this paint message so we do an update when the
// processing is finished.
FQueuedPaintMessages.Add(aMsg);
// Paint nothing here otherwise Windows will send it again and
// insist that we paint it.
if BeginPaint(aMsg.hwnd, paintStruct) <> 0 then
EndPaint(aMsg.hwnd, paintStruct);
end;
end;
end;
end;
end;
调用如下:
while ProcessMessage(msg) do {loop};
这似乎工作正常,但在测试期间我发现了一些情况,其中 Windows API 调用之一(PeekMeesage、TranslateMessage、DispatchMessage 或两个绘画调用)正在触发通过 ntdll.dll 中的 KiUserCallbackDispatcher 回调我们的停靠控件的 WndProc。对接控制最终会触发一个问题,这是一个问题。我不确定 Windows API 调用它是因为堆栈跟踪在该点缺少一些行。它只发生在我们的一台测试机器上,所以我不能设置断点。
我知道这远非理想的解决方案,我们最好重写代码,以便它可以 运行 在后台线程中运行,但这将是一项巨大的工作量,而且目前不可行。
看起来 SetWindowsHookEx with WH_MOUSE_LL 可能是一个解决方案,但仍然需要一个消息循环。
与其说是您问题的答案,不如说是您问题的潜在解决方案...
不再使用带有“取消”按钮的面板,而是显示一个标签,上面写着“按住 ESC 取消进程”。然后,在您的流程中,您可以通过定期调用 GetAsyncKeyState 来获取 ESC 键的状态。我没有测试它,但我希望它至少能“足够好”地满足您的需求。
所选答案为我提供了解决问题的方法,但@IInspectable 回答了有关捕获所有绘制消息的问题。所以对于其他正在看这个问题的人来说。答案是这样的:
"None 可以通过合理的努力使其可靠地工作。这只是与系统作斗争的笨拙尝试,而系统将赢得那场比赛。"
也就是说。不要尝试去做。
是否有一个应用程序中所有 windows 的全局 WinProc 或在短时间内将所有绘制消息捕获到应用程序的方法?我们使用的一些第三方库将它们自己的 wndproc 添加到控件(例如 DevExpress ribbon、Docking Manager)。
我们有一些代码应该 运行 在后台线程中,但目前不可能。此代码可能需要很长时间才能 运行 并且显然应用程序在此期间变得无响应。我们不能使用像 processmessages 这样的东西,因为那会导致重新输入代码。为了解决这个问题,我认为应该可以创建我们自己的 ProcessMessages 版本来查找特定消息。我的想法是,我会创建一个带有进度条和取消按钮的小面板,然后只允许取消按钮的 windows 句柄的消息。
处理单个消息的简化代码如下所示:
begin
if PeekMessage(aMsg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (aMsg.hwnd = 0) or IsWindowUnicode(aMsg.hwnd);
if Unicode then
MsgExists := PeekMessageW(aMsg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(aMsg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if aMsg.hwnd = aButtonWindowsHandle then
begin
TranslateMessage(aMsg);
if Unicode then
DispatchMessageW(aMsg)
else
DispatchMessageA(aMsg);
end
else
begin
// WM_PAINT is a special message. If you don't handle a WM_PAINT
// then windows will just stick it back in the queue again.
if (aMsg.message = WM_PAINT) and (aMsg.hwnd <> 0) then
begin
// Queue this paint message so we do an update when the
// processing is finished.
FQueuedPaintMessages.Add(aMsg);
// Paint nothing here otherwise Windows will send it again and
// insist that we paint it.
if BeginPaint(aMsg.hwnd, paintStruct) <> 0 then
EndPaint(aMsg.hwnd, paintStruct);
end;
end;
end;
end;
end;
调用如下:
while ProcessMessage(msg) do {loop};
这似乎工作正常,但在测试期间我发现了一些情况,其中 Windows API 调用之一(PeekMeesage、TranslateMessage、DispatchMessage 或两个绘画调用)正在触发通过 ntdll.dll 中的 KiUserCallbackDispatcher 回调我们的停靠控件的 WndProc。对接控制最终会触发一个问题,这是一个问题。我不确定 Windows API 调用它是因为堆栈跟踪在该点缺少一些行。它只发生在我们的一台测试机器上,所以我不能设置断点。
我知道这远非理想的解决方案,我们最好重写代码,以便它可以 运行 在后台线程中运行,但这将是一项巨大的工作量,而且目前不可行。
看起来 SetWindowsHookEx with WH_MOUSE_LL 可能是一个解决方案,但仍然需要一个消息循环。
与其说是您问题的答案,不如说是您问题的潜在解决方案...
不再使用带有“取消”按钮的面板,而是显示一个标签,上面写着“按住 ESC 取消进程”。然后,在您的流程中,您可以通过定期调用 GetAsyncKeyState 来获取 ESC 键的状态。我没有测试它,但我希望它至少能“足够好”地满足您的需求。
所选答案为我提供了解决问题的方法,但@IInspectable 回答了有关捕获所有绘制消息的问题。所以对于其他正在看这个问题的人来说。答案是这样的:
"None 可以通过合理的努力使其可靠地工作。这只是与系统作斗争的笨拙尝试,而系统将赢得那场比赛。"
也就是说。不要尝试去做。