Create Button Like Office 2010 Button in C#

Posted in: My Diary,Programming @en |

This post is also available in: Indonesian

button-paint-c-sharp

This post is also available in: Indonesian

This time I’m giving an example of Owner Drawing/Painting on a component/toolkit in desktop applications (in the web, it’s a lot easier, using CSS). By defining Owner Drawing/Painting we can make different appearance on our components as such applying theme on them. For example, initially ordinary Buttons can be displayed more impressive, with gradient color and appears much more attractive.

Different programming languages may provide different ways to accomodate this behavior. I used to apply Owner Drawing in Delphi thus I brought my experience here. Now, I’m trying it in WinForms C# .NET, both are nearly the same, especially if you are already familiar with Windows API (Java is also using the same logic). One of the ways is through the event painting (usually called OnPaint event) or through derivative Renderer class, with this we can create components/toolkits that have appearance like we want it to be.

Basically it is the same metaphor as in the real world, we will doing the painting on Canvas. Every time a component will always be drawn on the canvas to make it visually appears on the screen, a brief example is on Button, painting process begins from painting the background and then painting the edge line (border) until painting the text on it (sequence process could be vary depends on favor and logic). Painting is continuously drawn in order to make it appear until finally the component is destroyed/disposed.

Example of painting on the Button are as follows:

  1. Create button1 in Form1.
  2. Add event handler OnPaint of the button (select Properties, in Events click twice on Paint) or add these line in the Form’s constructor:
    public Form1()
    {
    	InitializeComponent();
    	button1.Paint += new System.Windows.Forms.PaintEventHandler(this.button1_Paint);
    }
  3. Then create procedure button1_paint() such as:
    private void button1_Paint(object sender, PaintEventArgs e)
    {
        //variables
        Color borderDark, borderLight, bgdark, bgmed, bglight,textColor;
        int _roundedRadiusX = 3;
        int _roundedRadiusY = 3;
    
        //initialization
        //blue button
        borderDark = ColorFromHex("#1f48a1");
        borderLight = ColorFromHex("#4487e4");
        bgdark = ColorFromHex("#2961b5");
        //bgmed = ColorFromHex("#3d7bd9");
        bglight = ColorFromHex("#3e7ddb");
        textColor = Color.White;
    
        //////yellow button
        //borderDark = ColorFromHex("#ecc757");
        //borderLight = ColorFromHex("#fcf3d7");
        //bgdark = ColorFromHex("#f9e189");
        ////bgmed = ColorFromHex("#3d7bd9");
        //bglight = ColorFromHex("#fbf9e0");
        //textColor = ColorFromHex("#1e395b");
    
        //now let's we begin painting
        Graphics g = e.Graphics;
    
        //now let's we begin painting
        //Graphics g = e.Graphics;
        g.SmoothingMode = SmoothingMode.HighQuality;
    
        Rectangle r = new Rectangle(e.ClipRectangle.Left,e.ClipRectangle.Top,e.ClipRectangle.Width,e.ClipRectangle.Height);
        Rectangle r2 = r;
        r2.Inflate(-1, -1);
        Rectangle r3 = r2;
        r3.Inflate(-1, -1);
    
        //clear background first
        using (SolidBrush br = new SolidBrush(Color.FromName("ButtonFace")))
        {
            g.FillRectangle(br, r);
        }
    
        //rectangle for gradient, half upper and lower
        RectangleF halfup = new RectangleF(r.Left, r.Top, r.Width, r.Height);
        RectangleF halfdown = new RectangleF(r.Left, r.Top + (r.Height / 2) - 1, r.Width, r.Height);
    
        //BEGIN PAINT BACKGROUND
        //for half upper, we paint using linear gradient
        using (GraphicsPath thePath = GetRoundedRect(r, _roundedRadiusX, _roundedRadiusY))
        {
            LinearGradientBrush lgb =
                new LinearGradientBrush(halfup, bglight, bgdark, 90f, true);
    
            Blend blend = new Blend(4);
            blend.Positions = new float[] { 0, 0.18f, 0.35f, 1f };
            blend.Factors = new float[] { 0f, .4f, .9f, 1f };
            lgb.Blend = blend;
            g.FillPath(lgb, thePath);
            lgb.Dispose();
    
            //for half lower, we paint using radial gradient
            using (GraphicsPath p = new GraphicsPath())
            {
                p.AddEllipse(halfdown); //make it radial
                using (PathGradientBrush gradient = new PathGradientBrush(p))
                {
                    gradient.WrapMode = WrapMode.Clamp;
                    gradient.CenterPoint = new PointF(Convert.ToSingle(halfdown.Left + halfdown.Width / 2), Convert.ToSingle(halfdown.Bottom));
                    gradient.CenterColor = bglight;
                    gradient.SurroundColors = new Color[] { bgdark };
    
                    blend = new Blend(4);
                    blend.Positions = new float[] { 0, 0.15f, 0.4f, 1f };
                    blend.Factors = new float[] { 0f, .3f, 1f, 1f };
                    gradient.Blend = blend;
    
                    g.FillPath(gradient, thePath);
                }
            }
            //END PAINT BACKGROUND
    
            //BEGIN PAINT BORDERS
            using (GraphicsPath gborderDark = thePath)
            {
                using (Pen p = new Pen(borderDark, 1))
                {
                    g.DrawPath(p, gborderDark);
                }
            }
            using (GraphicsPath gborderLight = GetRoundedRect(r2, _roundedRadiusX, _roundedRadiusY))
            {
                using (Pen p = new Pen(borderLight, 1))
                {
                    g.DrawPath(p, gborderLight);
                }
            }
            using (GraphicsPath gborderMed = GetRoundedRect(r3, _roundedRadiusX, _roundedRadiusY))
            {
                SolidBrush bordermed = new SolidBrush(Color.FromArgb(50, borderLight));
                using (Pen p = new Pen(bordermed, 1))
                {
                    g.DrawPath(p, gborderMed);
                }
            }
            //END PAINT BORDERS
    
            //BEGIN PAINT TEXT
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;
    
            if (this.ShowKeyboardCues)
                sf.HotkeyPrefix = HotkeyPrefix.Show;
            else
                sf.HotkeyPrefix = HotkeyPrefix.Hide;
            g.DrawString((sender as Control).Text, (sender as Control).Font, new SolidBrush(textColor), e.ClipRectangle, sf);
    
        }
    
    }
  4. Add these functions as follow:
    To create rounded rectangle with custom radius:

    public static GraphicsPath GetRoundedRect(RectangleF r, float radiusX, float radiusY)
    {
        GraphicsPath gp = new GraphicsPath();
        gp.StartFigure();
        r = new RectangleF(r.Left, r.Top, r.Width, r.Height);
        if (radiusX <= 0.0F || radiusY <= 0.0F)
        {
            gp.AddRectangle(r);
        }
        else
        {
            //arcs work with diameters (radius * 2)
            PointF d = new PointF(Math.Min(radiusX * 2, r.Width)
                      , Math.Min(radiusY * 2, r.Height));
            gp.AddArc(r.X, r.Y, d.X, d.Y, 180, 90);
            gp.AddArc(r.Right - d.X, r.Y, d.X - 1, d.Y, 270, 90);
            gp.AddArc(r.Right - d.X, (r.Bottom) - d.Y - 1, d.X - 1, d.Y, 0, 90);
            gp.AddArc(r.X, (r.Bottom) - d.Y - 1, d.X - 1, d.Y, 90, 90);
        }
        gp.CloseFigure();
        return gp;
    }

    To convert from hexadecimal format to Color format:

    public static Color ColorFromHex(string hex)
    {
        if (hex.StartsWith("#"))
            hex = hex.Substring(1);
    
        if (hex.Length != 6) throw new Exception("Color not valid");
    
        return Color.FromArgb(
            int.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
            int.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
            int.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber));
    }
  5. The result as follow:

    Example of painting on Button with Office 2010 color theme

Notice that source code above does not draw whenever button state is changed, so when you hover it using mouse (mouse hover/enter) or click (mouse down), etc the drawing will keep the same. To make it be able to draw the state changes, create a variable that will be used to hold value of state changes whenever OnMouseEnter event, OnMouseLeave event, on focus  event, or other event fires. Before painting in OnPaint, check the state and do the drawing depends on the state.

With this principal we can create any appearance of your own custom control.

Have fun and let’s be creative!




Comments

There are 2 comments


  1. Omar Chavez says:

    When increment the radious the background is gray… How to do transparent? I want a oval button …



  2. johnrey says:

    WHERE IS HOTKEY PREFIX???



Speak Up!

Leave your own comment

Notify me of follow-up comments via e-mail (or subscribe here).




 

Share

Subscribe Feed

Email

Facebook

Twitter

Delicious

Digg

StumbleUpon

Google Buzz

Deviantart