Reference

Glassy Vista Aero Forms in .NET

A search on Google will return quite a lot of more or less complicated and complete articles on how to implement Windows Vista's Aero glass look in your own .NET applications. This posting will describe how to achieve this effect with as little code as possible, and how to get a bit more out of it by processing user input accordingly.

Tags: c# dotnet programming windows

The Basics

Windows Vista provides two mechanisms to to create Aero glass in application forms. The first mechanism is to extend the glass areas from the form's borders, which are already glassy into the client area of your form. The API for this functionality is declared in the DwmApi.h header file of the Windows SDK:

  • DwmIsCompositionEnabled is a function to determine whether the operating system has Desktop Window Manager (DWM) composition enabled (see MSDN)
  • DwmExtendFrameIntoClientArea performs the actual work of extending the window frame into the client area (see MSDN).

The second mechanism for Aero glass is the DwmEnableBlurBehindWindow function, which will not be covered in this article. It allows for somewhat better control over blur effects in Windows forms, including the ability to define arbitrary glass regions.

Imports

Before the DWM functionality can be used in any .NET application, we have to import the appropriate APIs. All of the following sample code has been written in C#, but implementations will be very similar in Visual Basic and Managed C++.

Rather than adding the imports somewhere in your program code, it is good practice to isolate imported API functions, type definitions and contants in separate class files. This will allow you to keep things clean and reuse them in other projects. I recommend creating one class per header file, but you are free to choose a different aproach.

The first parameter of the DwmExtendFrameIntoClientArea function is a pointer to a MARGIN struct, which is defined in UxTheme.h of the Windows SDK. Its values hold the individual dimensions for the left, right, top and bottom margin:

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

Importing the two functions from the DWM API is just as easy:

public class DwmApi
{
    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern void DwmExtendFrameIntoClientArea (
            IntPtr hwnd, ref UxTheme.MARGINS margins);

    [DllImport("dwmapi.dll", PreserveSig = false)]
    public static extern bool DwmIsCompositionEnabled ();
}

Make sure to include using clauses for the System and System.Runtime.InteropServices namespaces, which provide the definitions for other required types and attributes.

Implementation

Extending Aero glass into the client area of the form is pretty straightforward and consists of two steps. First the application needs to check whether the Aero glass feature is available on the system. If it is available, it can then simply pass the desired dimensions to the DWM API.

In the following example, we will override the form's OnLoad method to set up the effect on program start. Windows forms in .NET can have a non-zero Padding value to shrink the form's client area. The example will use the DWM API to ensure that the padding created around the client area will be rendered with Aero glass instead of the form's original background color.

protected override void OnLoad (EventArgs e)
{
    base.OnLoad(e);

    if (!this.DesignMode && DwmApi.DwmIsCompositionEnabled())
    {
        UxTheme.MARGINS m = new UxTheme.MARGINS();

        m.cxLeftWidth = this.Padding.Left;
        m.cxRightWidth = this.Padding.Right;
        m.cyTopHeight = this.Padding.Top;
        m.cyBottomHeight = this.Padding.Bottom;

        DwmApi.DwmExtendFrameIntoClientArea(this.Handle, ref m);

        this.BackColor = Color.Black;
    }
}

The way Vista Aero works is that it will only paint the glass look on pixels that are set to black color. In order to avoid custom painting methods (as suggested in several other articles), we simply set the form's background color to black in the last line of the function above. Rest assured that the black color will never actually be visible.

And this is really all that is needed to create a simple Aero glass effect. The following paragraphs will cover some more advanced topics and explain how to make things look and feel a little better.

Controls and Drawing on Glass

After experimenting with the code from the previous paragraph, you will probably have noticed that there are some subtle, yet unacceptable rendering problems when placing Windows Forms controls on the newly created glass regions. The text of buttons, labels, textboxes and other controls is now rendered with a glass effect, even if you host them on top of other containers, such as panels or page controls, or if you use different front colors.

Unfortunately, there is no simple fix to this problem, as the text drawing routines used by the built-in controls do not use alpha channels when rendering themselves. It is safe to say that most standard controls cannot be used on Aero glass. If you want to have controls on the glass regions, you have to draw them yourself using the drawing functions in GDI+. One exception would be controls that use 32-bit images with alpha channels, or images without black color, such as in image buttons or picture boxes.

Windows provides a set of handy drawing functions, such as DrawThemeTextEx for shadowed text on glass, to simplify the implementation of custom drawing routines. The exact details of drawing custom controls is not part of this article. Please refer to the web links at the end of this page for more details.

Processing User Input

The extended window frame region does not automatically become part of the form's caption bar and will therefore not allow users to click and drag the form around. This problem can easily be solved by adding a little bit of custom code.

The trick is to capture WM_NCHITTEST messages, which check whether the mouse pointer should be captured by the receiving form, and then to pretend that the user clicked on the form's caption bar while, in fact, the click hit the extended window frame.

The actual implementation requires three constants declared in WinUser.h:

public class WinUser
{
    public const int WM_NCHITTEST = 0x0084;

    public const int HTCLIENT = 1;
    public const int HTCAPTION = 2;
}

Windows messages can be captured by overriding the form's WndProc method. Once the mouse coordinates passed in the LParam parameter have been converted from screen coordinates into the form's local client coordinate system, it is very easy to determine which part of the form received the click:

protected override void WndProc (ref Message m)
{
    base.WndProc(ref m);

    if ((m.Msg == WinUser.WM_NCHITTEST) && (m.Result.ToInt32() == WinUser.HTCLIENT))
    {
        if (DwmApi.DwmIsCompositionEnabled())
        {
            int i = m.LParam.ToInt32();
            Point p = this.PointToClient(
                new Point(i & 0xffff, i >> 16));

            if ((p.X < this.Padding.Left) ||
                (p.X >= this.ClientSize.Width - this.Padding.Right) ||
                (p.Y < this.Padding.Top) ||
                (p.Y >= this.ClientSize.Height - this.Padding.Bottom))
            {
                m.Result = (IntPtr)WinUser.HTCAPTION;
            }
        }
    }
}

The form can now be dragged around on the screen, not only by clicking its caption bar, but also by clicking any of the extended Aero glass areas.

Other Considerations

The code shown above is a very simple demonstration of what is necessary to leverage the new Aero glass features of Windows Vista. While it works fine for demonstration purposes, you might want to keep in mind potential issues when running your software in the real world.

One less obvious thing to consider is the fact that users have the ability to turn the new DWM features on or off at any time, and your application should be able to handle this. Applications can listen to the new message WM_DWMCOMPOSITIONCHANGED in order to get notified about changes in the composion state.

Additional information can be found on MSDN.

Related Resources

Since Aero glass is one of the more popular Vista development topics, there already are a variety of related articles on the internet. Tim Sneath's short introduction was probably one of the first working examples. Another introduction can be found on The Hackman's blog. Of course, The Code Project has an article as well. Sample code for Visual Basic can be found in a posting on the AeroXperience forums. An excellent article for Managed C++ can be found on Kenny Kerr's blog, which also covers the usage of the DwmEnableBlurBehindWindow function.

Microsoft has an introductory article on DWM including instructions on how to draw on Aero glass, as well as the Application Compatibility Cookbook for Vista. Another great resource for DWM related development is Greg Schlechter's blog.

The problem of rendering built-in controls on Aero glass is explained in a posting on the MSDN forums. The Code Project has a very detailed article on custom drawing in Vista and another, not quite as useful article on text drawing in particular. Sample code for using DrawThemeTextEx in Visual Basic is available on the french website VBFrance.