11

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.

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
Brian Clifton
  • 691
  • 7
  • 14
  • 1
    Can we see the manifest file you have created (and linked into your binary)? Although I don't see any indication, that theme rendering were related to an application manifest. Where did you get that information from? – IInspectable Sep 04 '16 at 17:18
  • Manifest added :) Here's a link to the MSDN page where I read that the manifest is tied to Visual Styles: https://msdn.microsoft.com/en-us/library/windows/desktop/bb773175(v=vs.85).aspx – Brian Clifton Sep 04 '16 at 18:16
  • 1
    Manifest files control numerous aspects of an application (such as platform compatibility, COM servers, etc.). The link in your previous comment explains, how to control use of Common Controls version 6. This is required to use Visual Styles (but it's missing from your manifest). There are also 2 ways to deploy manifest files: As an embedded resource as well as a separate file next to your executable image. If both are present, the embedded manifest takes precedence. Have you checked that the manifest you authored is also the one that gets used? – IInspectable Sep 04 '16 at 18:28
  • I went ahead and added a ref to common controls 6 (the article I linked to made it sound like an either/or), but didn't see a difference: https://github.com/bsclifton/BrowserWindowStyles/commit/1699668facfb21fdbed035e7cfeb51314b46356b I have currently have the manifest set as embedded. Let me add a call to GetVersionEx to see what it returns – Brian Clifton Sep 04 '16 at 19:21
  • **Update**: GetVersionEx() is properly returning: **major=10, minor=0, build=10586** (where before adding manifest/common controls, it returned **major=6, minor=2, build=9200**). No change to UI. Maybe I wrongly assumed that enabling Visual Styles would force DrawThemeBackground to use the correct bitmaps :( – Brian Clifton Sep 04 '16 at 19:31
  • 1
    Your [application manifest](https://msdn.microsoft.com/en-us/library/windows/desktop/aa374191.aspx) still doesn't include a reference to the common controls version 6. It only consists of a compatibility section (which is nice to have, but doesn't do anything to reference the common controls version 6). – IInspectable Sep 05 '16 at 10:13
  • Good catch :) I tried updating that last nite but didn't have any luck :( I updated this issue and also here's the commit where I added it: https://github.com/bsclifton/BrowserWindowStyles/commit/556782405b42c9b6b496b9320cc95b55ea5aaff8 – Brian Clifton Sep 05 '16 at 16:29
  • 1
    IIRC the theme files have never been updated for Windows 8 and Windows 10 non-client area? You'll have to see how Firefox implements the window frame, I guess :/ – andlabs Sep 06 '16 at 13:04
  • 2
    @andlabs: Apparently, they are still in the theme files. It's the API that hasn't been updated for Windows 8+ (see [GetThemeStream usage](http://stackoverflow.com/q/34222021/1889329) and [Windows 10 Close, Minimize and Maximize buttons](http://stackoverflow.com/q/34004819/1889329)). – IInspectable Sep 06 '16 at 13:31
  • 2
    Idea 1 should get you going, with a few changes. `GetThemeStream` asks for an `HTHEME` (as returned by `GetThemeData`). I have seen code passing `DwmWindow` as well as `CompositedWindow::Window` as the class list to `OpenThemeData`. None if this is documented, though. Idea 2 is likely not going anywhere. It is - in general - possible to force an invisible window to render itself. – IInspectable Sep 06 '16 at 13:34
  • 2
    That was meant to read *"**not** possible to force an invisible window to render itself"*. – IInspectable Sep 06 '16 at 18:09
  • I didn't forget about this issue, taking a break from it. I'm going to solve the issue (for now) by custom rendering the entire window (including the menu) manually. I'll definitely come back afterwards and dig into the GetThemeData call. The two window approach seemed feasible after reading https://stackoverflow.com/questions/242570/copying-content-from-a-hidden-or-clipped-window-in-xp – Brian Clifton Sep 07 '16 at 17:48
  • 1
    That approach needs coöperation of the receiver of `PrintWindow`. It needs to handle the `WM_PRINT` message. Since you are (or would be in control) that may be possible. I don't know if the default window procedure does respond to `WM_PRINT`, though (it probably does). – IInspectable Sep 07 '16 at 19:44

1 Answers1

3

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.

Community
  • 1
  • 1
zett42
  • 25,437
  • 3
  • 35
  • 72