Re: Owner Draw STATIC Window with XP Themes
From: Doug Harrison [MVP] (dsh_at_mvps.org)
Date: Thu, 11 Nov 2004 12:34:45 -0600
Jeff Partch [MVP] wrote:
>"Doug Harrison [MVP]" <email@example.com> wrote in message
>> Jonathan Wood wrote:
>> >Well, IsAppThemed will return TRUE even though a manifest resource is not
>> >present so I questioned its reliability. But I guess you know if you are
>> >including a manifest resource or not. Hopefully, it is reliable given
>> >the manifest is included.
>> IME, it is. You can test by turning themes off on a system-wide basis and
>> also just for your specific application through the .exe's properties.
>Don't ge too annoyed by my jumping in here, but I think you are both right.
>My recollected understanding of IsThemeActive and IsAppThemed is:
>1) they will both return TRUE when themes are active system wide
>2) except that IsAppThemed will return FALSE only in the case where themes
>are disabled in the compatibility properties for the application.
>Neither concerns itself with whether this particular app has a manifest and
>is supposed to use themes and neither is particularly determinate as to
>whether this particular control is supposed to use themes -- only that it
>*could* -- not not that it *should*. That leaves the similarly flawed
>GetThemeAppProperties, and GetWindowTheme which I discuss below.
I'm with you so far. ISTR a problem with IsThemeActive, causing me to use
IsAppThemed, which has been reliable for me in apps known to have a
>Another hack I use for the standard windows controls and which I've posted
>before is to test whether the GetClassLongPtr(hWnd, GCLP_HMODULE) is equal
>to GetModuleHandle(_T("Comctl32.dll"), and which I think does consider the
>"has a manifest" issue, but probably not the
>SetThemeAppProperties(~STAP_ALLOW_CONTROLS) and SetWindowTheme(L"", L"")
>cases. And IIRC, there are folks doing things with the activation context to
>disable theming after the fact even in an otherwise themed app with a
Example? I'm so not familiar with activation contexts. I guess if you were
going for full generality, you'd have to worry about those things.
>> Here's the function which helps maintain the theme state, called during
>> PreSubclassWindow and in response to WM_THEMECHANGED:
>> if (OsVersion::AtLeastXP())
>> m_hTheme = (UserEx::IsAppThemed())
>> ? OpenThemeData(m_hWnd, L"button")
>> : 0;
>FYI, According to Dave Anderson, Microsoft Developer Support, you can't
>really do this, Doug...
>> You should open a theme once per window... open it when the window is
>> created and close the handle when the window is destroyed. There is a
>> of 64K handles per process. The bug is when this limit is reached,
>> OpenThemeData does not return NULL.
>Even allowing an exception for WM_THEMECHANGED, you're now doing it twice
>per window. :)
Can't do what? Each OpenThemeData has a matching CloseThemeData. The
WM_THEMECHANGED docs say:
To release an existing theme handle, call CloseThemeData. To acquire a new
theme handle, use OpenThemeData.
Following the WM_THEMECHANGED broadcast, any existing theme handles are
invalid. A theme-aware window should release any pre-existing theme handles
when it receives the WM_THEMECHANGED message. It may optionally open a new
theme handle if the IsThemeActive function returns TRUE.
Then there's the CloseThemeData docs:
The CloseThemeData function should be called when a window that has a visual
style applied is destroyed. Also this function is used when a window
receives a WM_THEMECHANGED message followed by an attempt to create a new
theme data handle.
Now, if you're saying each HWND maintains a set of HTHEMEs opened on it,
such that it's an error to open the same theme twice on a window, then I'd
say, "Well, CloseThemeData doesn't take an HWND, it takes only an HTHEME, so
how can this list be maintained?" The only potential way I can see to
"revoke" an HTHEME is to call OpenThemeData on an imaginary theme, but that
would be exceedingly bizarre.
I just wrote a little app to determine if the HTHEMEs are reference-counted,
and it appears they are. This program has a dialog box with OK and Cancel
buttons, with the OK button subclassed. I called OpenThemeData once in my
button class's PreSubclassWindow, and it didn't mess up (drew solid black
rectangle for the OK and Cancel buttons) until I called CloseThemeData three
times. I deleted the Cancel button, and it messed up after calling
CloseThemeData twice. Finally, I got rid of the OpenThemeData call, and it
messed up after calling CloseThemeData once on the HTHEME returned by
GetWindowTheme for the OK button. This is consistent with what I've observed
for the 3+ years I've been using themes, namely, the approach I'm using
works fine, because the HTHEMEs are reference-counted.
Using Windows XP Visual Styles
The following code sample demonstrates how to draw a button control.
hTheme = OpenThemeData(hwndButton, "Button");
DrawMyControl(hDC, hwndButton, hTheme, iState);
I've actually done the following for my hover button, which overrides the
DrawBackground function I presented earlier to use the toolbar style for the
"show button" state, which looks better:
You can use parts from other controls and render each part separately. For
example, for a calendar control that consists of a grid, you can treat each
square formed by the grid as a toolbar button. Do the following to program
the squares as toolbar buttons.
Call OpenThemeData as follows:
OpenThemeData (hwnd, "Toolbar");
You can mix and match control parts by calling OpenThemeData multiple times
for a given control and using the appropriate theme handle to draw different
This all implies you needn't worry about any themes already opened on the
>Presumably a subclass should use GetWindowTheme to obtain, "the most recent
>theme handle from OpenThemeData". But...
>Back in July, I reported what I thought was a bug regarding
>"So here's the situation: I'm working on this NM_CUSTOMDRAW handler for a
>themed pushbutton on XPSP1. I'm using VC7.1 and a MFC7 dialog-based
>application (although I don't believe MFC is the culprit). Now, in my
>NM_CUSTOMDRAW handler, I have occasion to call GetWindowTheme..., but every
>button gains and looses the focus the call returns NULL".
>So if the question the control is asking is, "as an independent component of
>any application running on any OS under any externally imposed or inherited
>variable conditions, am I supposed to be using themes right now"?. my
>subjective conclusion is that there is no *one* API that answers this
>question, and even in consort all of them together may not answer the
>question with absolute certainty. At best, you end up with an increasingly
>more educated guess. FWIW.
The Visual Styles documentation is among the worst in MSDN. It was horribly
incomplete and ambiguous when it first came out, and reviewing it now, it
seems not to have improved much. For my purposes, IsAppThemed() has worked
reliably to determine if controls should be themed, but then again, I don't
have any controls that have disabled themes with SetWindowTheme, and as
mentioned earlier, I'm not concerned with apps that may not have a manifest.
These considerations aside, I've observed zero problems with the method I've
been using for 3+ years, and I've had no reports of any problems with it for
apps that run on Windows 9x and NT4 and later. The documentation gives no
hint the approach shouldn't work; on the contrary, it gives examples
indicating it should work. The test app I just wrote seems to confirm the
approach is sound. So as long as it ain't broke... :)
-- Doug Harrison Microsoft MVP - Visual C++