Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / WPF

WPF rounded corners polygon

Rate me:
Please Sign up or sign in to vote.
4.89/5 (51 votes)
18 Nov 2010CPOL3 min read 113.5K   4.4K   67   15
A custom WPF polygon that supports round corners.

Introduction

Recently I found myself in need of a rounded corner polygon, which I needed to build from the user interface, at runtime. WPF has a polygon shape, but it does not provide round corners. I started searching the internet for examples, but didn't find many examples about this, so I started implementing my own custom rounded corners polygon. This article describes the implementation of the polygon and the way you can use it in your own applications. You can find the full implementation in the RoundedCornersPolygon class in the attached demo application source code.

The attached demo application has two main sections of examples:

  1. Rounded corners polygons built in XAML.
  2. Examples_polygon1.JPG

  3. Rounded corners polygon built at runtime.
  4. Runtime_polygon1.JPG

Background

Polygons can be thought of as an equiangular set of points along a circle specified by a radius and a number of points/sides.

PolygonStar.gif

In WPF, you can build a polygon by adding a list of points to the Points property of the Polygon object.

XAML example:
XML
<Canvas>
    <Polygon Points="10,50 180,50 180,150 10,150" 
       StrokeThickness="1" Stroke="Black" />
</Canvas>
C# example:
C#
var cnv = new Canvas(); 
var polygon = new Polygon {StrokeThickness = 1, Fill = Brushes.Black};
polygon.Points.Add(new Point(10, 50)); 
polygon.Points.Add(new Point(180, 50));
polygon.Points.Add(new Point(180, 150));
polygon.Points.Add(new Point(10, 150));
cnv.Children.Add(polygon);
this.Content = cnv;

And the output will be the following rectangle:

Rectangle_normal.JPG

Using the Code

RoundedCornersPolygon is very similar to a normal polygon, but we have a few more properties used for rounding the corners. First, let's look at an example of how we build the same rectangle, but with rounded corners.

XAML example:
XML
<Canvas>
    <CustomRoundedCornersPolygon:RoundedCornersPolygon Points="10,50 180,50 180,150 10,150" 
               StrokeThickness="1" Stroke="Black" ArcRoundness="25" 
               UseAnglePercentage="False" IsClosed="True"/>
<Canvas>
C# example:
C#
var cnv = new Canvas();  
var roundedPolygon = new RoundedCornersPolygon
{
    Stroke = Brushes.Black, StrokeThickness = 1, 
    ArcRoundness = 25, UseAnglePercentage = false, IsClosed = true
};
roundedPolygon.Points.Add(new Point(10, 50));
roundedPolygon.Points.Add(new Point(180, 50));
roundedPolygon.Points.Add(new Point(180, 150));
roundedPolygon.Points.Add(new Point(10, 150));
cnv.Children.Add(roundedPolygon);
this.Content = cnv;

And the output will be the following rectangle with rounded corners:

Rectangle_rounded.JPG

The polygon has four main properties:

The ArcRoundness property specifies at what distance, from the LineSegment end, starts the curve. This property is used together with the UseRoundnessPercentage property. The UseRoundnessPercentage property specifies if the ArcRoundness value is a percentage or a value from the connecting segments.

If, for example, ArcRoundness is set to 10 and UseRoundnessPercentage is set to false, then the curve will start at a distance of 10 before the end of the first segment, and it will end at a distance of 10 after the second segment starts. If UseRoundnessPercentage is set to true, then the distance will be 10% from those segments.

C#
/// <summary> 
/// Gets or sets a value that specifies the arc roundness.
/// </summary>
public double ArcRoundness { get; set; }

/// <summary>  
/// Gets or sets a value that specifies if the ArcRoundness property
/// value will be used as a percentage of the connecting segment or not.
/// </summary>
public bool UseRoundnessPercentage { get; set; }

The IsClosed property specifies if the polygon connects the last point with the first point; to finish the polygon, this property should be set to true.

C#
/// <summary> 
/// Gets or sets a value that specifies if the polygon will be closed or not.
/// </summary> 
public bool IsClosed { get; set; }

The Points property represents the collection of points of the polygon.

C#
/// <summary> 
/// Gets or sets a collection that contains the points of the polygon.
/// </summary>
public PointCollection Points{ get; set; }

Implementation

The control implements the Shape class. The shape used to draw the polygon is a Path object where we add segments of type LineSegment and QuadraticBezierSegment. The second segment, QuadraticBezierSegment, represents a Quadric Bézier curve, which is defined by three points. More information about Bézier curves can be found at http://en.wikipedia.org/wiki/B%C3%A9zier_curve.

For a normal polygon, only LineSegment would be necessary, but to make the rounded corner the Bézier curve is needed. Every time a Point is added or a property is modified, the shape is redrawn. The main method that does the rounding of the corners is the ConnectLinePoints method:

C#
/// <summary>
/// Method used to connect 2 segments with a common point,
/// defined by 3 points and aplying a rounding between them
/// </summary>
/// <param name="pathFigure"></param>
/// <param name="p1">First point, of the first segment</param>
/// <param name="p2">Second point, the common point</param>
/// <param name="p3">Third point, the second point of the second segment</param>
/// <param name="roundness">The roundness of the arc</param>
/// <param name="usePercentage">A value that indicates
/// if the roundness of the arc will be used as a percentage or not</param>
private static void ConnectLinePoints(PathFigure pathFigure, Point p1, 
                                      Point p2, Point p3,
                                      double roundness, bool usePercentage)
{
      //The point on the first segment where the curve will start.
      Point backPoint;
      //The point on the second segment where the curve will end.
      Point nextPoint;
      if (usePercentage)
      {
          backPoint = GetPointAtDistancePercent(p1, p2, roundness, false);
          nextPoint = GetPointAtDistancePercent(p2, p3, roundness, true);
      }
      else
      {
          backPoint = GetPointAtDistance(p1, p2, roundness, false);
          nextPoint = GetPointAtDistance(p2, p3, roundness, true);
      }            

      int lastSegmentIndex = pathFigure.Segments.Count - 1;
      //Set the ending point of the first segment.
      ((LineSegment)(pathFigure.Segments[lastSegmentIndex])).Point = backPoint;

      //Create and add the curve.
      var curve = new QuadraticBezierSegment { Point1 = p2, Point2 = nextPoint };
      pathFigure.Segments.Add(curve);

      //Create and add the new segment.
      var line = new LineSegment { Point = p3 };
      pathFigure.Segments.Add(line);
}

There are two methods that calculate the point where the curve should start: GetPointAtDistance and GetPointAtDistancePercent, one by value and the other by percent:

C#
/// <summary>
/// Gets a point on a segment, defined by two points, at a given distance.
/// </summary>
/// <param name="p1">First point of the segment</param>
/// <param name="p2">Second point of the segment</param>
/// <param name="distance">Distance  to the point</param>
/// <param name="firstPoint">A value that indicates
///    if the distance is calculated by the first or the second point</param>
/// <returns>The point calculated.</returns>
private static Point GetPointAtDistance(Point p1, Point p2, 
                     double distance, bool firstPoint)
{
     double totalDistance = Math.Sqrt(Math.Pow((p2.X - p1.X), 2) + 
                            Math.Pow((p2.Y - p1.Y), 2));
     double rap = firstPoint ? distance / totalDistance : 
                   (totalDistance - distance) / totalDistance;
     return new Point(p1.X + (rap * (p2.X - p1.X)), p1.Y + (rap * (p2.Y - p1.Y)));
}

private static Point GetPointAtDistancePercent(Point p1, Point p2, 
                     double distancePercent, bool firstPoint)
{
     double rap = firstPoint ? distancePercent / 100 : (100 - distancePercent) / 100;
     return new Point(p1.X + (rap * (p2.X - p1.X)), p1.Y + (rap * (p2.Y - p1.Y)));
}

Conclusion

There are many other details that could be implemented. This is just a first attempt for a rounded corners polygon. For example, in other cases, a random rounding would be needed for each corner; WPF offers tools to make almost anything possible when it comes to graphical interfaces. My purpose for this article was to create a rounded corners polygon that people could use and, if needed, improve further for their use.

History

  • 18th November, 2010: Initial post.

License

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


Written By
Software Developer (Senior)
Germany Germany
.NET & JavaScript Senior Software Lead Developer & Architect.
Since 2005 worked with most of .NET technologies (Windows Forms, ASP.NET, WCF, WPF, XNA) and for some years passionate about JavaScript and client JavaScript Architecture.

Comments and Discussions

 
GeneralMy vote of 5 Pin
janvb28-May-21 16:44
janvb28-May-21 16:44 
GeneralMy vote of 5 Pin
2374113-Nov-12 14:13
2374113-Nov-12 14:13 
GeneralMy Vote of 4 Pin
Shahin Khorshidnia20-Apr-12 11:22
professionalShahin Khorshidnia20-Apr-12 11:22 
GeneralRe: My Vote of 4 Pin
Liviu Ignat20-Apr-12 12:07
Liviu Ignat20-Apr-12 12:07 
GeneralRe: My Vote of 4 Pin
Shahin Khorshidnia20-Apr-12 13:05
professionalShahin Khorshidnia20-Apr-12 13:05 
QuestionAwesome class Pin
Eaverae2-Nov-11 5:12
Eaverae2-Nov-11 5:12 
GeneralVery practical article: I've also built the RIA with plenty of rounded corners/gradients using HTML5 Pin
DrABELL14-Dec-10 17:22
DrABELL14-Dec-10 17:22 
GeneralMy Vote of 5 Pin
RaviRanjanKr23-Nov-10 0:29
professionalRaviRanjanKr23-Nov-10 0:29 
GeneralFelicitari! Pin
Romulus Corneanu22-Nov-10 20:41
Romulus Corneanu22-Nov-10 20:41 
GeneralRe: Felicitari! Pin
Toli Cuturicu23-Nov-10 4:46
Toli Cuturicu23-Nov-10 4:46 
GeneralRe: Felicitari! Pin
Romulus Corneanu23-Nov-10 20:18
Romulus Corneanu23-Nov-10 20:18 
Generalgood article Pin
BillW3319-Nov-10 9:04
professionalBillW3319-Nov-10 9:04 
GeneralMy vote of 5 Pin
Slacker00718-Nov-10 23:56
professionalSlacker00718-Nov-10 23:56 
GeneralGood stuff man, so 5 from me Pin
Sacha Barber18-Nov-10 5:49
Sacha Barber18-Nov-10 5:49 
GeneralMy vote of 5 Pin
Lutosław18-Nov-10 4:25
Lutosław18-Nov-10 4:25 
Thanks for your contribution!

The article is clear, the sources are well-documented, and the demo application shows the essence of the matter.

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.