【发布时间】:2021-08-06 09:53:17
【问题描述】:
是否有适用于应用程序中所有窗口的全局 WinProc 或在短时间内捕获应用程序的所有绘制消息的方法?我们使用的一些第三方库将自己的 wndproc 添加到控件(例如 DevExpress 功能区、对接管理器)。
我们有一些代码应该在后台线程中运行,但目前还不可能。此代码可能需要很长时间才能运行,并且显然应用程序在此期间变得无响应。我们不能使用像 processmessages 这样的东西,因为这会导致代码被重新输入。为了解决这个问题,我认为应该可以创建我们自己的 ProcessMessages 版本来查找特定消息。我的想法是,我将创建一个带有进度条和取消按钮的小面板,然后只允许为取消按钮的窗口句柄发送消息。
处理单个消息的简化代码如下所示:
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 或两个绘画调用)正在通过 KiUserCallbackDispatcher 触发对停靠控件的 WndProc 的回调在 ntdll.dll 中。停靠控制最终会触发油漆,这是一个问题。我不确定 Windows API 调用它是什么,因为此时堆栈跟踪缺少一些行。它只发生在我们的一台测试机器上,所以我不能设置断点。
我知道这远非理想的解决方案,我们最好重写代码以便它可以在后台线程中运行,但这将是大量工作,目前不可行.
看起来带有 WH_MOUSE_LL 的 SetWindowsHookEx 可能是一个解决方案,但仍然需要一个消息循环。
【问题讨论】:
-
通过合理的努力,这些都无法可靠地工作。这只是对抗系统的笨拙尝试,系统将赢得那个。只需修复您的应用程序,不要到处安装笨拙的解决方法,人们将随着时间的推移开始使用,然后您无法返回并删除它。您不会通过积累更多的技术债务来对抗技术债务。
-
@IInspectable 有一个项目正在进行中以从 UI 中破解代码,但不幸的是,这是一个为期 1 年的项目。我希望找到一种解决方法,让客户可以在此之前取消长期运行的流程。这肯定不会是人们会随着时间的推移开始使用的东西。
-
在这一切开始之前显示一个不同的窗口(解释程序变得没有响应),隐藏你当前的窗口,然后让它滚动。之后恢复当前窗口。对于“目前”无法将所有内容塞入自己的线程的借口,这应该是足够的解决方法。
-
可以只清除更新区域而不是 Begin/EndPaint 对,虽然我不明白这部分代码的用途。
-
如果要禁止更新窗口内容,可以使用 WM_SETREDRAW。