Windows Vista Aero Pt. 1 - Adding Glass to a Windows Forms Application

In a brief departure from my usual ramblings about Windows Presentation Foundation, I wanted to write a couple of technical posts about using new Windows Vista shell features from managed code. To start off, I thought I'd talk about how to add glass to an existing WinForms applications.

Firstly, what do I mean by adding glass? As most people know, Windows Vista includes a new Aero theme; one aspect of that theme is the translucent borders that are supplied by the Desktop Window Manager. There's a lot of subtle sophistication in the way window frames are drawn - a drop shadow from the window, glow effects over the maximize / minimize / close buttons, a reflective texture on the window frame itself - so it's far more than just painting with a 50% opacity gray brush.

On suitably equipped machines, every window gets a glass frame (even Command Prompt!). But some applications extend that glass frame into the client area of the window for aesthetic reasons; for example, Internet Explorer extends glass into the address bar, and Windows Media Player uses glass for the playback controls. Your application can also take advantage of the API behind this to extend glass into its own client area. This isn't free - there's quite a hefty tax involved in rendering glass, so it's something to use sparingly rather than as the background for your whole window. Nevertheless, it's a great way to make your application feel like an integral part of the operating system on which it runs.

In this first part, I'll show you how to add glass to a Windows Forms application; in future entries, I'll cover some other related areas such as drawing text onto glass, using glass in a WPF application, and creating blur effects.

The single API call that does most of the dirty-work is the following one:

[DllImport("dwmapi.dll")]
public static extern int DwmExtendFrameIntoClientArea(
IntPtr hWnd,
ref MARGINS pMarInset
);

This call takes two parameters - a window handle and a MARGINS struct that contains information on how much extra the DWM should extend the frame on the top, left, right and bottom sides of the screen. Here's the declaration for MARGINS:

[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}

The one big challenge with glass is getting alpha-blending to work correctly. Without using alpha-blending, then the content that you place on the glass will overwrite the glass itself and make it invisible. This is a problem in GDI, since it has no awareness of an alpha channel, but it's a little easier with GDI+. In your Windows Forms application, you simply need to set the TransparencyKey property to a color that you won't use elsewhere in the application (I use Gainsboro, for reasons that will become apparent later). Then you can create one or more panels that are docked to the margins of your form and set the background color for the panel to the transparency key. Now when you call DwmExtendFrameIntoClientArea, the glass will show within its margins wherever you've set something of the appropriate transparency key.

Here's an example of using the above API call (from a Form_Load event)

MARGINS margins = new MARGINS();
margins.cxLeftWidth = 0;
margins.cxRightWidth = 0;
margins.cyTopHeight = 45;
margins.cyBottomHeight = 0;

IntPtr hWnd = this.Handle;
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);

So long as you've created the panel appropriately, you should now see glass in your application. You can now draw buttons, labels or other controls onto the surface and so long as you set their background color to be transparent, you'll see them integrating well with glass. Here's an example screenshot that puts it all together:

There's just one caveat, which is that the text smoothing doesn't work out quite right. Since it uses the panel background to determine the color it should smooth against, you'll hit problems if you pick a garish color for the transparency key such as Fuchsia - you'll see that the text renders with a horrid pink glow. That's because we've cheated a little bit with the text rendering. So long as you set the transparency color to something that's close to a typical glass color, this effect is barely noticeable (you can just see a little white fringing around the title text above, if you look really closely). That's why we chose Gainsboro as our color earlier. Fortunately, there's a better way to do it - if a little more convoluted. Win32 actually provides a useful API for this situation called DrawThemeTextEx that renders the text correctly on glass and also provides an appropriate back-glow that helps distinguish the text when it's over a complex background. Next time I cover this topic, we'll look at that API as well as discussing how you can detect whether the DWM is present and enabled or not (which is important if you want your application to run downlevel).

Download the sample application and source code (requires Windows Vista to execute).