Windows Vista for Developers – Part 3 – The Desktop Window Manager

Although Windows 95 (and Window NT 3.51) introduced Windows developers to non-rectangular windows, through the ability to set the window region for a given window using the SetWindowRgn function, this did not provide transparency since the window was still completely opaque and merely afforded the developer some control over the window’s shape. Windows 2000 introduced layered windows by means of the WS_EX_LAYERED extended window style and developers were finally able to control not only the shape but also the level of transparency within the window region. Windows Vista introduces yet another twist on the road away from non-rectangular windows by allowing developers to apply translucency to portions of a window.

In this part 3 of the Windows Vista for Developers series, we are looking at the Desktop Window Manager (DWM) API. The DWM is responsible for the composition of windows on the desktop and the DWM API allows developers to control how composition affects a particular window. As you will see, the DWM is responsible for much more than just “glass”. We will also look at the existing functionality for transparency that Windows Vista inherited from Windows 2000 and see how it complements the new DWM functionality.

Terminology

When it comes to graphics, the terminology can be confusing. Here are some common terms you should be familiar with when working with transparency and translucency in Windows.

Transparency – Refers to the ability to see through something clearly and without obstruction. Think of it as clear glass. Some applications and APIs use the term transparency to refer to a scale that ranges from “completely” transparent to “completely” opaque.

Translucency – People often use translucency and transparency interchangeably but they actually mean very different things. Translucency refers to the ability to see through something where the background appears unclear whether it is out-of-focus or simply blurry in some way. Windows Vista refers to the glass effect as “transparent glass” when it technically should be called translucent glass.

Opacity – Opacity refers to the state of being opaque and opaque refers to something that is neither transparent nor translucent. Some applications and APIs use the term opacity to refer to a scale that ranges from completely opaque to completely transparent.

Alpha Channel – An alpha channel provides additional information for each pixel in an image that facilitates compositing images together.

Window Regions – A window’s region determines the area within the window where system permits painting. Although Windows 95 supported window regions, it was not until Windows XP that the default theme used regions to present windows with rounded corners. Although the default Windows Vista theme also presents windows with rounded corners, regions are no longer used unless you resort to the Windows Vista Basic theme.

Glass – Glass is the catchy marketing terms that Windows Vista uses to refer to translucency.

Blur – Some of the DWM APIs refer to blur and again this indicates translucency. Presumably, the Windows developers felt it was easier to spell and comprehend.

Desktop Composition – The DWM performs desktop composition, enabling visual effects on the desktop such as glass, 3D window transitions, etc.

RGB – RGB is short for Red, Green and Blue. RGB values are typically packed into a COLORREF (which is just a DWORD) as follows: 0x00BBGGRR. As you can see, the first byte is always zero and the remaining three bytes store the individual red, green and blue values in reverse order. Each color value ranges from zero through 255. If all three values are zero then the result is black. If all three values are 255 then the result is white. For example, to represent red specify 0x000000FF. You can also use the RGB macro as follows: RGB(255, 0, 0). As you can see, RGB does not provide an alpha channel.

ARGB – ARGB is short for Alpha, Red, Green and Blue. ARGB values are typically packed into an ARGB (which is just a DWORD) as follows: 0xAARRGGBB. The first byte stores the alpha value and the remaining three bytes store the red, green and blue values. Note that the color values are stored in the opposite order to RGB.

GDI – The Windows Graphics Device Interface (GDI) API is the original graphics interface used for 2D drawing in Windows. With the exception of a few newer functions, the GDI API does not honor the alpha channel in images. GDI uses RGB values to represent color.

GDI+ – GDI+ was introduced with Windows XP (and Windows Server 2003) to provide a more capable programming model for 2D drawing, imaging and typography and fully supports alpha blending. GDI+ uses ARGB values to represent color. Incidentally, GDI+ powers the the System.Drawing library in the .NET Framework.

Is Composition Enabled?

With the terminology out of the way, we can now dive into the guts of desktop composition. Of course, to take advantage of it we need to make sure that it is actually available at runtime. The user may have disabled desktop composition for performance reasons. This can be done as follows:

1. Open the System Properties window using the following command:

    %windir%\system32\SystemPropertiesAdvanced.exe

2. Click the Performance Settings button.

3. Either check or clear the “Enabled desktop composition” check box.

Keep in mind that desktop composition is independent of “glass”. Although glass requires desktop composition, you can use desktop composition while disabling glass.

Windows Vista provides the DwmIsCompositionEnabled function to determine whether composition is currently enabled. Consider the following example:

BOOL enabled = FALSE;
HRESULT result = ::DwmIsCompositionEnabled(&enabled);

Of course, this is not especially useful if you are targeting older platforms as your application will fail to load since it will be linked to a library that is not available. One solution is to use a blend of delay loading and runtime dynamic linking. The DWM API is provided by the dwmapi.dll library and to ensure that your application will load on older platforms you can use Visual C++’s Delay Load facility to only load the DWM library if it is actually used. The first step is to instruct the linker to delay load the DWM library. This can be done as follows:

1. Open the project’s property pages.

2. Navigate to the Linker > Input section.

3. Add dwmapi.dll to the list of Delay Load DLLs. You should already have dwmapi.lib in the list of Additional Dependencies to allow the linker to find the various DWM API functions that you make use of.



With this in place, the DWM library will only be loaded the first time you call any of its functions, but how do you know whether it is safe to do so? After all, calling DwmIsCompositionEnabled will attempt to load the DWM library and crash your application on older versions of Windows. The solution is to manually attempt to load the DWM library and for good measure attempt to retrieve the address of a DWM function. Consider the following wrapper function:

bool IsCompositionEnabled()
{
    HMODULE library = ::LoadLibrary(L"dwmapi.dll");
    bool result = false;

    if (0 != library)
    {
        if (0 != ::GetProcAddress(library,
                                  "DwmIsCompositionEnabled"))
        {
            BOOL enabled = FALSE;
            result = SUCCEEDED(::DwmIsCompositionEnabled(&enabled)) && enabled;
        }

        VERIFY(::FreeLibrary(library));
    }

    return result;
}

The IsCompositionEnabled function attempts to load the DWM library and retrieve the address of the DwmIsCompositionEnabled function. If this is successful, you can assume it is running on Windows Vista or later. It then simply calls the DwmIsCompositionEnabled function, which will actually load the DWM library, to determine whether composition is enabled. Now you simply have to ensure that you do not call any other DWM functions if IsCompositionEnabled returns false.

The other thing to keep in mind is that since the user, and other applications for that matter (more on this in a moment), can enable and disable desktop composition at any time, your application needs to be able to cope with changes in the availability of desktop composition. The system will send your window the WM_DWMCOMPOSITIONCHANGED message to indicate that the availability of desktop composition has changed. The WPARAM and LPARAM values are not used so you must call the DwmIsCompositionEnabled function again to determine the current state of desktop composition.

As I hinted at, it is possible for applications to temporarily disable desktop composition for the lifetime of the application or for some subset thereof. The DwmEnableComposition function allows you to disable desktop composition for the entire desktop. Desktop composition will be disabled until you call DwmEnableComposition again to enable it. If you fail to re-enable desktop composition, the system will automatically enable it when the application exits.

The following code disables composition:

HRESULT result = ::DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);

And the following code will enable it again:

HRESULT result = ::DwmEnableComposition(DWM_EC_ENABLECOMPOSITION);

Assuming you are using the default Windows Vista theme, these calls will toggle between the new “Window Vista” and “Windows Vista Basic” themes. Just remember, when you application exits, composition will be reset regardless of whether you re-enabled it.

Is Composition Translucent?

As I mentioned in the previous section, the fact that desktop composition is enabled does not necessarily mean that the “glass” is translucent. I took the following two window clippings of the exact same window. The one on the left was taken with translucent glass and the one of the right with opaque glass. As you can see, the translucent glass provides a hint of the desktop background color as well as the recycle bin hiding underneath the window whereas the opaque glass only offers the aurora effect provided by the DWM.


Users can set the translucency as well as the composition color, the color used for rendering glass, as follows:

1. Right-click on the desktop and select the “Personalize” command.

2. Click the “Window Color and Appearance” link.

Applications can determine whether composition is opaque or translucent as well as the composition color by calling the DwmGetColorizationColor function as follows:

Gdiplus::ARGB color = 0;
BOOL opaque = FALSE;

HRESULT result = ::DwmGetColorizationColor(&color,
                                           &opaque);

Since the user can change these settings at any time, windows are notified of changes by means of the WM_DWMCOLORIZATIONCOLORCHANGED message. The WPARAM provides the new ARGB value and LPARAM is zero if composition is translucent and non-zero if composition is opaque.

Blurring the Client Area

Assuming desktop composition is enabled, the DWM will take care of rendering the non-client area of your window with glass. The client area however is opaque by default and applications must specifically request glass for all or part of the client area. The DwmEnableBlurBehindWindow function allows you to control the blur effect for a window. The function accepts a handle to a window as well as a pointer to a DWM_BLURBEHIND structure defined as follows:

struct DWM_BLURBEHIND
{
    DWORD dwFlags;
    BOOL fEnable;
    HRGN hRgnBlur;
    BOOL fTransitionOnMaximized;
};

The following flags are defined and they indicate which of the remaining fields the function should take note of:

DWM_BB_ENABLE
DWM_BB_BLURREGION
DWM_BB_TRANSITIONONMAXIMIZED

The DwmEnableBlurBehindWindow takes a bit of getting used to. Consider using the following C++ wrapper function that simplifies its use considerably:

HRESULT EnableBlurBehindWindow(HWND window,
                               bool enable = true,
                               HRGN region = 0,
                               bool transitionOnMaximized = false)
{
    DWM_BLURBEHIND blurBehind = { 0 };
    
    blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_TRANSITIONONMAXIMIZED;
    blurBehind.fEnable = enable;
    blurBehind.fTransitionOnMaximized = transitionOnMaximized;

    if (enable && 0 != region)
    {
        blurBehind.dwFlags |= DWM_BB_BLURREGION;
        blurBehind.hRgnBlur = region;
    }

    return ::DwmEnableBlurBehindWindow(window,
                                       &blurBehind);
}

Now enabling or disabling blur behind is straightforward. Here are a few examples:

Enable blur behind client area:

HRESULT result = EnableBlurBehindWindow(window);

 

Disable blur behind client area:

HRESULT result = EnableBlurBehindWindow(window,
                                        false);

Blur a region of the window:

CRgn rgn;
rgn.CreateEllipticRgn(30, 30, 170, 170);

HRESULT result = EnableBlurBehindWindow(window,
                                        true,
                                        rgn);


 
Render the blur behind as if the window were maximized:

HRESULT result = EnableBlurBehindWindow(window,
                                        true,
                                        0,
                                        true);


 
As you can see, the DwmEnableBlurBehindWindow function provides a lot of functionality and with a little help from C++ it becomes quite simple to make use of. The ability to blur a region of the window is especially useful when combined with layered windows.

Extending the Window Frame

You may have noticed in the window clippings in the previous section that although the client area was blurred, the client edge was still visible. If you want to render glass seamlessly then you need to use a different approach. Of course, if your window does not have a frame then DwmEnableBlurBehindWindow is good for the full extent of the window.

To extend the frame into the window’s client area, you need to use the aptly named DwmExtendFrameIntoClientArea function. Unlike DwmEnableBlurBehindWindow, the DwmExtendFrameIntoClientArea function is very straightforward and you can simply use it directly.

The DwmExtendFrameIntoClientArea function accepts a window handle as well as a pointer to a MARGINS structure. The structure indicates how far into the client area the frame should be extended. The following example extends the bottom margin 20 pixels into the client area:

MARGINS margins = { 0 };
margins.cyBottomHeight = 20;

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
                                                &margins);


 
To restore the frame margins simply set all the margins to zero:

MARGINS margins = { 0 };

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
                                                &margins);

The DwmExtendFrameIntoClientArea function also has a little bonus feature that allows you to render the entire client and non-client area as a seamless sheet of glass. Just set any margin to -1:

MARGINS margins = { -1 };

HRESULT result = ::DwmExtendFrameIntoClientArea(m_hWnd,
                                                &margins);


 
Painting

Up to this point, I have focused on the DWM functions for control blurring. What I have not yet mentioned is the secret sauce you need to get the blurring to kick in. Then of course, there is the issue of what to do with all that lovely glass. Presumably, you want to draw something on it!

The trick with understanding how glass works is having an understanding of the relationship between the DWM functions and the contents of your window. The DwmEnableBlurBehindWindow and DwmExtendFrameIntoClientArea functions promise to instruct the DWM to render as glass any portions of your window that are painted with an alpha channel brush that is not completely opaque. Consider the following window clipping displaying a PNG image I created using Photoshop:


 
The image painted on the window includes an alpha channel so the DWM faithfully blurs the background based on the transparency level of each individual pixel. The catch is that virtually all of the GDI functions that Windows developers have learnt to use do not actually have any knowledge of alpha values in colors and do not perform alpha blending. Therefore, if you are serious about transparency and translucency on Windows you need to use GDI+ (or some other graphics library). Before we look at GDI+ however, let us see what can be done with good old GDI.

It so happens that the bit pattern for RGB black (0x00000000) is the same as the bit pattern for 100% transparent ARGB so you can actually draw with “black” GDI brush and assuming you’ve instructed the DWM to blur the painted area, the result will be the desired glass effect. Let us look at a simple example of this using ATL:

class SampleWindow :
    public CWindowImpl<SampleWindow, CWindow, CFrameWinTraits>
{
public:

    BEGIN_MSG_MAP(SampleWindow)
        MSG_WM_ERASEBKGND(OnEraseBackground)
    END_MSG_MAP()

    SampleWindow()
    {
        VERIFY(Create(0, 0, L"Sample"));

        const MARGINS margins = { -1 };

        COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                                  &margins));
    }

private:

    virtual void OnFinalMessage(HWND)
    {
        ::PostQuitMessage(0);
    }

    bool OnEraseBackground(CDCHandle dc)
    {
        CRect rect;
        VERIFY(GetClientRect(&rect));

        dc.FillSolidRect(&rect,
                         RGB(0, 0, 0));

        return true; // Yes, I erased the background.
    }
};

Just after the window is created, the DwmExtendFrameIntoClientArea function is used to instruct the DWM to render the entire client area seamlessly with glass. The window then handles the WM_ERASEBKGND message and fills the client area with “black” as needed. The results are as you might expect:


 
The trouble with using this technique for rendering glass is that anything you might want to draw on your window better not use a black GDI brush otherwise it will also appear translucent. Consider a dialog box with an edit control:


 
If we apply the technique of using a black GDI brush to this window, the results will be less the desirable:


 
As you can see, since the control uses a black brush to draw the text, the DWM is fooled into thinking that this too should be rendered translucent. One solution is to owner-draw the control. I don’t know about you but I do not want to spend my time “owner drawing” all my controls. I really like the way the system does it and I would rather not have the responsibility of trying to duplicate it.

A more pragmatic solution is to make use of layered windows. Layered windows, first introduced with Windows 2000, are intimately aware of alpha blending, and thus ideally suited for rendering glass. Layered windows offer two distinct programming models. You can either use the UpdateLayeredWindow  function and provide a device-independent bitmap to define the complete appearance of a window on screen (the hard way) or you can use the SetLayeredWindowAttributes function (the easy way). Let us start with the easy way.

The SetLayeredWindowAttributes function allows you to specify an RGB color so that any pixels painted with this color will be transparent. This frees the black brush from performing double-duty and you can once again draw controls using the regular black brush as needed. Assuming your window has the WS_EX_LAYERED extended window style, you can call SetLayeredWindowAttributes as follows to set the transparency color:

const COLORREF color = RGB(200, 201, 202);

VERIFY(::SetLayeredWindowAttributes(window,
                                    color,
                                    0,
                                    LWA_COLORKEY));

Choosing an appropriate color is the hardest part. Ideally, you should use a color that will blend well but it is unlikely that you will know what color to blend with. Font smoothing will also not be perfect, as it will be blending with the transparency color instead of the actual background. Nevertheless, it is an adequate solution in many cases. It is also important that you specify a color that is unlikely to be used by any controls drawn on your window. The SetLayeredWindowAttributes function also tries to provide some of the more advanced capabilities of the UpdateLayeredWindow  function for those stuck in GDI land. One such feature is the ability to create non-rectangular windows. This is somewhat off-topic but I mention it here only because SetLayeredWindowAttributes provides this functionality only if you choose a transparency color with the red, gren and blue values being equal. This does not mix well with the DWM as the glass will appear translucent but the user’s mouse will be directed to the window underneath.

With that, here is a complete example of using layered window translucency with a dialog box:

class SampleDialog :
    public CDialogImpl<SampleDialog>
{
public:

    enum { IDD = IDD_SAMPLE };

    BEGIN_MSG_MAP(MainWindow)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_ERASEBKGND(OnEraseBackground)
        COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
    END_MSG_MAP()

    SampleDialog() :
        m_transparencyKey(RGB(200, 201, 202))
    {
        // Do nothing
    }

private:

    LRESULT OnInitDialog(HWND /*control*/,
                         LPARAM /*lParam*/)
    {
        SetWindowLong(GWL_EXSTYLE,
                      GetExStyle() | WS_EX_LAYERED);

        VERIFY(::SetLayeredWindowAttributes(m_hWnd,
                                            m_transparencyKey,
                                            0,
                                            LWA_COLORKEY));

        const MARGINS margins = { -1 };

        COM_VERIFY(::DwmExtendFrameIntoClientArea(m_hWnd,
                                                  &margins));

        return TRUE; // Yes, go ahead and set the keyboard focus.
    }

    bool OnEraseBackground(CDCHandle dc)
    {
        CRect rect;
        VERIFY(GetClientRect(&rect));

        dc.FillSolidRect(&rect,
                         m_transparencyKey);

        return true; // Yes, I erased the background.
    }

    LRESULT OnCancel(WORD /*notifyCode*/,
                     WORD identifier,
                     HWND /*window*/,
                     BOOL& /*handled*/)
    {
        VERIFY(EndDialog(identifier));
        return 0;
    }

    const COLORREF m_transparencyKey;

};

The results are considerably better than before:


 
The main difference between the SampleDialog class and the previous SampleWindow class using a black GDI brush is that in the SampleDialog example SetLayeredWindowAttributes is used to set the transparency color and the WM_ERASEBKGND message handler fills the client area with the transparency color instead of with black. Considering how little work is involved, this is certainly an attractive solution if you cannot afford to go all out owner-drawing controls.

Advanced Layered Windows

The previous section covered the use of layered windows by means of the SetLayeredWindowAttributes function to provide translucency without much work. For greater control, you need to take it on yourself to prepare the layered window’s off-screen bitmap yourself rather than relying on the system to compose it for you. Although using GDI+ might seem ideal, repeatedly drawing in a GDI+ bitmap and then converting it to a screen-compatible bitmap can be expensive. Fortunately, ATL provides the CImage class that provides just enough functionality so that you can avoid the overhead of GDI+ bitmaps while being able to take advantage of the drawing power of GDI+. The resulting image can then be efficiently copied to the screen sing the UpdateLayeredWindow function. The beauty of using UpdateLayeredWindow is that it honors the entire alpha channel of the image and not just a single transparency color. This allows you to use different levels of transparency in your window quite simply.

Let us walk through a simple example.

First, we will use the CImage class to create an image with an alpha channel and GDI+ to fill the image with a gradient brush starting with transparent in the top-left corner and ending with black in the bottom-right corner. Start by creating the bitmap as follows:

CImage image;

VERIFY(image.Create(300, // width
                    300, // height
                    32, // bits per pixel
                    CImage::createAlphaChannel));

This creates a device-independent bitmap (DIB) section 300 pixels square with an alpha channel. You can now use the GDI+ Graphics class to draw to the bitmap. CImage’s GetDC method is used to pass the Graphics class a device context in which to draw. Once the Graphics class is destroyed, CImage’s ReleaseDC method must be called. The Graphics object can then be used to do whatever drawing you require. The following example fills the bitmap with a linear gradient brush:

{
    Gdiplus::Graphics graphics(image.GetDC());
    GDIPLUS_VERIFY(graphics);

    Gdiplus::Rect rect(0,
                       0,
                       image.GetWidth(),
                       image.GetHeight());

    Gdiplus::LinearGradientBrush brush(rect,
                                       Gdiplus::Color(0, 0, 0, 0),
                                       Gdiplus::Color(255, 0, 0, 0),
                                       Gdiplus::LinearGradientModeForwardDiagonal);

    GDIPLUS_VERIFY(graphics.FillRectangle(&brush,
                                          rect));
}

image.ReleaseDC();

The braces are used simply to ensure that the Graphics object is destroyed before calling the ReleaseDC method on the CImage object. The rest is just GDI+ code that is beyond the scope of this article but should be self-explanatory.

With the image prepared, it is time to call UpdateLayeredWindow to update the layered window based on the bitmap. UpdateLayeredWindow requires a source DC to copy from so we can once again use the GetDC and ReleaseDC pair of methods:

CPoint windowPosition(0, 0);
CPoint layerPosition(0, 0);

CSize size(image.GetWidth(),
           image.GetHeight());

BLENDFUNCTION blendFunction = { 0 };
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags = 0;
blendFunction.SourceConstantAlpha = 255;
blendFunction.AlphaFormat = AC_SRC_ALPHA;

VERIFY(::UpdateLayeredWindow(m_hWnd,
                             0, // obtain screen DC
                             0,
                             &size,
                             image.GetDC(),
                             &layerPosition,
                             0, // no color key
                             &blendFunction,
                             ULW_ALPHA));

image.ReleaseDC();

The results are what you might expect:


 
With the layered window in place, it is time to turn that transparency to translucency and achieve the glass effect. This is simple matter of calling the EnableBlurBehindWindow helper function described in the section entitled “Blurring the Client Area” and you are done:


 
The sample in the download for this article provides a more complete example and allows you to load an arbitrary PNG file to render in either a regular window or in a layered window, with or without glass.

DWM Window Attributes

There are a handful of additional window attributes that the DWM allows you to get or set using the DwmSetWindowAttribute function. Some are particularly interesting.

The DWMWA_TRANSITIONS_FORCEDISABLED attribute allows you to disable the animation that occurs when your window transitions between the restored, maximized and minimized states.

The DWMWA_ALLOW_NCPAINT attribute allows you to draw to the non-client area of the window, ensuring that what you draw is visible once desktop composition is done with it. Be very careful with this, as it is usually glaringly obvious when the painting conflicts with the glass resulting in an undesirable appearance. A good example of this is the beta 2 build of Word 2007 where the non-client area is poorly drawn (fortunately, this has been cleaned up in subsequent builds of Word 2007).

The DWMWA_FLIP3D_POLICY attribute allows you to control how the Flip3D feature (Windows key + Tab) handles your window. You can use the value DWMFLIP3D_EXCLUDEBELOW attribute value to instruct Flip3D to exclude your window from the animation and instead display your window behind the stack of rotating windows.
    
The sample in the download for this article allows you to experiment with a number of the DWM-provided window attributes.

Thumbnails

The final DWM feature I want to talk about is the ability to host your own thumbnails. You may have noticed the neat “thumbnail” feature provided by the Windows Vista task bar. Hovering your mouse over an application item in the taskbar results in a little thumbnail popping up allowing you to get a peak at the window without having to switch to it.

Did you know that the thumbnail is actually live? If you do not know what I mean then try the following experiment:

1. Right-client on the date/time on the taskbar and select the “Adjust Date/Time” command. A window should appear that includes wonderfully rendered analog clock.

2. Hover your mouse over the window’s task bar item and wait for the thumbnail to appear.

If you look closely, you should see the clock’s second hand moving in the thumbnail and as it turns out, the DWM exposes this feature so that you can add live thumbnails to your own applications!

The way thumbnails works is that you “register” a source and destination window with the DWM and as part of desktop composition, it will take care of updating the destination window to reflect the source window. It is amazingly powerful considering how little code you need to write to get it all working. Since registering a thumbnail results in a handle that must be used with subsequent calls to the DWM to update the thumbnail properties and ultimately unregister the thumbnail, it makes sense to use a C++ class to provide a light abstraction:

class Thumbnail
{
public:

    explicit Thumbnail(HTHUMBNAIL handle = 0) :
        m_handle(handle)
    {
        // Do nothing
    }

    ~Thumbnail()
    {
        if (0 != m_handle)
        {
            COM_VERIFY(Unregister());
        }
    }

    bool IsRegistered() const
    {
        return 0 != m_handle;
    }

    HRESULT Register(HWND destination,
                     HWND source)
    {
        ASSERT(0 == m_handle);
        CSize reserved(0, 0);

        return ::DwmRegisterThumbnail(destination,
                                      source,
                                      &reserved,
                                      &m_handle);
    }

    HRESULT Unregister()
    {
        ASSERT(0 != m_handle);
        HRESULT result = ::DwmUnregisterThumbnail(m_handle);
        m_handle = 0;
        return result;
    }

    HRESULT QuerySourceSize(CSize& size)
    {
        ASSERT(0 != m_handle);

        return ::DwmQueryThumbnailSourceSize(m_handle,
                                             &size);
    }

    HRESULT UpdateProperties(const DWM_THUMBNAIL_PROPERTIES& properties)
    {
        ASSERT(0 != m_handle);

        return ::DwmUpdateThumbnailProperties(m_handle,
                                              &properties);
    }

private:

    HTHUMBNAIL m_handle;
};

Registering and unregistering the thumbnail is self-explanatory. The QuerySourceSize method allows you to determine the size of the source window so that you can factor that into your thumbnail properties. The UpdateProperties method must be called at least once to instruct the DWM to begin updating the live thumbnail. The DWM_THUMBNAIL_PROPERTIES structure offers some control over the behavior of the thumbnail.

struct DWM_THUMBNAIL_PROPERTIES
{
    DWORD dwFlags;
    RECT rcDestination;
    RECT rcSource;
    BYTE opacity;
    BOOL fVisible;
    BOOL fSourceClientAreaOnly;
};

The dwFlags field indicates which subsequent fields are populated.

The rcDestination field indicates the bounds in the client area of the destination window in which to render the thumbnail. Include the DWM_TNP_RECTDESTINATION flags to use this field.

The rcSource field indicates the bounds of the source window to include in the thumbnail. This is used to display only a portion of the source window in the thumbnail. Include the DWM_TNP_RECTSOURCE flag to use this field.

The opacity field indicates the level of opacity to use when blending the source with the destination window background. If the source window is semi-transparent or translucent it will automatically be alpha blended so this field just provides an additional degree of control. Include the DWM_TNP_OPACITY flag to use this field.

The fVisible field allows you to turn the thumbnail blending on or off temporarily without having to unregister the thumbnail completely. Include the DWM_TNP_VISIBLE flag to use this field.

Finally, the fSourceClientAreaOnly field allows you to limit the thumbnail to exclude the non-client area of the source window. This is incidentally the way the task bar displays thumbnails. Include the DWM_TNP_SOURCECLIENTAREAONLY flag to use this field.

The sample in the download for this article demonstrates the use of a live thumbnail by providing a resizable thumbnail window.

Sample

The sample project provided in the download for this article demonstrates much of the functionality described in the article. Most of the controls affect the image and thumbnail windows directly so you can play around with the options and the windows will automatically update.

 

That concludes my coverage of the transparency and translucency features of Windows Vista.

Daniel Moth also covered glass basics in C# here.

Update: I have written another DWM article that talks about Controls and the Desktop Window Manager.

 

Read part 4 now: User Account Control


© 2006 Kenny Kerr

 

29 Comments

  • I do have to eat crow, but the relevant statement was not quoted by Ari Glaizel. The relevant statement is in section 1.9, paragraph 18:

    > In the evaluation of each of the expressions
    > a && b
    [...]
    > using the built-in meaning of the operators in these expressions
    > (5.14, 5.15, 5.16, 5.18), there is a sequence point after the
    > evaluation of the first expression[12].

    The reason why I am sensitive to this is that at least three Microsoft employees (who really are Microsoft employees) have put undefined code in articles in their blogs. One of them was something like i = i++ which very clearly doesn't contain a sequence point and is listed in some FAQs. The other two involved completely unrelated areas of the standard, not sequence points. But this made me overly sensitive and I screwed up this time.

    In terms of facts I was equally wrong in this case as three MS employees were in the other cases, and I have no excuse.

    In terms of safety it doesn't hurt to break up a complicated expression into two shorter bullet-proof expressions regardless of whether the original expression was wrong or the complainer was wrong ^_^ Of course that doesn't excuse me, it's just an observation about safety ^_^

    In future I'll try to apply my oversensitivity to myself as well as other programmers, and delay a couple of days before posting such fiercely written mistakes again.

    Yours sincerely,
    Norman Diamond

  • Norman, setting aside legal jargon, the problem you&rsquo;re alluding to usually manifests itself when you attempt to modify a variable more than once within a single expression. That can often result in undefined behavior, but is not the case here. I spoke about this kind of nastiness earlier this year:
    Increment differences in C++ and C#
    &nbsp;

  • > when you attempt to modify a variable more than once within a
    > single expression

    That is indeed part of it. Section 5 paragraph 4 states another part right after that.

    * Furthermore, the prior value shall be accessed only to determine
    * the value to be stored.

    Let's consider an expression which you didn't write, but just as an example:

    SUCCEEDED(::DwmIsCompositionEnabled(&enabled)) & enabled;

    In the left-hand operand of this bitwise and operator, scalar object enabled has its value modified. In the right-hand operand, the access to the scalar object enabled can either get the new value and yield a lucky result or can access the prior value and yield undefined behaviour. No compiler is required to produce any defined behaviour for this example.

    Your actual expression with the && operator has a sequence point in the necessary place, which I belatedly noticed.

  • KENNY

    I am really sorry, but the described "improvements" of Vista transparency ussage within the "advanced LAYERED" windows is not really a enhancement regarding the XP os.
    The news would be:
    1- if such region transparency would be supported for all windows (not only layered), or
    2 - if layered attributes could be set also for the child windows or if the layered windows could hawe child windows, or
    3 - The change of the laered/nonlayered would be permitted not only before call SetParent (which makes a window a child), but also after the call of mentioned finction.

    I mean, all these sh... remains the same also within Vista as it was on the XP and I see no real improvements within Vista.

    best: Jani

  • Jani: thanks for your comments.

    By “Advanced Layered Windows” I simply mean more power compared to using the simpler SetLayeredWindowAttributes function. The only “new” aspect for layered windows is the ability to combine layered window alpha blending with translucency (blurring) which was not possible prior to Windows Vista due to the lack of desktop composition.

    Certainly there are things about layered windows we would all like improved, but that’s a topic for another article.

  • Also I am sorry for adding an unnecessary increment to my apology.

  • Kenny,

    I'd like to thank you very much for your articles! I looked high and low for something for C++/MFC developers and yours is the only one.

    Adrian

  • Kenny,

    I think something changed in RC1 as I've been unable to duplicate your results with a dialog box (based on your layered sample above): the background remains black.

    1. The background is rendered transparent on XP so I know the layering works.
    2. The call to DwmExtendFrameIntoClientArea succeeds.

    In your sample, the "extent frame into client area" does not seem to work either, the background is black - except if all the margins are 0. If I change a margin to a number (say 10), the background remains black.

    Looking carefully, it seems the edge does change underneath but the black rectangle remains on top obscuring it (look carefully at the margin).

    Has anybody else tried RC1 and got this to work?

    Thanks,

    Adrian

  • Adrian:
    The transparency key approach (using SetLayeredWindowAttributes) no longer works on RC1 and presumably won&rsquo;t work in the RTM build.
    The &ldquo;Extend frame into client area&rdquo; option in the sample application still works. Just make sure that you don&rsquo;t combine it with the &ldquo;Enable blur&rdquo; option. The DwmExtendFrameIntoClientArea and DwmEnableBlurBehindWindow functions are mutually exclusive.

  • Kenny,

    Many thanks for following up, I appreciate it.

    Shoudn't we bug the transparent key approach issue? Are we sure it is not a RC1 bug rather than a "feature"? It looked very useful to me.

    I was trying to implement a menu like the Start menu using this based on your sample. Any idea how to do this? (i.e. glass and then transparent theme painting over it)

    Also, if you have time, would you have the source of your dialog sample (the one with "can you see me")? I'm trying to work it out. Or maybe I can post the code here?

    I'm just using MFC (not ATL) app, dialog based created by VS as per your sample. Yes, I was using SetLayeredWindowAttributes so I guess that does not work in RC1. So how can you get transparency now?

    I will check the sample again.

    Thanks,

    Adrian

  • Kenny:

    Indeed the "Extend frame into client area" works if "Blur" is disabled.

    I've tried exactly that for a dialog box as I mentioned and still cannot get it to work. The frame is extended but it is still painted over. Does it work for dialogs too or just windows?

    Failing that, can an owner-drawn control have a glass background?

    This could compensate for the inability to extend frame.

    Anyway, if you ever have some free time and it sounds interesting... I will keep trying to get it to work.

    Thanks,

    Adrian

  • DwmExtendFrameIntoClientArea works just as well for dialogs. Take the SampleDialog class from the article and do the following:

    1. Change the transparency key to RGB(0,0,0). This results in the background simply being painted black.

    2. Comment out the call to SetWindowLong and SetLayeredWindowAttributes in OnInitDialog, leaving only the call to DwmExtendFrameIntoClientArea.

    As I’ve said, the transparency key approach no longer works. To get the control to appear properly on the glass you will need to draw it yourself using an alpha channel bitmap.

    I’m finishing part 4 right now and then I’ll try and make some time to provide some further samples for using glass with dialog boxes and controls.

  • So is there no longer a solution for using DwmExtendFrameIntoClientArea and getting controls to render properly on top of a Glassy area?

  • Dorin: Most of the GDI and GDI+ functions and classes will continue to work on Vista. There are some exceptions when desktop composition is enabled. Greg Schechter provides some details with respect to this.

  • The signature for the DwmRegisterThumbnail function has changed since I wrote this article. To compile and run the sample on later builds simply use the latest SDK and the compiler will point out the code that needs updating. Simply remove the “reserved” parameter in Thumbnail::Register and it should compile and run just fine.

    I plan on updating all the articles and samples once I have my hands on the RTM build.

  • I learned so much from this article. I was telling a friend at work of my newly learned skills with DWM, he couldn't believe how easy it was after I showed him this article. One odd interesting and un-expected effect I encountered, in one rendention of this while working on it I did HRESULT result = EnableBlurBehindWindow(window,rgn); but 'rgn' was Null, the effect was a totally transparent client area.
    Keep up the great work.

  • Great article! It explained lots of new things in an easy-to-read way :)
    I have a question btw: If you try "faking" the a window's titlebar (with a custom control, like office 2007 does) and use ExtendFrameIntoClientArea how do you send the hittest result to Windows? If you return HTTRANSPARENT from your control and try handling it in your form's WM_NCHITTEST how do you calculate system button rects?

    Thanks!

  • Hello, me again. I just got an answer to my question and wanted to share it here: Just call DwmDefWindowProc from your WM_NCHITTEST handler and Windows will take care of your system buttons :)

  • If followed the below to disable DWM.

    1. Open the System Properties window using the following command:

    %windir%\system32\SystemPropertiesAdvanced.exe

    2. Click the Performance Settings button.

    3. Either check or clear the “Enabled desktop composition” check box.


    I don't see the option#3 in Windows Vista Ultimate ?

  • When I try to build this example, I get errors saying that 'WM_DWMCOMPOSITIONCHANGED' and 'WM_DWMCOLORIZATIONCOLORCHANGED' are undeclared identifiers.

  • George: you need to use the latest Windows SDK header and lib files. Get it here:

    http://www.microsoft.com/downloads/details.aspx?familyid=7614FE22-8A64-4DFB-AA0C-DB53035F40A0&displaylang=en

  • Very well written, and easy to understand. Thanks a lot! However, I can't get the Layered Windows part to work. If I don't call DwmExtendFrameIntoClientArea, the window is completely transparent. Once I call DwmExtendFrameIntoClientArea, the background of the window becomes black.

  • Ondrej: For layered windows you need to call EnableBlurBehindWindow instead of DwmExtendFrameIntoClientArea.

  • mark: well it’s not the end of the world to call LoadLibrary repeatedly. It is however a good idea to cache the result where appropriate and that is what I typically do in production. Just remember that composition can be disabled at any time so your application should be aware of these runtime changes.

  • mark: Just implemented this for our own project, so I thought I'd share the code (Caveat Coder: I haven't tested it yet :) )



    // Disable Composition in Vista DWM (Desktop Window Manager) ----------------

    typedef HRESULT (CALLBACK * P_DwmIsCompositionEnabled)(BOOL *pfEnabled);
    typedef HRESULT (CALLBACK * P_DwmEnableComposition) (BOOL fEnable);

    struct DWMAPILib
    {
    HMODULE lib;
    P_DwmIsCompositionEnabled fIsEnabled;
    P_DwmEnableComposition fEnable;

    DWMAPILib() : lib((HMODULE)0xFFFFFFFF), fIsEnabled(NULL), fEnable(NULL) {}
    ~DWMAPILib()
    {
    if (lib != NULL && lib != (HMODULE)0xFFFFFFFF)
    ::FreeLibrary(lib);

    lib = (HMODULE)0xFFFFFFFF;
    }

    BOOL Load()
    {
    if (lib == NULL) return FALSE;

    lib = ::LoadLibrary("dwmapi.dll");

    if (lib == NULL) return FALSE;

    fIsEnabled = (P_DwmIsCompositionEnabled)::GetProcAddress(lib, "DwmIsCompositionEnabled");
    fEnable = (P_DwmEnableComposition) ::GetProcAddress(lib, "DwmEnableComposition");

    return TRUE;
    }

    bool IsCompositionEnabled()
    {
    BOOL enabled = FALSE;

    return (fIsEnabled != NULL && SUCCEEDED(fIsEnabled(&enabled)) && enabled);
    }

    HRESULT EnableComposition(BOOL enable)
    {
    if (!fIsEnabled) return 0x80070000 + ERROR_PROC_NOT_FOUND; // @@@efh really should get from GetLastError / ERROR_MOD_NOT_FOUND if lib == NULL

    return fEnable(enable);
    }
    };

    static DWMAPILib dwmapiLib;
    static BOOL CompositionWasEnabled = false;

    void DisableComposition()
    {
    if (!dwmapiLib.Load()) return;

    if ((CompositionWasEnabled = dwmapiLib.IsCompositionEnabled()) == true)
    dwmapiLib.EnableComposition(FALSE); // DWM_EC_DISABLECOMPOSITION
    }


    void RestoreComposition()
    {
    if (CompositionWasEnabled && dwmapiLib.Load())
    dwmapiLib.EnableComposition(TRUE); // DWM_EC_ENABLECOMPOSITION
    }

  • Kenny,
    Thanks for your very clarifying article.
    Our development environment is BCB6.
    In order to contol colors and position we display the text in the caption line of our forms via DrawText(). This works fine up to WinXP. Under Windows Vista however, no text at all is displayed any more. (with Theme='Windows-Classic' it's Ok again...)
    Could you give us a way how to do our 'own display' in WinVista as well?

    Thanks in advance,
    Wiebe

  • Sorry.
    This is our correct URL

    Wiebe

  • Wiebe: your best bet is to check with Borland on Vista support. Presumably C++ Builder will let you call the Windows SDK directly and revert back to system-based composition and let you set the window’s title bar text using the standard SetWindowText function.

  • Thank you very much, its a good content

Comments have been disabled for this content.