3

I'm coding a custom Win32 UI control that I want to incorporate visual themes in. I load themes in its WM_NCCREATE as such:

case WM_NCCREATE:
{
    HTHEME hTheme = ::OpenThemeData(hWnd, L"EDIT");
    assert(hTheme);
    assert(::GetWindowTheme(hWnd) != 0);

}
return 1;

and then release them when control is destroyed:

case WM_DESTROY:
{
    HTHEME hTheme = ::GetWindowTheme(hWnd);
    assert(hTheme);
    if(::CloseThemeData(hTheme) != S_OK)
    {
        assert(NULL);
    }
}
break;

This works well, until someone tries to change that control's styles. The following call (just by itself without even changing any styles):

::SetWindowLongPtr(hChildWnd, GWL_STYLE, dwStyle);

will make GetWindowTheme on hChildWnd return NULL.

So, is it a bug or a feature?

PS. To make a reproducible Win32 example I had to adjust the stock Win32 solution from the VS 2017. (Here is its full source code.) The way it works is this: in it I create a small child control (shown in gray below) that has theme in question:

enter image description here

Then when you click on the white area of the main window, I try to change its styles and its theme disappears:

enter image description here

To see the full Win32 code for that project, I also posted it on PasteBin.

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • @RemyLebeau: No, it's supposed to be `GetWindowTheme` and no, there's no handle leak there. It's removed in `WM_DESTROY`. That first assertion shows that the window actually had that theme handle. As for your last point, if I follow your logic, why would I "trust" the HWND to store `WNDCLASSEXW::cbWndExtra` data or window styles, or anything else either? – c00000fd Mar 24 '19 at 03:53
  • @RemyLebeau: That's not the point. `GetWindowTheme` is used in many places to store theme handle. Again, my question is why does `SetWindowLongPtr` seem to reset it? – c00000fd Mar 24 '19 at 03:57
  • 1
    Just like with any other handle type, your WM_NCCREATE should save the opened HTHEME somewhere for WM_DESTROY to close later, don't rely on the HWND remembering the HTHEME for you. And don't forget to handle WM_THEMECHANGED, too. – Remy Lebeau Mar 24 '19 at 04:02
  • 1
    you need save result of `OpenThemeData` in struct accosiated with window and use saved value in call `CloseThemeData`. use `GetWindowTheme` no sense at all, it only returns the **most recent** theme handle from `OpenThemeData`. if another code internally call `OpenThemeData` result will be another already. not reliable api at all – RbMm Mar 24 '19 at 08:20
  • @RbMm: `GetWindowTheme` seems to [be used](https://github.com/wine-mirror/wine/blob/master/dlls/comctl32/edit.c) for that kinda purpose. – c00000fd Mar 24 '19 at 09:43
  • `GetWindowTheme` simply returns the most recent theme handle from `OpenThemeData`. use `OpenThemeData` **once** for open need to you threme. if you open threme - **save** returned handle in class data. also you probably need to call `SetWindowTheme(hwnd, L"Explorer", 0);` – RbMm Mar 24 '19 at 09:57
  • @c00000fd `OpenThemeData()` can be called multiple times on a window, and `GetWindowTheme()` will return only the last opened theme. Each opened HTHEME needs to be closed individually. The safest option is to remember the HTHEME you open so you can close it. Don't expect `GetWindowTheme()` to always return the same HTHEME you opened – Remy Lebeau Mar 24 '19 at 21:14
  • @RemyLebeau: OK, I see that. I guess it's a bug in Wine then. – c00000fd Mar 24 '19 at 21:44
  • @c00000fd "*I guess it's a bug **in Wine** then.*" - the fact that you are not even running your app on a *real* Windows system but in an *emulated* system is a very important piece of information that you should have provided up front. – Remy Lebeau Mar 24 '19 at 21:49

1 Answers1

1

According to Window Styles document:

"After the window has been created, these styles cannot be modified, except as noted."

Because this is not permitted, the theme engine does not always check for changed styles and in some circumstances will draw the caption based on old data. And the only guaranteed and supportable solution is for the application to destroy the window and recreate it with the new styles rather than trying to change them on the fly.

A similar discussion can be found: http://social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/7b5ef777-ff0d-4f15-afed-5588f93f0e23

Drake Wu
  • 6,927
  • 1
  • 7
  • 30