Skip to content

Programming for Vista, Part 2: Adding Aero Glass in Managed Code

In part 1 of my Vista programming series I discussed how to add CommandLink’s to your application. In part 2 this chapter, I wish to discuss how you can enhance your Windows smart client application (primarily targetting Windows Forms in this example) adding a Windows Vista Aero “glass” effect to your application. That would be the glass effect you may have come across in many Windows Vista native applications and wizards.

BACKGROUND

Again, similar to the CommandLink example I would like to put into the spotlight the “Add Printer” dialog of Windows Vista to demonstrate an example of where glass has been used to enhance the look and feel of the application:

I believe not only does it enhance the look and feel but the overall user experience has been increased as many users would be familiar with this effect within Windows Vista so these Vista user’s can jump in and be comfortable right away without subconsciously taking time to adjust to a new interface. Plus any users unfamiliar should hopefully still find it clean and quite eye-catching.

Now when I say it does add a great look and feel and is well worth using, that may be the case; but use the effect sparingly; as overuse could result in a negative, cumbersome user experience and be very disconcerting to the user when they are attempting to perform simple tasks in your application. For further information on creating an excellent User Interface (UI) and User Experience (UX) please see the guide I keep banging on about, the Windows Vista Design Guidelines which includes great material on basic to advanced design principals and window layout and design. Do you know what, I don’t just believe in a great UI and UX for Windows Vista, but I have always felt strongly about these two principals and they should always be considered if you wish to take pride in your work.

Now one way to add the Windows Vista Aero glass effect elegantly whilst adhering and upkeeping the rule of maintaing a simple, yet powerful and concise UI is to quite simply just extend the glass effect already featured in the application’s form border. By doing this with just a few steps, you can end up with a very stunning, sexy and elegant effect something of a result similiar to the Add Printer dialog. In fact this glass effect works magic in wizards just like that, as glass may not be something you want to see in every part of your application but strategically placed, hence it’s judicious use, or even something you wish to be looking at all the time. It should be a treat so to say.

In conclusion, the Windows Aero glass effect coupled with the Command Link can get you on your way to creating a truly Windows Vista native looking application or dialog (as long as you are adhering to the other Windows Vista Design Guidelines) which will give you the edge for your smart client, look brilliant and most of all stand out from all the other boring apps out there. Remember these guidelines are being actively promoted for Windows Vista application design and no doubt will be continued through to Windows 7 and beyond.

CODE

So in this article I will demonstrate adding glass to the top of your application window, effectively extending the window titlebar of glass down into the form by about 20 pixels or so, which will achieve the same look and feel featured in the “Add Printer” dialog of Windows Vista. I will also show you how to add a label on top of the glass which will effectively be our new window titlebar text.

The first thing you will need to do is add a PictureBox control and dock it to the top so it will stay correctly placed no matter how the form may be resized:

You can then go ahead and set the height of the PictureBox to anything you like, I am using 40px in this example to mimic the Vista wizard “Add Printer” dialog as close as possible. Give it a name of glassArea and most importantly set the Visible property to False which will allow the DWM API to recognise this to be used as the glass area.

If you are interested in creating a custom window titlebar text embedded in the glass and not using the standard window titlebar (this will create a truer Vista look and feel): Place a label on the screen, change the “Text” property to Title and move it into your PictureBox at a place you are happy with, and a styling you are happy with. For me, this was Segoe UI, 8.75pt, style=Bold and at X=8, Y=12. You should also remove the form title text at this time and name it labelNoCompositionTitle. This will act as the label if composition is not enabled (We are in Windows Basic or on a non-Vista OS machine). You will end up with a result similar to this:

Now we’re into some code.  At the top of your class add the following code which will setup some required variables and expose the Desktop Windows Manager (DWM) API with an unmanaged solution.  This is required as there is no current managed code solution provided by .NET to access this API.

        #region "Glass Frame"
        private bool isLegacyOS = System.Environment.OSVersion.Version.Major < 6;
        int aeroEnabled;
        GraphicsPath fontpath;
        GraphicsPath glowpath;
        PathGradientBrush pathbrush;
        Graphics gph;

        private GlassText glassText;

        #region DWM (Desktop Windows Manager) API

        public struct MARGINS
        {
            public int m_Left;
            public int m_Right;
            public int m_Top;
            public int m_Buttom;
        };

        [System.Runtime.InteropServices.DllImport("dwmapi.dll")]
        public extern static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margin);

        [System.Runtime.InteropServices.DllImport("dwmapi.dll")]
        public extern static int DwmIsCompositionEnabled(ref int en);

        #endregion
        #endregion

Add a using statement to System.Drawing.Drawing2D. Now create a new class named Aero.cs and add the following code:

public class GlassText
    {
        private const int DTT_COMPOSITED = (int)(1UL << 13);
        private const int DTT_GLOWSIZE = (int)(1UL << 11);

        //Text format consts
        private const int DT_SINGLELINE = 0x00000020;
        private const int DT_CENTER = 0x00000001;
        private const int DT_VCENTER = 0x00000004;
        private const int DT_NOPREFIX = 0x00000800;

        //Const for BitBlt
        private const int SRCCOPY = 0x00CC0020;

        //Consts for CreateDIBSection
        private const int BI_RGB = 0;
        private const int DIB_RGB_COLORS = 0;//color table in RGBs

        private struct MARGINS
        {
            public int m_Left;
            public int m_Right;
            public int m_Top;
            public int m_Buttom;
        };

        private struct POINTAPI
        {
            public int x;
            public int y;
        };

        private struct DTTOPTS
        {
            public uint dwSize;
            public uint dwFlags;
            public uint crText;
            public uint crBorder;
            public uint crShadow;
            public int iTextShadowType;
            public POINTAPI ptShadowOffset;
            public int iBorderSize;
            public int iFontPropId;
            public int iColorPropId;
            public int iStateId;
            public int fApplyOverlay;
            public int iGlowSize;
            public IntPtr pfnDrawTextCallback;
            public int lParam;
        };

        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;

        };

        private struct BITMAPINFOHEADER
        {
            public int biSize;
            public int biWidth;
            public int biHeight;
            public short biPlanes;
            public short biBitCount;
            public int biCompression;
            public int biSizeImage;
            public int biXPelsPerMeter;
            public int biYPelsPerMeter;
            public int biClrUsed;
            public int biClrImportant;
        };

        private struct RGBQUAD
        {
            public byte rgbBlue;
            public byte rgbGreen;
            public byte rgbRed;
            public byte rgbReserved;
        };

        private struct BITMAPINFO
        {
            public BITMAPINFOHEADER bmiHeader;
            public RGBQUAD bmiColors;
        };

        //API declares
        [DllImport("dwmapi.dll")]
        private static extern void DwmIsCompositionEnabled(ref int enabledptr);
        [DllImport("dwmapi.dll")]
        private static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margin);

        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr GetDC(IntPtr hdc);
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern int SaveDC(IntPtr hdc);
        [DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern int ReleaseDC(IntPtr hdc, int state);
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateCompatibleDC(IntPtr hDC);
        [DllImport("gdi32.dll", ExactSpelling = true)]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern bool DeleteObject(IntPtr hObject);
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern bool DeleteDC(IntPtr hdc);
        [DllImport("gdi32.dll")]
        private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);

        [DllImport("UxTheme.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern int DrawThemeTextEx(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, string text, int iCharCount, int dwFlags, ref RECT pRect, ref DTTOPTS pOptions);
        [DllImport("UxTheme.dll", ExactSpelling = true, SetLastError = true)]
        private static extern int DrawThemeText(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, string text, int iCharCount, int dwFlags1, int dwFlags2, ref RECT pRect);
        [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO pbmi, uint iUsage, int ppvBits, IntPtr hSection, uint dwOffset);

        private bool IsCompositionEnabled()
        {

            if (Environment.OSVersion.Version.Major < 6)
                return false;

            int compositionEnabled = 0;
            DwmIsCompositionEnabled(ref compositionEnabled);
            if (compositionEnabled > 0)
            {
                return true;
            }
            else
            {
                return false;
            }

        }

        public void FillBlackRegion(Graphics gph, Rectangle rgn)
        {
            RECT rc = new RECT();
            rc.left = rgn.Left;
            rc.right = rgn.Right;
            rc.top = rgn.Top;
            rc.bottom = rgn.Bottom;

            IntPtr destdc = gph.GetHdc();    //hwnd must be the handle of form,not control
            IntPtr Memdc = CreateCompatibleDC(destdc);
            IntPtr bitmap;
            IntPtr bitmapOld = IntPtr.Zero;

            BITMAPINFO dib = new BITMAPINFO();
            dib.bmiHeader.biHeight = -(rc.bottom - rc.top);
            dib.bmiHeader.biWidth = rc.right - rc.left;
            dib.bmiHeader.biPlanes = 1;
            dib.bmiHeader.biSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
            dib.bmiHeader.biBitCount = 32;
            dib.bmiHeader.biCompression = BI_RGB;
            if (!(SaveDC(Memdc) == 0))
            {
                bitmap = CreateDIBSection(Memdc, ref dib, DIB_RGB_COLORS, 0, IntPtr.Zero, 0);
                if (!(bitmap == IntPtr.Zero))
                {
                    bitmapOld = SelectObject(Memdc, bitmap);
                    BitBlt(destdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, Memdc, 0, 0, SRCCOPY);

                }

                //Remember to clean up
                SelectObject(Memdc, bitmapOld);

                DeleteObject(bitmap);

                ReleaseDC(Memdc, -1);
                DeleteDC(Memdc);

            }
            gph.ReleaseHdc();

        }

        public void DrawTextOnGlass(IntPtr hwnd, String text, Font font, Rectangle ctlrct, int iglowSize)
        {
            if (IsCompositionEnabled())
            {
                RECT rc = new RECT();
                RECT rc2 = new RECT();

                rc.left = ctlrct.Left;
                rc.right = ctlrct.Right + 2 * iglowSize;  //make it larger to contain the glow effect
                rc.top = ctlrct.Top;
                rc.bottom = ctlrct.Bottom + 2 * iglowSize;

                //Just the same rect with rc,but (0,0) at the lefttop
                rc2.left = 0;
                rc2.top = 0;
                rc2.right = rc.right - rc.left;
                rc2.bottom = rc.bottom - rc.top;

                IntPtr destdc = GetDC(hwnd);    //hwnd must be the handle of form,not control
                IntPtr Memdc = CreateCompatibleDC(destdc);   // Set up a memory DC where we'll draw the text.
                IntPtr bitmap;
                IntPtr bitmapOld = IntPtr.Zero;
                IntPtr logfnotOld;

                int uFormat = DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOPREFIX;   //text format

                BITMAPINFO dib = new BITMAPINFO();
                dib.bmiHeader.biHeight = -(rc.bottom - rc.top);         // negative because DrawThemeTextEx() uses a top-down DIB
                dib.bmiHeader.biWidth = rc.right - rc.left;
                dib.bmiHeader.biPlanes = 1;
                dib.bmiHeader.biSize = Marshal.SizeOf(typeof(BITMAPINFOHEADER));
                dib.bmiHeader.biBitCount = 32;
                dib.bmiHeader.biCompression = BI_RGB;
                if (!(SaveDC(Memdc) == 0))
                {
                    bitmap = CreateDIBSection(Memdc, ref dib, DIB_RGB_COLORS, 0, IntPtr.Zero, 0);   // Create a 32-bit bmp for use in offscreen drawing when glass is on
                    if (!(bitmap == IntPtr.Zero))
                    {
                        bitmapOld = SelectObject(Memdc, bitmap);
                        IntPtr hFont = font.ToHfont();
                        logfnotOld = SelectObject(Memdc, hFont);
                        try
                        {

                            System.Windows.Forms.VisualStyles.VisualStyleRenderer renderer = new System.Windows.Forms.VisualStyles.VisualStyleRenderer(System.Windows.Forms.VisualStyles.VisualStyleElement.Window.Caption.Active);

                            DTTOPTS dttOpts = new DTTOPTS();

                            dttOpts.dwSize = (uint)Marshal.SizeOf(typeof(DTTOPTS));

                            dttOpts.dwFlags = DTT_COMPOSITED | DTT_GLOWSIZE;

                            dttOpts.iGlowSize = iglowSize;

                            DrawThemeTextEx(renderer.Handle, Memdc, 0, 0, text, -1, uFormat, ref rc2, ref dttOpts);

                            BitBlt(destdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, Memdc, 0, 0, SRCCOPY);

                        }
                        catch (Exception e)
                        {
                            Trace.WriteLine(e.Message);
                        }

                        //Remember to clean up
                        SelectObject(Memdc, bitmapOld);
                        SelectObject(Memdc, logfnotOld);
                        DeleteObject(bitmap);
                        DeleteObject(hFont);

                        ReleaseDC(Memdc, -1);
                        DeleteDC(Memdc);
                    }

                }

            }

        }
    }

Add the following using statements to the top of the file:


using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;

Add the following to your Form's constructor:

            if (!isLegacyOS)  //make sure you are not on a legacy OS
            {
                aeroEnabled = 0;
                MARGINS mg = new MARGINS();
                mg.m_Buttom = 0;
                mg.m_Left = 0;
                mg.m_Right = 0;
                mg.m_Top = glassArea.Height;

                // Set aeroEnabled by checking if desktop composition is on
                DwmIsCompositionEnabled(ref aeroEnabled);
                if (aeroEnabled > 0)
                {
                    glassText = new GlassText();

                    DwmExtendFrameIntoClientArea(this.Handle, ref mg);

                }
                //this.Paint += new PaintEventHandler(Form1_Paint);
            }

Create a Form1_Paint method which will subscribe to the Paint event of the form and tell the picture box it should be recognised as our glass region. If you are creating a custom window titlebar text embedded in the glass and not using the standard window titlebar: Uncomment the Paint hook this.Paint += new PaintEventHandler(Form1_Paint); line in the constructor code above and uncomment all the lines below. The below can be changed to suit your requirements.

        void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (aeroEnabled > 0)
            {
                glassText.FillBlackRegion(e.Graphics, glassArea.ClientRectangle);
                //glassText.DrawTextOnGlass(this.Handle, "Welcome to My Wizard", new Font("Segoe UI", 9.25f, FontStyle.Regular), new Rectangle(13, 0, 120, 13), 10);
            }
            //else
            //{
            //    this.labelNoCompositionTitle.Visible = true;
            //    this.labelNoCompositionTitle.Text = "Welcome to My Wizard";
            //}
        }

That will be all that is required! Now when running the Windows Vista Aero Theme you will see a beautiful new Aero glass effect added to the top of your form with your title text written, if you are running Windows Vista Basic theme or a legacy OS (non-Vista) there will be no glass effect and the title will be printed as a bold label.

Feel free to set the Form BackgroundColor to White for a further native Vista feel.

Download: Windows Vista Aero Glass Application Sample Source Code

Good night!

Note: The window titlebar text code was written by me, but the DWM API and Aero.cs codebase was mostly cut from the CodeProject article: "Drawing smooth text and pictures on the extended glass area of your WinForm in Windows Vista" and may help you have a deeper understanding of the actual code and introduce you to more advanced drawing techniques you can perform such as adding pictures.

VN:F [1.8.3_1051]
Rating: 5.0/5 (1 vote cast)
VN:F [1.8.3_1051]
Rating: 0 (from 0 votes)
Programming for Vista, Part 2: Adding Aero Glass in Managed Code5.051
Bookmark and Share
kick it on DotNetKicks.com
Shout it

NOW, FOR A WORD FROM OUR SPONSORS

2 Comments

  1. I’d often wondered how you’d do this.
    I have to say – i’m very dissapointed there’s not a managed wrapper already for this.

    As far as I understood it, the whole point in .Net was to help the programmer avaoid making interop calls to non-managed code (for core operating system features, anyway)

    UN:F [1.8.3_1051]
    Rating: 0.0/5 (0 votes cast)
    UN:F [1.8.3_1051]
    Rating: 0 (from 0 votes)
    Posted on 17-Feb-09 at 7:17 pm | Permalink
  2. Graham O'Neale

    Yes I know, very annoying. Here’s hoping for .NET 4.0.

    UN:F [1.8.3_1051]
    Rating: 0.0/5 (0 votes cast)
    UN:F [1.8.3_1051]
    Rating: 0 (from 0 votes)
    Posted on 18-Feb-09 at 1:48 pm | Permalink

2 Trackbacks/Pingbacks

  1. DotNetShoutout on 16-Feb-09 at 11:30 pm

    Programming for Vista, Part 2: Adding Aero Glass in Managed Code…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

    UN:F [1.8.3_1051]
    Rating: 0.0/5 (0 votes cast)
    UN:F [1.8.3_1051]
    Rating: 0 (from 0 votes)
  2. [...] Programming for Vista, Part 2: Adding Aero Glass in Managed Code – Graham O’Neale shows how you can get Vista Aero effects in your own applications. [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*
My name is Graham O'Neale and I'm a software architect from Gold Coast, Australia. I am an overtime thinker, full time coder and awake part time in the real world. I have a keen interest in software development, particularly in the realm of programming (C#, ASP.NET, ASP.NET MVC, LINQ (2 SQL), Entity Framework, Silverlight, Blend, WCF, WPF) and a keen interest in the cutting edge and innovation. I have a new found love for design patterns, ALT.NET practices and well crafted software architecture. The purpose of this blog is to express any thoughts, findings, tips and gripes along my travels in the wonderful world of coding and technology...