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