The MASM Forum

General => The Workshop => Windows API => Topic started by: NoCforMe on May 13, 2024, 04:49:07 AM

Title: The mysteries of WM_PAINT, pt. I
Post by: NoCforMe on May 13, 2024, 04:49:07 AM
Well, I learned a little something about WM_PAINT just now.
In my li'l VU meter toy demo, I had code like this in my WM_TIMER handler that drove the demo animation:
; Erase previous needle image:
    MOV    NeedleBMPhandle, NULL
    INVOKE InvalidateRect, MeterDispHandle, <addr. of rect.>, FALSE
    .  .  .  .
; Draw next needle image:
    MOV    NeedleBMPhandle, <handle from structure>
    INVOKE InvalidateRect, MeterDispHandle, <addr. of rect.>, FALSE
I threw some logging code into the WM_PAINT handler of the meter-display proc, and was surprised to find that ... my NeedleBMPhandle was never getting set to NULL! (I'm using this value as a flag to indicate that an image needs to be erased rather than drawn.)

I couldn't figure out what was going on here--after all, I was explicitly setting that variable to NULL before the InvalidateRect() call, so why was it never coming up NULL in the WM_PAINT handler?

Then I thought about it a bit more and remembered that WM_PAINT is actually a low-priority message in Windows, and that apparently there was enough delay in handling that paint request (the InvalidateRect()) that by the time it was sent the handle had been reset to non-NULL. And then I ran across this comment in a thread on the topic on Stack Overflow (https://stackoverflow.com/questions/7216161/determine-priority-of-a-window-message):
QuoteThe 'synthesized from the window state' clause is what makes WM_PAINT and WM_TIMER appear to have a low priority. And why moving the mouse rapidly doesn't flood the message queue with mouse messages. That is however not exclusive, you can for example call UpdateWindow() to force a WM_PAINT message to be sent, making it being dispatched with a 'high priority'.

Well, sure enough, when I changed my code to this:
; Erase previous needle image:
    MOV    NeedleBMPhandle, NULL
    INVOKE InvalidateRect, MeterDispHandle, <addr. of rect.>, FALSE
    INVOKE UpdateWindow, MeterDispHandle
it worked like a charm.

Title: Re: The mysteries of WM_PAINT, pt. I
Post by: kcvinu on July 30, 2024, 03:04:56 AM
That's a helpful tip. Thank you  :winking:
Title: Re: The mysteries of WM_PAINT, pt. I
Post by: Greenhorn on August 03, 2024, 10:49:14 PM
Quote from: NoCforMe on May 13, 2024, 04:49:07 AMWell, sure enough, when I changed my code to this:
; Erase previous needle image:
    MOV    NeedleBMPhandle, NULL
    INVOKE InvalidateRect, MeterDispHandle, <addr. of rect.>, FALSE
    INVOKE UpdateWindow, MeterDispHandle
it worked like a charm.

You can also use RedrawWindow (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-redrawwindow) ...
Title: Re: The mysteries of WM_PAINT, pt. I
Post by: jj2007 on August 04, 2024, 04:30:25 AM
I am surprised that you need UpdateWindow after InvalidateRect. Have you tried to set bErase to TRUE?
Title: Re: The mysteries of WM_PAINT, pt. I
Post by: NoCforMe on August 04, 2024, 05:26:50 AM
I was surprised too. I'll have to go back and check that.
Title: Re: The mysteries of WM_PAINT, pt. I
Post by: Greenhorn on August 04, 2024, 08:39:31 AM
If you want the client area to be redrawn immediately after a call of InvalidateRect you need to call UpdateWindow. Or you take both calls in one step with RedrawWindow ...

; Erase previous needle image:
    MOV    NeedleBMPhandle, NULL
    INVOKE RedrawWindow, MeterDispHandle, <addr. of rect.>, RDW_ERASE or RDW_INVALIDATE or RDW_UPDATENOW