Reputation: 6587
Is there a global WinProc for all windows in an application or a way to capture all paint messages to the application for a brief period of time? Some of the third party libraries that we use add their own wndproc's to controls (e.g. DevExpress ribbon, Docking Manager).
We have some code that should be run in a background thread but that's not possible at the moment. This code can take a long time to run and obviously the application becomes unresponsive during this time. We can't use things like processmessages because that would result in the code being reentered. To get around this I thought it should be possible to create our own version of ProcessMessages that looks for specific messages. The idea is that I would create a small panel with a progress bar and a cancel button and then only allow messages for the cancel button's windows handle.
The simplified code to process a single message looks something like this:
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;
This is called as follows:
while ProcessMessage(msg) do {loop};
This appeared to work correctly but during testing I picked up a couple of cases where one of the Windows API calls (PeekMeesage, TranslateMessage, DispatchMessage, or the two painting calls) is triggering a call back to our docking control's WndProc via KiUserCallbackDispatcher in ntdll.dll. The docking control is ultimately triggering a paint which is a problem. I am not sure what Windows API call it is because the stack trace is missing some lines at that point. It only happens on one of our test machines so I can't put in a breakpoint.
I am aware that this is far from an ideal solution and that we would be better off rewriting the code so that it can be run in a background thread but that would be a huge amount of work and is not feasible at this time.
It looks like SetWindowsHookEx with WH_MOUSE_LL might be a solution but that still needs a message loop.
Upvotes: 1
Views: 643
Reputation: 6587
The selected answer gave me a solution to my problem but @IInspectable answered the question about catching all of the paint messages. So for other people looking at that question. The answer is this:
"None of this can be made to work reliably with reasonable effort. It's just a clumsy attempt at fighting the system, and the system is going to win that one."
In other words. Don't try and do it.
Upvotes: 1
Reputation: 6467
Not so much an answer to your question than maybe a potential solution to your problem...
Instead of having a panel with a "cancel" button, instead display a label saying "Press and hold ESC to cancel process". Then, within your process you can get the status of the ESC key by calling GetAsyncKeyState at regular interval. I did not test it, but I'd expect it to work at least "well enough" for your needs.
Upvotes: 3