Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parts drawn with DrawThemeBackground on Windows 10 are not correct

Problem description

I want to create a Windows API app in C which renders the menu and the caption buttons in the same non-client area, similar to Firefox notice the menu and caption buttons

In order to do this, I've determined the solution needs to:

  • be type WS_POPUP, in order for the menu to be aligned to the top
  • take ownership of the non-client area (where the menu is rendered)
  • manually render the minimize/maximize/close buttons

The solution needs to work on Windows 7, 8, and 10 (and ideally future versions too).

How it looks now

I have a test program available on GitHub.

In my app, I've overridden the appropriate events:

WM_NCCALCSIZE, WM_NCHITTEST, WM_NCLBUTTONDOWN, WM_NCLBUTTONUP, WM_NCMOUSEMOVE, WM_NCPAINT

And then I repaint non-client areas on these events: WM_NCACTIVATE, WM_SETTEXT

Here's an example of how I'm doing the rendering:

// globals set elsewhere
RECT customAreaRect, minRect, maxRect, closeRect, coverMenuRect;
BOOL maximized;

// ...

LRESULT OnPaintNCA(HWND hWnd, WPARAM wParam, LPARAM lParam) {
  RECT windowRect;
  HRGN hRgn = NULL;
  GetWindowRect(hWnd, &windowRect);
  if (wParam == 1) {
    hRgn = CreateRectRgnIndirect(&windowRect);
  } else {
    hRgn = (HRGN)wParam;
  }

  if (hRgn) {
    // Carve out the area for custom content
    HRGN captionButtonRgn = CreateRectRgnIndirect(&customAreaRect);
    CombineRgn(hRgn, hRgn, captionButtonRgn, RGN_XOR);
    DeleteObject(captionButtonRgn);

    // Force default painting for non-client area
    LRESULT ret = DefWindowProc(hWnd, WM_NCPAINT, (WPARAM)hRgn, 0);

    // black background covering part of menu, behind buttons
    HDC hDC = GetWindowDC(hWnd);
    FillRect(hDC, &coverMenuRect, (HBRUSH)GetStockObject(BLACK_BRUSH));

    HTHEME hTheme = OpenThemeData(hWnd, TEXT("WINDOW"));

    DrawThemeBackground(hTheme, hDC, WP_MINBUTTON, partState, minRect, NULL);
    DrawThemeBackground(hTheme, hDC, maximized ? WP_RESTOREBUTTON : WP_MAXBUTTON, partState, maxRect, NULL);
    DrawThemeBackground(hTheme, hDC, WP_CLOSEBUTTON, partState, closeRect, NULL);

    CloseThemeData(hTheme);
  }
}

The rendered result looks like this: screenshot showing what I have now Unfortunately, the styles used for the parts (minimize, maximize/restore, close) look like the styles for Windows 7/8, and not the native Windows 10 controls. I've been searching for a way to do this for several days without luck. I need help understanding how to render these buttons for Windows 10 using the Windows API.

Current status (and what I've tried so far)

My first hunch was that I need to properly enable Visual Styles.

  • Per this article, calls checking the OS version will get Windows 8 unless you specifically target Windows 10 via a manifest. Click here to view my manifest. This does work:

    • Before GetVersionEx returned major=6, minor=2, build=9200
    • Now GetVersionEx returns major=10, minor=0, build=10586
  • Per the official "Enabling Visual Styles" article, I made sure to use Common Controls version 6.

    • Added linker input for Comctl32.lib
    • Call is made to InitCommonControls on program start
    • Added dependency for version 6 to the application manifest

Here are some screenshots of relevant project settings that I have tried: target platform version

embed manifest

Other ideas

I'm running low on things to try. Before throwing in the towel, there were some things I was going to try:

  • Idea 1: using GetThemeStream which allows you to retrieve the size/bitmap for controls.

    • Load aero msstyles file like so:

    HMODULE themeFile = LoadLibraryEx(TEXT("C:\\Windows\\Resources\\Themes\\aero\\aero.msstyles"), NULL, LOAD_LIBRARY_AS_DATAFILE);

    • Get the bitmap for the part (minimize button, maximize button, etc) like so (passing the loaded theme file):

    GetThemeStream(h, WP_MAXBUTTON, MAXBS_NORMAL, TMT_DISKSTREAM, (void**)&buffer, &bufferSize, themeFile);

    • Load the bitmap; it appears to be in PNG format (I haven't gotten this far)
    • Draw the bitmap
  • Idea 2: copy the non-client area from a hidden window which has the caption area (and minimize, maximize, close buttons).

    • Create a window which has the caption and min/max buttons, never activating it.
    • In the non-client paint, get the DC for that Window and capture the pixels for the min/max/close button
    • Render them using bitblt
like image 722
Brian Clifton Avatar asked Sep 04 '16 17:09

Brian Clifton


1 Answers

I think the issue comes from trying to use WM_NCPAINT on OS version >= Win Vista. Since Vista all NC rendering is controlled by DWM (desktop window manager). If you still dare to handle WM_NCPAINT, DWM rendering will be turned off and you get "old-school" look:

From the Shell Revealed Blog:

The DWM doesnt have any legacy worries because applications cannot draw inside the glass frame, since its rendered and managed by a totally different process. If an application tries to do it, Windows will detect it and remove the glass frame entirely (and therefore revert to the Basic frame), so that the application can draw what it wants to draw.

To get proper results, you have to do it the "DWM way" (specifically the sections "Removing the Standard Frame" and "Drawing in the Extended Frame Window"). This works by letting DWM render the frame within the client area, so you can paint over it. Also with this solution you don't have to draw the caption buttons on your own. This answer summarizes the required steps (under "Aero supported solution").

The caveat is that you propably have to draw the menu on your own and you can't use most of GDI API for that, because GDI ignores the alpha channel and things will look ugly if the frame is translucent (Vista and Win 7 by default, Win8+ with extensions). BitBlt() works if the source is a memory DC that contains a 32bpp bitmap with an alpha channel. GDI+ works aswell.

like image 171
zett42 Avatar answered Nov 15 '22 22:11

zett42