Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

RoundedButton Control - Demystifying DrawArc

4.95/5 (20 votes)
18 Jul 2015CPOL6 min read 35.7K   1.3K  
This article presents the RoundedButton control and describes the .Net DrawArc method, the subject of a number of questions on programming forums.

Image 1

Introduction Table of Contents

This article presents the RoundedButton control. It was developed during a revision to the Known Colors Palette Tool. This article not only describes the workings of the RoundedButton control, but it also discusses the .Net Graphics.DrawArc Image 4 method, the subject of a number of questions on programming forums.

All of the graphics, presented in this article, initially display as thumbnails. This allows the reader to click on an image to open it in the reader's default graphics display program. All images in this article are PNG images.

In the following discussions, properties, specified by the developer, are displayed in BoldMixedCase text; variables, used internally by the software, are displayed in italicized_lowercase text.

Background Table of Contents

Image 6

In .Net, the ellipse is the basis for drawing ellipses, circles, and arcs. However, as would be the case, Microsoft has made some modifications to the graphics surfaces. In the figure to the left, ( 0, 0 ) is the top-left corner of a graphics surface (e.g., windows form or control origin).

In the normal world (i.e., non-.Net), x-coordinates have increasing values to the right; y-coordinates have increasing values upward. In .Net, x-coordinates have increasing values to the right; but y-coordinates have increasing values downward.

Also, in the normal world, angles are measured counter-clockwise from the x-axis and usually are expressed in radians. In .Net, angles are measured clockwise from the x-axis and are expressed in either radians or degrees, depending upon the method invoked.

 

Image 7

Recall the ellipse from Analytical Geometry. Its definition Image 8 is:

A curved line forming a closed loop, where the sum of the distances from two points (foci) to every point on the line is constant.

A circle is a special case of an ellipse wherein the major axis and the minor axis are equal. When this holds true, the two foci are collocated at the center and we call the axes the circle's diameter.

 

Image 9

In .Net, ellipses are defined in terms of the figure's top-left corner and its width and height.

The figure to the left depicts the.Net view. To draw an ellipse, one invokes the Graphics.DrawEllipse Image 10 method:

 
C#
public void DrawEllipse ( Pen pen,
                          int x,
                          int y,
                          int width,
                          int height )

The color and thickness of the elliptical figure are set by the color and width properties of the Pen Image 11 supplied in the invocation of DrawEllipse. If it is desired to draw a circle, rather than an ellipse, the width and height are set equal.

Image 12

Drawing an arc is almost exactly the same as drawing an ellipse, only with two additional parameters: the start angle and the sweep angle.

In the figure to the left, a circular arc has been drawn starting at the angle 180° and extending through a sweep angle of 90° clockwise. To accomplish this we invoke the Graphics.DrawArc Image 13 method:

 
C#
public void DrawArc ( Pen pen,
                      int x,
                      int y,
                      int diameter,   // width
                      int diameter,   // height
                      int start_angle,
                      int sweep_angle )

Because we desire a circular arc, width and height are set equal (to the diameter of the circle).

Note the similarities between DrawArc and DrawEllipse. If the start_angle and sweep_angle were removed from the parameters of the DrawArc method, we would be left with the parameters for the DrawEllipse method. So DrawArc can be viewed as drawing the same figure as would be drawn by DrawEllipse but with only that portion of the line starting at start_angle and extending for sweep_angle visible.

RoundedButton Control Table of Contents

Construction Table of Contents

Image 16

Consider the figure to the left. For illustrative purposes, the RoundedButton control outline is drawn in black; the four arcs are drawn in red; the four green lines are the result of invoking the Graphics.CloseAllFigures Image 17 method; the orange lines are the height for each arc; the blue lines are the width for each arc; and the black dots are the origins for each arc. Note that the orange and blue lines are of equal length, forming the diameters of the circles on which the arcs are drawn.

To draw a figure made up of the red and green lines, we use a GraphicsPath Image 18. The GraphicsPath is created by the rounded_rectangle_path method.

 
C#
// ************************************ rounded_rectangle_path

/// <summary>
/// computes the GraphicsPath of a rounded rectangle
/// </summary>
/// <param name="x">
/// x coordinate of the upper left corner of the rectangle
/// </param>
/// <param name="y">
/// y coordinate of the upper left corner of the rectangle
/// </param>
/// <param name="width">
/// width of the rectangle
/// </param>
/// <param name="height">
/// height of the rectangle
/// </param>
/// <param name="radius">
/// radius of the circle that defines the rounded corner
/// </param>
/// <returns>
/// the GraphicsPath that defines the rounded rectangle
/// </returns>
GraphicsPath rounded_rectangle_path ( int x,
                                      int y,
                                      int width,
                                      int height )
    {
    int           local_diameter = 0;
    GraphicsPath  path = new GraphicsPath ( );
                                // take into account right and
                                // bottom sides
    x += 1;
    y += 1;
    width -= 1;
    height -= 1;

    if ( Diameter == 0 )
        {
        local_diameter = Math.Min ( width, height );
        local_diameter =
            round ( ( float ) local_diameter / 2.0F );
        }
    else
        {
        local_diameter = Diameter;
        }

    path.StartFigure ( );
                                // bottom right
    path.AddArc ( ( x + width - local_diameter ),
                  ( y + height - local_diameter ),
                  local_diameter,
                  local_diameter,
                  0.0F,
                  90.0F );
                                // bottom left
    path.AddArc ( x,
                  ( y + height - local_diameter ),
                  local_diameter,
                  local_diameter,
                  90.0F,
                  90.0F );
                                // top left
    path.AddArc ( x,
                  y,
                  local_diameter,
                  local_diameter,
                  180.0F,
                  90.0F );
                                // top right
    path.AddArc ( ( x + width - local_diameter ),
                  y,
                  local_diameter,
                  local_diameter,
                  270.0F,
                  90.0F );
                                // join all arcs together
    path.CloseAllFigures ( );

    return ( path );
    }

The value of local_diameter is derived from the Diameter property. During the development of this control, various values of the diameter were tried. The "best" one turned out to be based on the formula:

C#
Min ( width, height ) / 2.0F )

If the supplied value of the Diameter property is zero, the control will calculate the value of local_diameter from this formula.

rounded_rectangle_path returns a GraphicsPath. From MSDN documentation, a GraphicsPath can be used to

draw outlines of shapes, fill the interiors of shapes, and create clipping regions.

The GraphicsPath returned by rounded_rectangle_path will be used to create a clipping region that limits all drawing to the desired areas of the control and to draw the outline of the RoundedButton control. rounded_rectangle_path is invoked by draw_border_graphic

C#
// *************************************** draw_border_graphic

/// <summary>
/// creates the border_graphic GraphicsBuffer by performing the
/// following actions:
///
///     1.  create a GraphicsPath
///     2.  establish the clipping region that limits graphics
///         operations to within the GraphicsPath
///     3.  draws the outline of the GraphicsPath to the
///         border_graphic GraphicsBuffer
///     4.  deletes the GraphicsPath
///     5.  optionally draws the outlines of the circles that
///         were used to create the rounded corners to the
///         border_graphic GraphicsBuffer
/// </summary>
void draw_border_graphic ( )
    {
    GraphicsPath    path = null;
                                // ORDER IS IMPORTANT!!!

                                // compute the path
    path = rounded_rectangle_path ( 0,
                                    0,
                                    this.Width,
                                    this.Height );
                                // set clipping region
    this.Region = new Region ( path );
                                // draw the border
    border_graphic.Graphic.DrawPath (
        new Pen ( BorderColor,
                  BorderThickness ),
        path );
    path.Dispose ( );
                                // draw circles
    if ( DrawRoundingCircles )
        {
        draw_rounding_circles ( border_graphic.Graphic,
                                0,
                                0,
                                this.Width,
                                this.Height );
        }
    }

The GraphicsBuffer is a class that provides a flicker-free drawing surface. The draw_rounding_circles method is very much like the rounded_rectangle_path method with the exception that Graphics.DrawEllipse replaces GraphicsPath.AddArc Image 19.

Properties Table of Contents

The following are the properties unique to the RoundedButton control. The properties of the Button Image 21 class, from which RoundedButton inherits, are not discussed here.

Property Type Default Purpose
BorderColor Color Color.White Sets/Gets color of the button border
BorderThickness int 1 Sets/Gets thickness of button border
Diameter int 0 Sets/Gets diameter for rounding corners. If the supplied value of Diameter is zero, the control will calculate the value of the rounding corner diameters as Min ( width, height ) / 2.0F ).
DrawRoundingCircles bool false Specifies if rounding corner circles are to be drawn

Rounded Button Demonstration Table of Contents

RoundedButton Demonstration

The download contains the Rounded Button Dialog demonstration application that allows its users to determine what the RoundedButton control will look like under various circumstances.

The Button GroupBox allows the user to modify the size and color of the RoundedButton. The Width and Height NumericUpDown controls are both limited to the range [ 22, 272 ]. Both affect the RoundedButton Width and Height properties. The BackColor and ForeColor Buttons allow the user to set the associated RoundedButton properties. Note that the ForeColor is used when drawing any text on the face of the RoundedButton. The Use a contrasting ForeColor CheckBox determines if RoundedButton text is displayed using the user-specified ForeColor or a contrasting Black or White color that depends upon the RoundedButton BackColor.

The Border GroupBox allows the user to set the RoundedButton border properties. The Diameter NumericUpDown control specifies the diameter of the rounding corner circles. The lower limit of Diameter is zero; the upper limit is Min ( width, height ) / 2. As the height and width change, so too does the upper limit of Diameter. The Thickness NumericUpDown control specifies the width of the border. It is limited in range to [ 0, 100 ]. The Color Button sets the color of the border. The Draw Rounding Circles CheckBox is present for illustration purposes only. It should remain unchecked in the production environment.

 

For readers interested in how the contrasting colors were determined, the following code was used.

C#
// ***************************************** contrasting_color

/// <summary>
/// determines the color (black or white) that contrasts with
/// the given color
/// </summary>
/// <param name="color">
/// color for which to find its contrasting color
/// </param>
/// <returns>
/// the contrasting color (black or white)
/// </returns>
/// <reference>
/// http://stackoverflow.com/questions/1855884/
///     determine-font-color-based-on-background-color
/// </reference>
Color contrasting_color ( Color color )
    {
    double  a;
    int     d = 0;

    a = 1.0 - ( ( 0.299 * color.R +
                  0.587 * color.G +
                  0.114 * color.B ) / 255.0 );

    if ( a < 0.5 )
        {
        d = 0;                  // bright colors - black font
        }
    else
        {
        d = 255;                // dark colors - white font
        }

    return ( Color.FromArgb ( d, d, d ) );
    }

Conclusion Table of Contents

This article has presented the RoundedButton control and described in some detail the workings of the .Net DrawArc method.

References Table of Contents

Development Environment Table of Contents

The RoundedButton control was developed in the following environment:

Microsoft Windows 7 Professional Service Pack 1
Microsoft Visual Studio 2008 Professional
Microsoft .Net Framework Version 3.5 SP1
Microsoft Visual C# 2008

History Table of Contents

  • July 18, 2015 - Original article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)