Click here to Skip to main content
15,114,081 members
Articles / Multimedia / GDI+
Article
Posted 26 Feb 2009

Stats

479.2K views
49.7K downloads
404 bookmarked

Fancy Windows Forms

Rate me:
Please Sign up or sign in to vote.
4.93/5 (135 votes)
16 Mar 2009CPOL7 min read
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

Image 2

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.

Image 3

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

Image 4

Rounded border

Image 5

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.

Image 6

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.

    Image 7

  • Rectangle fill titlebar style

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

    Image 8

  • Texture fill titlebar style

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

    Image 9

  • Linear gradient titlebar style

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

    Image 10

  • Glow style

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

    Image 11

  • None

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

    Image 12

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

  • Image 13

  • Rounded

  • Image 14

  • Angular

  • Image 15

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.

      Image 16

    • Upper glow

    • Upper glow is drawn when the button is hovered.

      Image 17

    • 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.

      Image 18

  • MacOS style

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

    Image 19

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.

Image 20

  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.

Image 21

Implementation

UML diagram

Image 22

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 nameDescription
X3DBorderPrimitiveProvides drawing routes for building a 3D border. Flat border functionality is also implemented here.
XTitleBarButtonRepresents the titlebar button. The drawing process, which includes full fill, glow, etc., is implemented here.
XTitleBarIt implements methods for border drawing, titlebar filling, background image drawing, alignment, etc.
XTitleBarIconHolderIt also contains a XHolderButton class to correctly draw the gradient panel when the mouse is hovered.
XStatusBarMethods for drawing items, background image, size grip, statusbar filling, and glow drawing.
XCoolFormHelperSome utility methods for drawing rounded rectangles and building color blends.
XAntiAliasUsed for smooth drawing. It implements the IDisposable interface.
XmlThemeLoaderUtility 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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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

  • Image 23

  • Blue winter theme

  • Image 24

  • Dark system theme

  • Image 25

  • Animal kingdom theme

  • Image 26

  • Valentine theme

  • Image 27

  • White theme

  • Image 28

  • Black theme

  • Image 29

  • Standard Windows theme

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

    Image 30

  • Vista theme

  • Image 31

  • MacOS theme

  • Image 32

  • 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.

    Image 33

  • Adobe Media Player theme

  • Image 34

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Nedim Sabic
Software Developer
Spain Spain
No Biography provided

Comments and Discussions

 
QuestionListView in "Alien theme" demo [modified] Pin
xixia095121-Jun-11 0:56
Memberxixia095121-Jun-11 0:56 
Generaldrop shadow? Pin
TheCardinal23-Apr-11 5:54
MemberTheCardinal23-Apr-11 5:54 
GeneralWindowState Maximized Pin
Yuriy Y20-Apr-11 4:14
MemberYuriy Y20-Apr-11 4:14 
GeneralPorting to VB.NET Pin
MaddTech8-Dec-10 7:19
MemberMaddTech8-Dec-10 7:19 
GeneralNew themes Pin
johnatan172-Dec-10 19:08
Memberjohnatan172-Dec-10 19:08 
GeneralThank u - help! Pin
semekor1-Dec-10 2:43
Membersemekor1-Dec-10 2:43 
GeneralRe: Thank u - help! Pin
Nedim Sabic1-Dec-10 4:40
MemberNedim Sabic1-Dec-10 4:40 
QuestionWhere i can get other Controls for your themes? Pin
Demaker7-Nov-10 23:50
MemberDemaker7-Nov-10 23:50 
There is only form and button Controls, but for developing need more controls...
GeneralMy vote of 4 Pin
ZIAV21-Oct-10 22:15
MemberZIAV21-Oct-10 22:15 
GeneralMy vote of 5 Pin
Karthikeyan Ganesan6-Oct-10 21:14
MemberKarthikeyan Ganesan6-Oct-10 21:14 
GeneralMy vote of 4 Pin
KrishnaKumar_ve004823-Sep-10 1:30
MemberKrishnaKumar_ve004823-Sep-10 1:30 
GeneralError [modified] Pin
dragonrose4-Sep-10 7:58
Memberdragonrose4-Sep-10 7:58 
QuestionAdding form controls Pin
uwmstud4life16-Aug-10 9:23
Memberuwmstud4life16-Aug-10 9:23 
GeneralMy vote of 5 Pin
Nagendran10-Jul-10 2:18
MemberNagendran10-Jul-10 2:18 
QuestionBeautiful Piece of work. How do I refresh the Status Bar ? Pin
mendy aaron2-Jul-10 7:08
Membermendy aaron2-Jul-10 7:08 
AnswerRe: Beautiful Piece of work. How do I refresh the Status Bar ? Pin
Nedim Sabic3-Jul-10 0:19
MemberNedim Sabic3-Jul-10 0:19 
GeneralMy vote of 5 Pin
Eduardo_David2-Jul-10 0:59
MemberEduardo_David2-Jul-10 0:59 
GeneralRe: My vote of 5 Pin
Nedim Sabic2-Jul-10 3:37
MemberNedim Sabic2-Jul-10 3:37 
QuestionCan I change the bacground? Pin
JayveeJavier1-Jul-10 18:19
MemberJayveeJavier1-Jul-10 18:19 
AnswerRe: Can I change the bacground? Pin
Nedim Sabic2-Jul-10 3:36
MemberNedim Sabic2-Jul-10 3:36 
QuestionCan i use your library in my project? Pin
xorl10-Jun-10 9:55
Memberxorl10-Jun-10 9:55 
AnswerRe: Can i use your library in my project? Pin
Nedim Sabic11-Jun-10 1:45
MemberNedim Sabic11-Jun-10 1:45 
GeneralRe: Can i use your library in my project? Pin
xorl11-Jun-10 8:11
Memberxorl11-Jun-10 8:11 
GeneralRe: Can i use your library in my project? Pin
Nedim Sabic12-Jun-10 8:43
MemberNedim Sabic12-Jun-10 8:43 
Generalwo so good ! i like it Pin
iwanna_201021-Apr-10 19:11
Memberiwanna_201021-Apr-10 19:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.