A customer had an owner-drawn static control, and they found that when the user pressed the Alt key, their static control redrew. This extra redraw was unwanted, presumably because the control takes a long time to draw, and they didn't want to waste the time redrawing something that hadn't changed.
So why does tapping the Alt key cause the owner-draw static control to repaint?
Because the state of the keyboard accelerators has changed.
The customer noticed that on the initial draw,
the itemState
had the numeric value
of 260, whereas the
itemState
is 4 on the unwanted redraw.
The customer noted that 4 is documented as
ODS_
,
but 260 is not documented.
Okay, well, let's note that the value 260 is documented.
It breaks down as 4 | 256
which is
ODS_
.
Let's walk through what's going on here.
When the static control first paints, the window
that contains it is in the "hidden accelerators" state,
so the draw flags are
ODS_
.
The
ODS_
is a bug,
as we saw some time ago.
The
ODS_
is telling you
to do your owner-draw thing, but don't draw any
accelerators.
When the user presses the Alt key, the window changes state to "visible accelerators", so the static control asks you to draw once more, with accelerators, as I discussed some time ago.
Okay, now that we know what's going on, what can we do to stop it?
Once you understand
the way the
WM_
and
WM_
messages interact,
you can see two ways out.
One is to ignore the request to draw the control
if it is happening in response to a
WM_
message.
LRESULT CALLBACK StaticSubclassProc( HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR refData) { LRESULT lres; ParentClass *parentClass = (ParentClass*)refData; switch (wm) { case WM_UPDATEUISTATE: parentClass->ignoreOwnerDraw = true; lres = DefSubclassProc(hwnd, wm, wParam, lParam); parentClass->ignoreOwnerDraw = false; return lres; } return DefSubclassProc(hwnd, wm, wParam, lParam); } class ParentClass { ... void OnDrawItem(const DRAWITEMSTRUCT* pdis) { if (pdis->CtlID == IDC_MYSTATIC && !ignoreOwnerDraw) { DoSlowOwnerDraw(...); } } bool ignoreOwnerDraw = false; }
The idea here is to set a flag if a
WM_
is in progress, and if the handling of the
WM_
message results in a request to redraw the control,
then ignore it.
I leave as an exercise the code to install and remove the subclass procedure. I do this partly as an actual exercise, and partly to avoid me having to write two versions of the answer, depending on whether the parent is a regular window or a dialog box.
Another solution is to prevent the static control from
seeing the
WM_
message at all.
LRESULT CALLBACK IgnoreUIStateChangeSubclassProc( HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam, UINT_PTR id, DWORD_PTR /* refData */) { switch (wm) { case WM_UPDATEUISTATE: return DefWindowProc(hwnd, wm, wParam, lParam); case WM_NCDESTROY: RemoveWindowSubclass(hwnd, IgnoreUIStateChangeSubclassProc, 0); break; } return DefSubclassProc(hwnd, wm, wParam, lParam); } BOOL IgnoreUIStateChange(HWND hwnd) { return SetWindowSubclass(hwnd, IgnoreUIStateChangeSubclassProc, 1, 0); }
I made this subclass procedure self-unregistering
because it has no reference to any other objects,
so there are no lifetime issues with letting the
subclass procedure outlive the parent class.
This makes the function self-contained and consequently
generally useful.
The IgnoreUIStateChange
function
registers the subclass procedure on any control,
at which point the control will ignore any changes
to show or hide accelerators or focus rectangles.
The subclass procedure works by intercepting the
WM_
message and sending it directly to
DefWindowProc
for
default processing,
bypassing any custom processing in the control itself.
Passing the message to
DefWindowProc
allows
the normal message propagation to continue,
but bypassing the control's window procedure
means that the control is never told that the
UI state has changed,
which means that it never tries to redraw itself
to hide or show accelerators or focus rectangles.