Skip to content
This repository has been archived by the owner on May 28, 2021. It is now read-only.
Domagoj Pandža edited this page Sep 26, 2016 · 2 revisions

Introduction

If you're reading this, you're probably on Windows using your favorite browser. You've also noticed that all browsers have something in common. And that common element is likely why you're here. You're wondering how exactly they pull off that customized window frame (sometimes called chrome).

Chances are that you're already aware you can customize the entire window by starting from scratch with the WS_POPUP window style and achieve basically any look. However, Google Chrome, Mozilla Firefox, Opera and others manage to retain the distinct Windows style, while being consistent across all recent Windows versions, all the way back to the infamous Windows Vista.

You may also not feel like reinventing the window frame, generating shadows and basically doing all the boilerplate. You want to integrate your design over the basic frame. You've seen Microsoft's documentation on the subject and found it to be incomplete and that all the available code samples are broken and/or incomplete. You've went into the pit of depravity that are the codebases of mentioned browsers and found nothing but despair. This document and the associated sample are here to correct that, both are intended to be sharp and to the point. So, let us take a look at the details, how the sample works and, as an added bonus, how the various browsers that initially drove you here handle themselves on the Windows platform.

Getting jiggy with DWM

Desktop Window Manager or, as it was previously known, the Desktop Compositing Engine, was introduced in Vista as an improvement over the way desktop was rendered on Windows XP. Essentially, every application wrote to the desktop directly, losing a lot of potential for optimization, as well as power when it comes to visual effects, not to mention that it was the equivalent of specifying the vertices of a complex polygonal object directly in world space.

DWM changed all that, applications now draw to surfaces which are then composited onto the desktop by the system. It also introduced new features for programmers, a whole API which allows more control over the look and feel of windows without doing a rewrite from scratch. We'll be using the DWM API to manipulate the border sizes to accomodate our custom window frame. In the now old new model, applications that don't care too much can simply render to a classic surface through GDI or any other graphics API available on the Windows platform, which is then copied over to video memory where it is composited with the rest of the desktop by DWM. More advanced applications can take more control over the way the content of the window is rendered through DirectComposition, which allows for avoiding the pesky redirection bitmap and constructing optimized window buffers directly in video memory. We do the latter in the sample.

DWM generates the basic window frame whose visual parametrization (roughened glass versus solid) is a function of the version of the operating system, which is semi-programmable and allows the customization of its prominent parts, like the border size and others. These manipulations are considered extensions of the basic window frame and they can be actually pushed into the client area, where they're fair game to be composited over, allowing for the sticking of tabs, buttons and other UI elements directly over the frame.

The 1px border is baked in and cannot be removed through any combination of window style flags and removing it requires also removing the transparent border. This then turns into an ugly padding of the close, maximize and minimize buttons (as well as on the left side where it is harder to perceive).

Creating a custom frame for your application

With everything above in mind, we can begin. The right approach to operating on non-client area is to use the DWM API to expand the frame into the client area, then cut off the unwanted cruft from it by triggering a recalculation of the client area.

MARGINS margins = { ... }
auto hr = DwmExtendFrameIntoClientArea(system_window_handle, &margins);
if (FAILED(hr)) throw std::runtime_error { "DWM failed to extend frame into the client area." };

This will just expand the frame into the client area. The caption area will remain, so will the 1px border (WS_BORDER) and the transparent resizing padding (WS_THICKFRAME). If you wish to remove the standard frame's caption area, but retain the transparent resizing padding, you will have to trigger and handle WM_NCCALCSIZE (SetWindowPos will do):

if (message == WM_NCCALCSIZE) {

    auto client_area_needs_calculating = static_cast<bool>(wparam);

    if(client_area_needs_calculating) {
        auto parameters = reinterpret_cast<NCCALCSIZE_PARAMS*>(lparam);

        auto& requested_client_area = parameters->rgrc[0];
        requested_client_area.right -= GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
        requested_client_area.left += GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
        requested_client_area.bottom -= GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);

        // We got this!
        return 0;
    }

}

We extend the client area on top (by not doing anything) and make sure it doesn't envelop the transparent padding and the 1px border on the sides (shrink). After this, you will have to handle WM_NCHITTEST and according to your margins parametrization, make sure that the resizing handles appear in the right places. A simple variant can be found in the sample.

Is this really all there is to it?

Yes. Rendering can be handled in WM_PAINT or you can introduce your own variable refresh rate by any means you deem necessary. Furthermore, you can draw with GDI, Direct2D, Direct3D or anything else. Either you query the client area DC (which now contains the frame itself), create a D2D render target out of it and draw to it or you opt out of the redirection bitmap and use DirectComposition to construct and draw the surfaces that will be stored in video memory and directly composited onto your window frame by DWM.