65.9K
CodeProject is changing. Read more.
Home

Button with Rounded Edges C#

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.10/5 (9 votes)

Jan 28, 2019

CPOL

2 min read

viewsIcon

72005

downloadIcon

4516

A Windows customizable buttom with rounded edges. It's supports the designer support.

Introduction

Some time ago, I tried to find a nice round-border-button control. But I couldn't find one that fit my needs, so like a good bored programmer, I decided to write my own. I "almost" finished it, I guess that it can have more features but it works for my particular case, so I decided to make use of that tradition that says "if it works, just do not touch it".

Though I say it myself, I think the buttons look nice - you'll have to judge for yourself!

How It Works

My class "RoundedButton" which inherits from "System.Windows.Forms.Button" contains a list of new properties that let customize the look of the button and it has some methods overridden.

New Properties:

  • BorderRadius " - how far is the edge of the button rounded."
  • BorderWidth " - specifies the width of edge."
  • BorderColor " - specifies the color of edge."
  • Properties on Mouse Over:
    • BorderOverWidth " - specifies the width of edge when mouse is over button."
    • BorderOverColor " - specifies the color of edge when mouse is over button."
    Properties on Mouse Click:
    • BorderDownWidth " - specifies the width of edge when button triggers the event "OnMouseDown""
    • BorderDownColor " - specifies the color of edge when button triggers the event "OnMouseDown""

Additionally, the inherited properties "BackColor", "FlatAppearance.MouseDownBackColor" or FlatAppearance.MouseOverBackColor" can be used to specify the Back Color in the possible states of button.

Noteworthy Sections of Code

There are three methods that are worth showing:

  • In the first place "GetRoundPath":
    GraphicsPath GetRoundPath(RectangleF Rect, int radius, float width)

    This method returns a GraphicsPath that fits in "Rect" with the specified "radius" considering that the thickness of the edge is "width".

    GraphicsPath GetRoundPath(RectangleF Rect, int radius, float width)
    {
    //Fix radius to rect size
    radius = (int) Math.Max(
             ( Math.Min(radius, 
             Math.Min(Rect.Width, Rect.Height)) - width),1);
    float r2 = radius / 2f;
    float w2 = width / 2f;   
    GraphicsPath GraphPath = new GraphicsPath();
    
    //Top-Left Arc
    GraphPath.AddArc(Rect.X + w2, Rect.Y + w2, radius, radius, 180, 90);
    
    //Top-Right Arc
    GraphPath.AddArc(Rect.X + Rect.Width - radius - w2, Rect.Y + w2, radius, 
                     radius, 270, 90);
                     
    //Bottom-Right Arc
    GraphPath.AddArc(Rect.X + Rect.Width - w2 - radius,
                Rect.Y + Rect.Height - w2 - radius, radius, radius, 0, 90);
    //Bottom-Left Arc
    GraphPath.AddArc(Rect.X + w2, Rect.Y - w2 + Rect.Height - radius, radius, 
                     radius, 90, 90);
                     
    //Close line ( Left)           
    GraphPath.AddLine(Rect.X + w2, Rect.Y + Rect.Height - r2 - w2, Rect.X + 
    w2,Rect.Y + r2 + w2);
    
    return GraphPath;
    }

    In the first place, I used the method of GraphicsPath "CloseFigure()", but the behavior was far away than I expected. A measure that increased the thickness of the edge was closer to the center, I don't know the reason.

  • In the second place "DrawText":
    private void DrawText(Graphics g,RectangleF Rect)

    When I tried to draw the back color by base.OnPaint() it didn't work as I expected, so I decided to draw the back color by myself but then I was forced to write this method with the goal of drawing the text inside of button.

    private void DrawText(Graphics g,RectangleF Rect)
       {
          float r2 = BorderRadius / 4f;
          float w2 = BorderWidth / 2f;
          Point point = new Point();
          StringFormat format = new StringFormat();
          
          switch (TextAlign)
          {
              case ContentAlignment.TopLeft:
                  point.X = (int)(Rect.X + r2/2 + w2 + Padding.Left);
                  point.Y = (int)(Rect.Y + r2/2 + w2 + Padding.Top);
                  format.LineAlignment = StringAlignment.Center;                    
                  break;
              case ContentAlignment.TopCenter:
                  point.X = (int)(Rect.X + Rect.Width/2f);
                  point.Y = (int)(Rect.Y + r2/2 + w2 + Padding.Top);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Center;
                  break;
              case ContentAlignment.TopRight:
                  point.X = (int)(Rect.X + Rect.Width - r2/2 - w2 - Padding.Right);
                  point.Y = (int)(Rect.Y + r2 / 2 + w2 + Padding.Top);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Far;
                  break;
              case ContentAlignment.MiddleLeft:
                  point.X = (int)(Rect.X + r2 / 2 + w2 + Padding.Left);
                  point.Y = (int)(Rect.Y + Rect.Height/2);
                  format.LineAlignment = StringAlignment.Center;
                  break;
              case ContentAlignment.MiddleCenter:
                  point.X = (int)(Rect.X +Rect.Width / 2);
                  point.Y = (int)(Rect.Y + Rect.Height / 2);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Center;
                  break;
              case ContentAlignment.MiddleRight:
                  point.X = (int)(Rect.X + Rect.Width - r2 / 2 - w2 - Padding.Right);
                  point.Y = (int)(Rect.Y + Rect.Height / 2);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Far;
                  break;
              case ContentAlignment.BottomLeft:
                  point.X = (int)(Rect.X + r2 / 2 + w2 + Padding.Left);
                  point.Y = (int)(Rect.Y + Rect.Height - r2 / 2 - w2 - Padding.Bottom);
                  format.LineAlignment = StringAlignment.Center;
                  break;
              case ContentAlignment.BottomCenter:
                  point.X = (int)(Rect.X + Rect.Width/2);
                  point.Y = (int)(Rect.Y + Rect.Height - r2 / 2 - w2 - Padding.Bottom);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Center;
                  break;
              case ContentAlignment.BottomRight:
                  point.X = (int)(Rect.X + Rect.Width - r2 / 2 - w2 - Padding.Right);
                  point.Y = (int)(Rect.Y + Rect.Height - r2 / 2 - w2 - Padding.Bottom);
                  format.LineAlignment = StringAlignment.Center;
                  format.Alignment = StringAlignment.Far;
                  break;
              default:
                  break;
          }
          
          /* Debug
          using (Pen pen = new Pen(Color.Black, 1))
          {
             g.DrawLine(pen, new Point(0, 0), point);
            g.DrawLine(pen, point.X, 0, point.X, point.Y);
            g.DrawLine(pen, 0, point.Y, point.X, point.Y);
          }
          */
          
          using (Brush brush = new SolidBrush(ForeColor))
                g.DrawString(Text, Font, brush, point, format);
       }  
  • In Last place and most important "OnPaint":

    This method is overridden from System.Windows.Forms.Button and it is responsible for drawing the button.

     protected override void OnPaint(PaintEventArgs e)
     {
         e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
         RectangleF Rect = new RectangleF(0, 0, this.Width, this.Height);
         Brush brush = new SolidBrush(this.BackColor);            
         
         GraphicsPath GraphPath = GetRoundPath(Rect, BorderRadius);
         
         this.Region = new Region(GraphPath);
         
         //Draw Back Color
         if(IsMouseDown && !FlatAppearance.MouseDownBackColor.IsEmpty)
            using (Brush mouseDownBrush = new         SolidBrush(FlatAppearance.MouseDownBackColor))
        e.Graphics.FillPath(mouseDownBrush, GraphPath);
        else if (IsHovered && !FlatAppearance.MouseOverBackColor.IsEmpty)
            using (Brush overBrush = new SolidBrush(FlatAppearance.MouseOverBackColor))
                e.Graphics.FillPath(overBrush, GraphPath);
        else            
            e.Graphics.FillPath(brush, GraphPath);
            
        //Draw Border
        #region DrawBorder
        GraphicsPath GraphInnerPath;
        Pen pen;
        
        if (IsMouseDown && !BorderDownColor.IsEmpty)
        {
            GraphInnerPath = GetRoundPath(Rect, BorderRadius, BorderDownWidth);
            pen = new Pen(BorderDownColor, BorderDownWidth);
        }                
        else if (IsHovered && !BorderOverColor.IsEmpty)
        {
            GraphInnerPath = GetRoundPath(Rect, BorderRadius, BorderOverWidth);
            pen = new Pen(BorderOverColor, BorderOverWidth);
        }                
        else
        {
            GraphInnerPath = GetRoundPath(Rect, BorderRadius, BorderWidth);
            pen = new Pen(BorderColor, BorderWidth);
        }
        
        pen.Alignment = PenAlignment.Inset;
        if(pen.Width>0)
            e.Graphics.DrawPath(pen, GraphInnerPath);
        #endregion
        
        //Draw Text
        DrawText(e.Graphics,Rect);
     }

How to Use

Using Visual Studio:

  1. Add file to your project
  2. Drag and Drop the class to Toolbox
  3. Rebuild
  4. Drag and Drop "RoundedButton" from Toolbox to your Control
  5. Customize it on Properties panel

If you want use it by code, it's the same as a button from System.Windows.Form, just use the Properties included in RoundedButton to customize it.