Final result
Features
The goal of this article is to create a circular progress bar (see the examples in the screenshot above) as simple as possible, that is working in Silverlight and WPF and only uses the path arc segment. Because of that, it’s fully customizable.
- Draws an arc segment based on the angle / percentage
- Specifies radius of the circle
- Only uses path ArcSegment
- Specifies Stroke Thickness
- Fully customizable
- Runs in Silverlight AND WPF
- Implementation as simple as possible
Creating the circular progress bar
First of all we are creating a user control with the following content (in a real project you will create a custom control instead):
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}" StrokeThickness="{Binding StrokeThickness, ElementName=userControl}" HorizontalAlignment="Left" VerticalAlignment="Top"> <Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigureCollection> <PathFigure x:Name="pathFigure"> <PathFigure.Segments> <PathSegmentCollection> <ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" /> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> </PathFigureCollection> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path>
The properties “Stroke” and “StrokeThickness” of the path element are bound to dependency properties of the user control.
We’ll get to the code behind class in a minute, but let’s get the XAML code right. The path’s data property is set to a path geometry figure, which consists of a path segment collection.
The first and only segment is an arc segment, which is used to draw the circular progress bar. Notice, that the path figure and the arc segment got a name attached.
These references are used in the code behind to update the arc segment (in a custom control template you will provide these references as a template part). That’s all we have to do in the XAML part of the user control.
Now let’s get to the interesting part.
Code behind
The code behind class is the heart of the circular progress bar, but it’s not that complicated. Let’s start with the most difficult part of the control: the calculation of the arc segment.
To understand the meaning of the arc segment, it is necessary to dig into the world of higher mathematics.
Or you just read along and get the solution to the problem of drawing a perfect circle with the arc segment.
public void RenderArc() { Point startPoint = new Point(Radius, 0); Point endPoint = ComputeCartesianCoordinate(Angle, Radius); endPoint.X += Radius; endPoint.Y += Radius; pathRoot.Width = Radius * 2 + StrokeThickness; pathRoot.Height = Radius * 2 + StrokeThickness; pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0); bool largeArc = Angle > 180.0; Size outerArcSize = new Size(Radius, Radius); pathFigure.StartPoint = startPoint; if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y)) endPoint.X -= 0.01; arcSegment.Point = endPoint; arcSegment.Size = outerArcSize; arcSegment.IsLargeArc = largeArc; }
The arc segment doesn’t have a starting point property. It starts from the last drawn point of the path figure.
In this example the start point of the path figure is set to the top center of the control (Radius, 0).
The end point of the arc segment is calculated depending on the radius and the angle. Now we’ll take a look at the calculation:
private Point ComputeCartesianCoordinate(double angle, double radius) { double angleRad = (Math.PI / 180.0) * (angle - 90); double x = radius * Math.Cos(angleRad); double y = radius * Math.Sin(angleRad); return new Point(x, y); }
It translates the angle to a cartesian coordinate using the radius of the circle. It’s necessary to get the end point as an absolute coordinate, because the arc segment needs to get this information.
Last but not least, here is the calculation of the angle, depending on the percentage (0% to 100%):
Angle = (Percentage * 360) / 100;
To try this sample, feel free to download the source code or ask any questions in the comments.
Source Code:
CircularProgressBar.zip
09.02.2013 at 01:56
Thank you for your post. You saved me a lot of time.
The only modifications I made were to (1) move “RenderArc();” from the constructor to the Loaded event and (2) in the RenderArc function added “if (this.IsLoaded == true)…”.
We observed an issue where the the arc was not correctly rendered depending on the placement of the Percentage setting in the XAML tag. So, for example “…StrokeThickness=”25″ Percentage=”100″ />” would work, but “…Percentage=”60″ StrokeThickness=”25″ />” would not work.
These changes solved the problem.
26.03.2013 at 09:03
Why if the radius is too small(for example Radius=”10″) there is some problem in the drawing of the arc?
30.03.2013 at 08:57
Hi matteo, make sure that the StrokeThickness is <= 2 x Radius. In your example try a Thickness of a maximum of 20.
06.05.2013 at 21:29
Oh hay there,
I tried using this on windows phone 7, but the bar appears to lose its circle appearance with percentages under 65. AM i doing it wrong?
18.07.2013 at 16:56
Thank you so much for this wonderful progress bar! Just wondering, how could I make this thread safe? I update my percentage from another thread, but there is no invoke.
Thanks!
02.09.2013 at 08:20
Great article!
15.09.2013 at 20:26
Awesome control but it does not work on Windows Phone. Tried it on WP7 and WP8, copied code from both WPF and Silverlight versions but all I could see was a gray unchanging square. It is a black eye for Microsoft. Why does it have to be so different between platforms? Why every time porting from WPF to WP7/8 is such a pain and waste of time.
Anyway, whining aside how can I make it work on WP8? What is the magic sauce that is missing?
26.09.2013 at 00:46
It works fine to me on WP, I just rebind in blend the SegmentColor to the UserControl.
01.11.2013 at 11:12
How to resize the control and make it bigger?
02.01.2014 at 10:05
Thanks helps me .. a lot
14.02.2014 at 10:54
Muchas gracias!
01.06.2014 at 13:41
Hello,
this is the best solution for a circular progress bar in WPF so far as i know. May can i use this in one of my programs which im going to sell? Happy to hear from you! Great job u did there.
Fabian
08.06.2014 at 06:05
Hi Man,
I searched for a couple of things regarding this..
But simply done it with the Arc segmentt. THis is what i exactly searching..
Super.!!!!! ..
Thank u so much for nice article…
09.09.2014 at 12:46
Thank you very much. Your solution works fine.
15.09.2014 at 11:32
Dude, thank you. You have saved me lots of time
24.11.2014 at 18:30
Nice Thing, thank you
31.01.2015 at 15:48
Many Thanks. You saved my time :) great work..
05.02.2015 at 14:32
I’ll use parts of your amazing control for one plugin I’m writing. Thanks a lot for this article.
Pingback: Circular Progress Bar in Windows Phone 8.1 Store App | 我爱源码网
20.05.2015 at 15:16
Hello,
Thanks so Much for the big effort, so simple and easy to use, it’s really life saving.
Can you me how to make it work CounterClockWise?
14.07.2015 at 10:10
Hello !
Great component but I didn’t find how to resize the control and make it bigger or smaller?
Can you help me?
31.08.2015 at 18:15
Thanks for your work!
I just want to make one remark. When we do:
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
we get (StrokeThinkness / 2) margin aroud circular progress bar. But if we take (StrokeThikness / 2) instead of (StrokeThikness) there like:
pathRoot.Width = Radius * 2 + StrokeThickness / 2.0;
pathRoot.Height = Radius * 2 + StrokeThickness / 2.0;
pathRoot.Margin = new Thickness(StrokeThickness / 2.0, StrokeThickness / 2.0, 0, 0);
we get rid of that margin.
23.11.2016 at 19:38
I’ve used this aproach. Width and Height stays the same, only Margin modified.
_path.Width = Radius * 2 + StrokeThickness;
_path.Height = Radius * 2 + StrokeThickness;
_path.Margin = new Thickness(StrokeThickness / 2, StrokeThickness / 2, -StrokeThickness / 2, -StrokeThickness / 2);
31.10.2015 at 21:41
I want to define the start point from bottom instead of upper (sameple code).
What will be the technique using this sample code, please help.
Pingback: Creating a circular progress bar - wpf
13.01.2016 at 16:25
Hi,
Apologies as I’m new to wpf, so this could be a simple one.
I’m finding the circle “clips” and becomes square at a certain size, and I cant tell why.
I’ve tried making the Grid of the control bigger but this still happens.
thanks for any response
19.07.2016 at 20:32
Amazing code!
what would need to change for it to go in a clockwise movement? Changing the SweepDirection does no good
08.09.2016 at 18:26
Hey sal, see my comment below
01.08.2016 at 10:07
Best implementation!
Made my day :)
10.08.2016 at 10:50
How can I adjust the code to ago CounterClockwise
08.09.2016 at 18:24
Hey Mark & sal,
you can do the following:
Change SweepDirection to “Counterclockwise” (in CircularProgressBar.xaml) and in the code behind change all Angle calculations (add a “-“):
Line 26: Angle = -(Percentage * 360) / 100;
Line 83: circle.Angle = -(circle.Percentage * 360) / 100;
Line 103: bool largeArc = -Angle > 180.0;
Line 110: endPoint.X += 0.01;
06.10.2016 at 15:08
How can we start from the bottom instead of the top as the startpoint?
10.11.2016 at 13:04
The easiest way is to just rotate it with the render transform property.
02.01.2017 at 18:11
Just did it ! Fine ;-)
25.12.2016 at 00:54
Thank you for your post. You saved me a lot of time :)
02.01.2017 at 18:10
Thank you ! Very userfull. I just used it in my UWP project.
06.02.2017 at 11:34
Yo Timo. I am trying to use this code on UWP app or WPF app with .net framework 4.6 the circular progressbar is not visible for some reason can’t find the cause very strange.
17.02.2017 at 07:44
I have the exact same problem.
17.02.2017 at 10:10
Some property bindings doesn’t workwith UWP and WPF. Try manually set some values.
local:CircularProgressBar x:Name=”circularProgressBar” HorizontalAlignment=”Left” Margin=”114,231,0,0″ Percentage=”{Binding Value, ElementName=slider}” SegmentColor=”#FF0595E0″ Radius=”50″ BorderBrush=”#FF3686D6″ StrokeThickness=”8″ VerticalAlignment=”Top” Width=”124″ Height=”124″ />
31.03.2017 at 14:05
Oguz,
I had the exact same problem and it ended up being the x:Name=”userControl” tag was missing in the new user control xaml file. When I added the name it started working just like the downloaded sample. Hopefully this works for you as well.
Great job Timo!
10.05.2017 at 11:42
How to put in slider replaced two textbox To control
17.08.2018 at 13:15
hi,
I have created circular progress bar which is a custom control by using your code.
I am trying to add a shine brush with some gradient, to my progress bar but am not able to get the effect perfectly.
any suggestions?
21.04.2020 at 15:52
Hi, nice workaround for drawing arc when angle is 360 degrees, thank you!