Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Fancy Windows Forms

0.00/5 (No votes)
16 Mar 2009 28  
Bring a fresh / cool look and feel to your applications.

XCoolForms

Contents

Introduction

Are you tired of the standard Windows Forms, and want to add some cool interfaces to your application? Then, keep on reading this article, and you will find out how easily it can be done. XCoolForm is a library which allows you to paint fancy titlebars, borders, titlebar buttons, status bars, etc. It comes with a few pre-built styles, and you can easily make your own styles. It also comes with a huge number of properties, so it can fit the needs of every user. The icons I have used are free; I downloaded them from http://www.iconarchive.com, and I got the Visitor font from http://www.dafont.com.

Background

The idea for this project came a year ago, when I already had enough experience using GDI+, so I started to implement it. My next goal is to re-implement this project in WPF, as long as it offers some really good stuff and most work can be done using XAML.

Using the code

To start using the XCoolForm library, you should follow these three simple steps:

  1. Add a reference to the XCoolForm library.
  2. Make your forms inherit from XCoolForm instead of Form.
  3. Customize your form by adding icon holder buttons, menu icons, status bar items, etc.

Structure

Elements

Border

For a 3D border to be correctly rendered, you need to provide six colors (two for the outer border, and four for the inner border). Then, the drawing routine will iterate through the arrays of colors and build the border. If you want a simple, flat border, then you only need to provide one color.

XCoolForm also supports rounded and inclined borders (see pictures below). You can specify the roundness or inclination using the Radius and Inclination properties.

Rounded border

Inclined border

Titlebar

One of the first elements that attracts the user is the titlebar. So, it's important to provide stylish features for the titlebar. XCoolForm offers a whole set of properties, allowing the user to configure the finest details of the titlebar. You can choose between six different styles, and a few titlebar types to control the shape of the titlebar, set the background image, caption, etc. In the following section, the most important titlebar styles and types are explained.

Titlebar styles

XCoolForm supports six titlebar styles used in the rendering process. I'll add new styles soon.

  • Advanced titlebar style

  • The titlebar is rendered using a gradient mix and shine. You need to specify five colors from which the titlebar is drawn. This can be done by assigning the list of colors to the TitleBarMixColors property. Note that if the number of colors is not equal to five, an exception will be thrown.

  • Rectangle fill titlebar style

  • When using this style, the titlebar area is divided into two rectangles filled using start and end gradient colors.

  • Texture fill titlebar style

  • The titlebar is filled using a custom texture. It can be any valid image file.

  • Linear gradient titlebar style

  • The basic titlebar style filled using custom start / end gradient colors.

  • Glow style

  • The upper area of the titlebar is filled with a smooth glow.

  • None

  • When set to this style, only the inner and outer borders are rendered.

Titlebar types

Note, when the TitleBarType property is set to the desired type, the icon area and the titlebar button box are automatically adapted according to the selected type.

  • Rectangular

  • Rounded

  • Angular

Titlebar buttons

Titlebar buttons are drawn inside a button box, which is automatically dimensioned according to the button's width and height. XCoolForm supports three styles for titlebar buttons when those are being hovered. Symbols for close, maximize, and minimize buttons are drawn using some pixel art. For the next release, I'm planning to add support for custom bitmaps which can then be used as button symbols.

  • Pixeled style

  • When set to Pixeled style, XCoolForm provides three different filling modes:

    • None

    • Only the button symbol is highlighted using the highlight color.

    • Upper glow

    • Upper glow is drawn when the button is hovered.

    • Full fill

    • This style involves three steps:

      1. First, the button is filled using a solid color.
      2. Next, the glow is rendered.
      3. Finally, the upper shine is drawn.

  • MacOS style

  • Titlebar buttons have a MacOS look and feel. When a button is hovered, the symbol and lower shine are drawn.

Icon holder

Icon holder is a kind of quick access toolbar where you can place shortcuts or frequently used actions in your program. As shown on the image, it is formed of an array of icons. When the mouse is over the icon holder button, the frame is shown including the image, description, and caption.

  1. Caption

  2. Title to identify the holder button.

  3. Description

  4. Short description about the holder button.

  5. Panel

  6. Container for a button caption, description, and image. You can specify transparency using the FrameAlpha property.

  7. Image

  8. Background image for the panel.

Status bar

As shown on the image below, the statusbar is built from different elements. The background of the statusbar is filled using simple gradients, using the StatusStartColor and StatusEndColor properties. To give a glossy effect for the statusbar, the upper area is filled with a glow. Then, the status bar items are rendered and bound by separators. You can also provide a background image. The last element to comment is the size grip. It's drawn overlapping several rectangles to give it a 3D look.

Implementation

UML diagram

Classes

The table below contains a brief description about the classes found in the XCoolForm library. Some classes also have internal classes, but I didn't put them here. For details, take a look at the library source code.

Class name Description
X3DBorderPrimitive Provides drawing routes for building a 3D border. Flat border functionality is also implemented here.
XTitleBarButton Represents the titlebar button. The drawing process, which includes full fill, glow, etc., is implemented here.
XTitleBar It implements methods for border drawing, titlebar filling, background image drawing, alignment, etc.
XTitleBarIconHolder It also contains a XHolderButton class to correctly draw the gradient panel when the mouse is hovered.
XStatusBar Methods for drawing items, background image, size grip, statusbar filling, and glow drawing.
XCoolFormHelper Some utility methods for drawing rounded rectangles and building color blends.
XAntiAlias Used for smooth drawing. It implements the IDisposable interface.
XmlThemeLoader Utility class for loading themes from XML files.

Code snippets

FillTitleBar is the main method for the titlebar filling. Basically, it uses the LinearGradientBrush and PathGradientBrush classes to perform the painting logic. First, we must figure out which titlebar type is selected by calling BuildTitleBarShape, which will return the GrapichsPath object describing the shape.

private void FillTitleBar(
            Graphics g,
            Rectangle rcTitleBar
            )
{
    GraphicsPath XTitleBarPath = new GraphicsPath();
    XTitleBarPath = BuildTitleBarShape(rcTitleBar);
    using (XAntiAlias xaa = new XAntiAlias(g))
    {

        #region Fill titlebar 
        switch (m_eTitleBarFill)
        {
            case XTitleBarFill.AdvancedRendering:
               using (LinearGradientBrush lgb = new LinearGradientBrush(
                       rcTitleBar,
                       m_TitleBarMix[0],
                       m_TitleBarMix[4],
                       LinearGradientMode.Vertical))
                {


                    lgb.InterpolationColors = XCoolFormHelper.ColorMix(
                        m_TitleBarMix,
                        true
                    );

                    g.FillPath(
                      lgb,
                      XTitleBarPath
                    );
                }

                #region Draw titlebar glow
                using (GraphicsPath XGlow = new GraphicsPath())
                {
                    XGlow.AddEllipse(
                        rcTitleBar.Left,
                        rcTitleBar.Bottom / 2 + 4,
                        rcTitleBar.Width,
                        rcTitleBar.Height
                    );

                    using (PathGradientBrush pgb = new PathGradientBrush(XGlow))
                    {
                        pgb.CenterColor = Color.White;
                        pgb.SurroundColors = 
                          new Color[] { Color.FromArgb(0, 229, 121, 13) };

                        g.SetClip(XTitleBarPath);
                        g.FillPath(
                          pgb,
                          XGlow
                        );
                        g.ResetClip();

                    }
                }
                #endregion
                     
                 break;
              case XTitleBarFill.Texture:
                  if (m_TitleBarTexture != null) {
                      using (TextureBrush tb = new TextureBrush(m_TitleBarTexture))
                      {
                          
                          g.FillPath(
                            tb,
                            XTitleBarPath
                          );
                      }
                  }
                 break;
             case XTitleBarFill.LinearRendering:
                RectangleF rcLinearFill = XTitleBarPath.GetBounds();
                g.SetClip(XTitleBarPath);
                using (LinearGradientBrush lgbLinearFill = new LinearGradientBrush(
                      rcLinearFill,
                      m_clrFillStart,
                      m_clrFillEnd,
                      LinearGradientMode.Vertical
                      ))
                {
                    
                    g.FillRectangle(
                      lgbLinearFill,
                      rcLinearFill
                    );
                }
                    
                g.ResetClip();
                break;
            case XTitleBarFill.UpperGlow:
                RectangleF rcGlow = XTitleBarPath.GetBounds();
                g.SetClip(XTitleBarPath);
                rcGlow.Height /= 2;
                using (LinearGradientBrush lgbUpperGlow = new LinearGradientBrush(
                      rcGlow,
                      m_clrUpperFillStart,
                      m_clrUpperFillEnd,
                      LinearGradientMode.Vertical
                      ))
                {
                    
                    g.FillRectangle(
                      lgbUpperGlow,
                      rcGlow
                    );
                }

                g.ResetClip();
                break;
            case XTitleBarFill.RectangleRendering:
                RectangleF rcDownRect = XTitleBarPath.GetBounds();
                RectangleF rcUpRect = XTitleBarPath.GetBounds();
                g.SetClip(XTitleBarPath);
                rcUpRect.Height /= 2;
                using (LinearGradientBrush lgbUpperRect = new LinearGradientBrush(
                      rcUpRect,
                      m_clrUpperFillStart,
                      m_clrUpperFillEnd,
                      LinearGradientMode.Vertical
                      ))
                {

                    lgbUpperRect.WrapMode = WrapMode.TileFlipY;
                    g.FillRectangle(
                      lgbUpperRect,
                      rcUpRect
                    );
                }

                rcDownRect.Height = rcDownRect.Height / 2;
                rcDownRect.Y = rcUpRect.Bottom;
                using (LinearGradientBrush lgbDwnRect = new LinearGradientBrush(
                      rcDownRect,
                      m_clrFillStart,
                      m_clrFillEnd,
                      LinearGradientMode.Vertical
                      ))
                {

                    g.FillRectangle(
                      lgbDwnRect,
                      rcDownRect
                    );
                }

                g.ResetClip();
                break;
            }
#endregion

The method called from FillTitleBar returns the GraphicsPath object built using arcs and lines.

private GraphicsPath BuildTitleBarShape(
            Rectangle rc
            )
{
    GraphicsPath e = new GraphicsPath();
    switch (m_eTitleBarType)
    {
        case XTitleBarType.Rounded:
            e.AddArc(
              rc.Left,
              rc.Top,
              rc.Height,
              rc.Height,
              90,
              180);
            e.AddLine(
              rc.Left + rc.Height / 2,
              rc.Top,
              rc.Right,
              rc.Top
            );
            e.AddArc(
             rc.Right,
             rc.Top,
             rc.Height,
             rc.Height,
             -90,
             180);
            e.AddLine(
             rc.Right,
             rc.Bottom,
             rc.Left + rc.Height / 2,
             rc.Bottom);
            break;
        case XTitleBarType.Angular:
            e.AddLine(
              rc.Left,
              rc.Bottom,
              rc.Left + 20,
              rc.Top);
            e.AddLine(
              rc.Left + 20,
              rc.Top,
              rc.Right,
              rc.Top);
            e.AddLine(
              rc.Right,
              rc.Top,
              rc.Right - 20,
              rc.Bottom
            );
            e.AddLine(
              rc.Right - 20,
              rc.Bottom,
              rc.Left,
              rc.Bottom
            );
            break;
        case XTitleBarType.Rectangular:
            e.AddRectangle(rc);
            break;

    }
    return e;
}

RenderHolderButtons will iterate through the collection of XHolderButtons, and when the button is hovered, the BuildHolderButtonFrame method will be called to draw the panel. This method also calculates the position of the caption and description literals, and draws them.

public void RenderHolderButtons(
     int x,
     int y,
     Graphics g
   )
{
    int lX = x;
    Rectangle rcIcon = new Rectangle();
    RectangleF rcImage = new RectangleF();
    RectangleF rcFrame = new RectangleF();

    foreach (XHolderButton xbtn in m_xhBtn)
    {

        if (xbtn.ButtonImage != null)
        {
            xbtn.Left = lX;
            xbtn.Top = y + 1;

            rcIcon = new Rectangle(
            lX,
            y + 1,
            xbtn.ButtonImage.Size.Width,
            xbtn.ButtonImage.Size.Height
            );

            if (xbtn.Hot)
            {
                using (XAntiAlias xaa = new XAntiAlias(g))
                {
                    using (GraphicsPath XHolderBtnPath = 
                            BuildHolderButtonFrame(rcIcon, 100, 40))
                    {

                        using (LinearGradientBrush lgb = new LinearGradientBrush(
                              XHolderBtnPath.GetBounds(),
                              Color.FromArgb(xbtn.FrameAlpha, xbtn.FrameStartColor),
                              Color.FromArgb(xbtn.FrameAlpha, xbtn.FrameEndColor),
                              LinearGradientMode.Vertical
                              ))
                        {
                            g.FillPath(
                               lgb,
                               XHolderBtnPath
                            );
                        }

                        rcFrame = XHolderBtnPath.GetBounds();

                    }
                    int lFrameImageWidth = 0;
                    if (xbtn.FrameBackImage != null)
                    {
                        // draw frame image:
                        rcImage = new RectangleF(
                        rcFrame.Right - xbtn.FrameBackImage.Width,
                        rcFrame.Bottom - xbtn.FrameBackImage.Height,
                        xbtn.FrameBackImage.Width,
                        xbtn.FrameBackImage.Height
                        );
                        g.DrawImage(xbtn.FrameBackImage, rcImage);
                        lFrameImageWidth = xbtn.FrameBackImage.Height;
                    }
                    // draw caption / description:
                    g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                    g.DrawString(
                        xbtn.XHolderButtonCaption,
                        xbtn.XHolderButtonCaptionFont,
                        new SolidBrush(xbtn.XHolderButtonCaptionColor),
                        rcFrame.Left + 2,
                        rcIcon.Bottom + 4
                    );

                    StringFormat sf = new StringFormat();
                    sf.Alignment = StringAlignment.Near;
                    sf.LineAlignment = StringAlignment.Near;
                    sf.Trimming = StringTrimming.EllipsisCharacter;
                    sf.FormatFlags = StringFormatFlags.LineLimit;

                    float fCaptionWidth = g.MeasureString(xbtn.XHolderButtonCaption, 
                                          xbtn.XHolderButtonCaptionFont).Height;

                    RectangleF rcDescription = new RectangleF(
                    rcFrame.Left + 2,
                    rcIcon.Bottom + fCaptionWidth,
                    rcFrame.Width,
                    rcFrame.Height - lFrameImageWidth);
                    g.DrawString(
                      xbtn.XHolderButtonDescription,
                      xbtn.XHolderButtonDescriptionFont,
                      new SolidBrush(xbtn.XHolderButtonDescriptionColor),
                      rcDescription,
                      sf);

                }

            }

                // draw button:
                g.DrawImage(
                xbtn.ButtonImage,
                rcIcon
                );

                xbtn.ButtonRectangle = rcIcon;

                // update position:
                lX += rcIcon.Width + 2;
            }

        }
    }

To find the hovering button, the HitTestHolderButton method will return the index of the button.

public int HitTestHolderButton(
        int x,
        int y,
        Rectangle rcHolder
        )
    {
        int lBtnIndex = -1;
        if (x >= rcHolder.Left && x <= rcHolder.Right)
        {
            XHolderButton btn = null;
            for (int i = 0; i < m_xhBtn.Count; i++)
            {
                btn = m_xhBtn[i];
                if (y >= 4 && y <= btn.ButtonRectangle.Bottom)
                {
                    if (x >= btn.Left)
                    {
                        if (x < btn.Left + btn.ButtonRectangle.Width)
                        {
                            lBtnIndex = i;
                            break;
                        }
                    }
                }
            }
        }
        return lBtnIndex;
    }

The FillButton method paints the buttons inside the button box area. In full fill mode, it's needed to clip the drawing inside the bounding of a button. LinearGradientBrush and PathGradientBrush are also used to perform the drawing process.

private void FillButton(
         Rectangle rcBtn,
         Graphics g,
         Color clrStart,
         Color clrEnd,
         GraphicsPath XBoxClip
         )
{
    switch (m_eFillMode)
    {
        case XButtonFillMode.UpperGlow:
            rcBtn.Height = 3;
            using (LinearGradientBrush lgb = new LinearGradientBrush(
                     rcBtn,
                     clrStart,
                     clrEnd,
                     LinearGradientMode.Vertical))
            {
                
                g.FillRectangle(
                  lgb,
                  rcBtn
                );
            }
            break;
        case XButtonFillMode.FullFill:
            // restrict drawing inside button box / rectangle:
            g.SetClip(XBoxClip);
            g.SetClip(rcBtn, CombineMode.Intersect);
            
            #region Fill button
            using (SolidBrush sb = new SolidBrush(clrStart))
            {
                g.FillRectangle(
                  sb,
                  rcBtn
                  );
            }
            #endregion

        using (XAntiAlias xaa = new XAntiAlias(g))
        {
            #region Fill shine
            using (GraphicsPath XBtnGlow = new GraphicsPath())
            {
                XBtnGlow.AddEllipse(rcBtn.Left - 5, rcBtn.Bottom - 
                   rcBtn.Height / 2 + 3, rcBtn.Width + 11, rcBtn.Height + 11);
                using (PathGradientBrush pgb = new PathGradientBrush(XBtnGlow))
                {
                    pgb.CenterColor = Color.FromArgb(235, Color.White);
                    pgb.SurroundColors = new Color[] { Color.FromArgb(0, clrEnd) };
                    pgb.SetSigmaBellShape(0.8f);

                    g.FillPath(
                      pgb,
                      XBtnGlow
                    );

                }
            }
            #endregion

            #region Fill upper glow
            rcBtn.Height = rcBtn.Height / 2 - 2;
            using (LinearGradientBrush lgb = new LinearGradientBrush(
                     rcBtn,
                     Color.FromArgb(80, Color.White),
                     Color.FromArgb(140, Color.White),
                     LinearGradientMode.Vertical))
            {
                using (GraphicsPath XGlowPath = 
                       XCoolFormHelper.RoundRect((RectangleF)rcBtn, 0, 0, 4, 4))
                {
                    lgb.WrapMode = WrapMode.TileFlipXY;
                    g.FillPath(
                      lgb,
                      XGlowPath
                    );

                }
            }
            #endregion
        }
        // reset clipping back:
        g.ResetClip();
        break;
    }

Themes

Themes are loaded from XML configuration files using the XmlThemeLoader class. First, set the target form using the TargetForm property. Then, you can apply your theme by calling the ApplyTheme method. XCoolForm comes with five themes, and I hope to make much more. Also, I would be glad to see themes made by other users.

  • Gray theme

  • Blue winter theme

  • Dark system theme

  • Animal kingdom theme

  • Valentine theme

  • White theme

  • Black theme

  • Standard Windows theme

  • As shown at image below, you can use standard Windows Forms Controls to build your user interface.

  • Vista theme

  • MacOS theme

  • Alien theme

  • Time ago, I built an utility for process monitoring, so I used XCoolForm and some user controls to make custom interface as shown at image.

  • Adobe Media Player theme

History

  • 26th February, 2009: Initial version.
  • 14th March, 2009
    • New themes added (Vista, Standard Windows, Adobe Media Player, Alien, Black, White, Mac OS).
    • Added border types: rounded and inclined.
    • Added Mac titlebar button style.
    • Added new button box fill style.
    • Fixed various issues.
  • Coming soon: Office 2007 Ribbon titlebar / buttons style + more cool features / themes.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here