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

WPFSpark: 1 of n: SprocketControl

Rate me:
Please Sign up or sign in to vote.
4.95/5 (48 votes)
22 Dec 2011Ms-PL5 min read 86.1K   1.3K   117   46
A circular progress control in WPF
Image 1

Introduction

WPFSpark is an open source project in CodePlex, which I started in December 2009, with the hope of creating a library of rich user controls which can be used by the developer community. Initially, I ported the Circular Progress Control (which I had developed using C# and GDI+) to WPF. I had planned for adding a few more user controls, but, due to unavoidable commitments, I could not devote much time to this project as a result of which this project was dormant for more than a year.

Now, I have taken up the responsibility of doing justice to this project and adding more controls to the library. As a result, I have revamped the Codeplex site (and my blog too!). In this article and the subsequent articles, I will be describing in detail about how I developed these controls and how they can be used in your projects in a simple way.

History of SprocketControl

I had created the CircularProgressControl initially in C# and GDI. I had published an article in CodeProject for the same. It was titled Circular Progress Control - Mac OS X Style. Then as I gained more knowledge of WPF, I thought of porting the CircularProgressControl to WPF as well. I created a basic version of the control with limited features, renaming the control as SprocketControl, and released it in CodePlex.

With the revamp of my CodePlex site, I refactored the SprocketControl, adding more features which allow the user to customize it.

Image 2

Update: WPFSpark v1.0

With the release of WPFSpark v1.0, the following changes have been incorporated into SprocketControl:

  • Internal timer stopped when control is no longer visible. It is started only when the control is Visible. This reduces CPU load.
  • Added the LowestAlpha dependency property which indicates the lowest Opacity value that must be used while rendering the SprocketControl's spokes.
  • Added the AlphaTicksPercentage dependency property which indicates the percentage of total ticks which must be considered for step by step reduction of the alpha value. The remaining ticks remain at the LowestAlpha value.
  • SprocketControl now implements IDisposable.

Check out the latest source code available at CodePlex to get the latest SprocketControl.

SprocketControl Properties

Dependency PropertyTypeDescriptionDefault Value
AlphaTicksPercentageDoubleGets or sets the percentage of total ticks which must be considered for step by step reduction of the alpha value. The remaining ticks remain at the LowestAlpha value. Value Range: 0-100.100
IntervalDoubleGets or sets the duration (in milliseconds) of moving the spoke(tick) to the next position.60 ms
IsIndeterminateBooleanGets or sets whether the SprocketControl is a normal Progress control or an indeterminate one.True
LowestAlphaInt32Gets or sets the lowest Opacity value that must be used while rendering the SprocketControl's spokes.0
ProgressDoubleGets or sets progress of the SprocketControl. Used when <code>IsIndeterminate=False. Value Range: 0-100.0
RotationDirection (enum)Gets or sets the direction of Rotation - clockwise or anticlockwise.Direction.CLOCKWISE
StartAngleDoubleGets or sets the Angle (in degrees) at which the first spoke(tick) should be drawn.270
TickColorColorGets or sets the color of each spoke(tick) in the Control.RGB(58,58,58)
TickCountInt32Gets or sets the number of spokes(ticks) in the SprocketControl.12
TickStylePenLineCap (enum)Gets or sets the shape at the end of each spoke(tick) - Flat, Round, Square or Triangle.PenLineCap.Round
TickWidthDoubleGets or sets the width of each spoke(tick).3

The StartAngle dependency property follows the following convention for specifying the angle in degrees.

Image 3

SprocketControl Demystified

Image 4

In order to render the spokes of the control, first the control calculates two circles - inner circle and outer circle. These two circles are concentric. The radii of these two circles are dependent on the size of the CircularProgressControl. The start point (X1, Y1) and the end point (X2, Y2) are calculated for each spoke, based on its angle. These two points are the stored as the StartPoint and EndPoint of the structure Spoke.

C#
struct Spoke
{
    public Point StartPoint;
    public Point EndPoint;

    public Spoke(Point pt1, Point pt2)
    {
        StartPoint = pt1;
        EndPoint = pt2;
    }
}

The angle between two adjacent spokes (angleIncrement) is based on the number of spokes (TickCount), and it is calculated as:

C#
angleIncrement = (int)(360/TickCount);

The calculation of the spoke points is done in the CalculateSpokePoints. This method is called in the constructor and whenever any of the following properties change:

  • Size
  • Rotation
  • StartAngle
  • TickCount
C#
private void CalculateSpokesPoints()
{
    spokes = new List<spoke>();

    // Calculate the angle between adjacent spokes
    angleIncrement = (360 / (double)TickCount);
    // Calculate the change in alpha between adjacent spokes
    alphaChange = (int)((255 - ALPHA_LOWER_LIMIT) / (double)TickCount);

    // Set the start angle for rendering
    renderStartAngle = StartAngle;

    // Calculate the location around which the spokes will be drawn
    double width = (this.Width < this.Height) ? this.Width : this.Height;
    centerPoint = new Point(this.Width / 2, this.Height / 2);
    // Calculate the inner and outer radii of the control. 
    // The radii should not be less than the
    // Minimum values
    innerRadius = (int)(width * INNER_RADIUS_FACTOR);
    if (innerRadius < MINIMUM_INNER_RADIUS)
        innerRadius = MINIMUM_INNER_RADIUS;
    outerRadius = (int)(width * OUTER_RADIUS_FACTOR);
    if (outerRadius < MINIMUM_OUTER_RADIUS)
        outerRadius = MINIMUM_OUTER_RADIUS;

    double angle = 0;

    for (int i = 0; i < TickCount; i++)
    {
        Point pt1 = new Point(innerRadius * 
			(float)Math.Cos(ConvertDegreesToRadians(angle)), 
          innerRadius * (float)Math.Sin(ConvertDegreesToRadians(angle)));
        Point pt2 = new Point(outerRadius * 
			(float)Math.Cos(ConvertDegreesToRadians(angle)), 
          outerRadius * (float)Math.Sin(ConvertDegreesToRadians(angle)));

        // Create a spoke based on the points generated
        Spoke spoke = new Spoke(pt1, pt2);
        // Add the spoke to the List
        spokes.Add(spoke);

        // If it is not it Indeterminate state, 
        // ensure that the spokes are drawn in clockwise manner
        if (!IsIndeterminate)
        {
            angle += angleIncrement;
        }
        else
        {
            if (Rotation == Direction.CLOCKWISE)
            {
                angle -= angleIncrement;
            }
            else if (Rotation == Direction.ANTICLOCKWISE)
            {
                angle += angleIncrement;
            }
        }
    }
}

The OnRender method is overridden to handle the rendering of the control. Here to the RenderTransform of the control, a TranslateTransform and a RotateTransform is applied so that the SprocketControl is rendered with respect to its centre. The Alpha value of the color of the first spoke is 255. After each spoke is rendered, the alpha value of the next spoke's color is reduced by a fixed amount (alphaDecrement) until it reaches the lower limit of the Alpha Value (ALPHA_LOWER_LIMIT).

C#
protected override void OnRender(DrawingContext dc)
{
    if (spokes == null)
        return;

    TranslateTransform translate = new TranslateTransform(centerPoint.X, centerPoint.Y);
    dc.PushTransform(translate);
    RotateTransform rotate = new RotateTransform(renderStartAngle);
    dc.PushTransform(rotate);

    byte alpha = (byte)255;

    // Get the number of spokes that can be drawn with zero transparency
    int progressSpokes = (int)Math.Floor((Progress * TickCount) / 100.0);

    // Render the spokes
    for (int i = 0; i < TickCount; i++)
    {
        if (!IsIndeterminate)
        {
            if (progressSpokes > 0)
                alpha = (byte)(i <= progressSpokes ? 255 : DEFAULT_PROGRESS_ALPHA);
            else
                alpha = (byte)DEFAULT_PROGRESS_ALPHA;
        }

        Pen p = new Pen(new SolidColorBrush(Color.FromArgb(alpha,
            this.TickColor.R, this.TickColor.G, this.TickColor.B)), TickWidth);
        p.StartLineCap = p.EndLineCap = TickStyle;
        dc.DrawLine(p, spokes[i].StartPoint, spokes[i].EndPoint);

        if (IsIndeterminate)
        {
            alpha -= (byte)alphaChange;
            if (alpha < ALPHA_LOWER_LIMIT)
                alpha = (byte)ALPHA_LOWER_LIMIT;
        }
    }

    // Perform a reverse Rotation and Translation to obtain the original Transformation
    dc.Pop();
    dc.Pop();
}

Using the SprocketControl

As an Indeterminate Progress Control

By default, the SprocketControl is an indeterminate progress control. You can customize the control by setting appropriate values to the control's dependency properties - Interval, StartAngle, TickStyle, TickCount, TickWidth. Setting the TickCount to a large number will display the control like a solid ring.

As a Normal Progress Control

By setting the IsIndeterminate property to false, the SprocketControl will behave like a normal progress control. In this mode, the StartAngle is set to 270 degrees. You must set the Progress property to indicate the progress of the process. You can further customize the control by setting appropriate values to the control's dependency properties - Interval, TickStyle, TickCount, TickWidth.

Image 5

EndPoint

If you look at the WPFSpark project in CodePlex, I have added the screenshot of the next control - ToggleSwitch which I am developing. I will be explaining that control in detail in my next article.

History

  • December 21, 2011: WPFSpark v1.0 released
  • May 28, 2011: WPFSpark v0.5 released

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer
United States United States
An individual with more than a decade of experience in desktop computing and mobile app development primarily on the Microsoft platform. He loves programming in C#, WPF & XAML related technologies.
Current interests include web application development, developing rich user experiences across various platforms and exploring his creative side.

Ratish's personal blog: wpfspark.wordpress.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
iJam_j6-Apr-13 14:14
iJam_j6-Apr-13 14:14 
GeneralRe: My vote of 5 Pin
Ratish Philip16-Jul-13 1:45
Ratish Philip16-Jul-13 1:45 
QuestionMy Vote of 5 Pin
AlirezaDehqani8-Nov-12 19:49
AlirezaDehqani8-Nov-12 19:49 
AnswerRe: My Vote of 5 Pin
Ratish Philip16-Jul-13 1:45
Ratish Philip16-Jul-13 1:45 
GeneralAwesome!!!! Pin
raananv18-Sep-12 0:05
raananv18-Sep-12 0:05 
GeneralRe: Awesome!!!! Pin
Ratish Philip16-Jul-13 1:45
Ratish Philip16-Jul-13 1:45 
GeneralMy vote of 5 Pin
yangyr14-Mar-12 16:29
yangyr14-Mar-12 16:29 
GeneralRe: My vote of 5 Pin
Ratish Philip14-Mar-12 22:04
Ratish Philip14-Mar-12 22:04 
GeneralMy vote of 5 Pin
Shahin Khorshidnia11-Feb-12 21:51
professionalShahin Khorshidnia11-Feb-12 21:51 
GeneralRe: My vote of 5 Pin
Ratish Philip19-Feb-12 23:09
Ratish Philip19-Feb-12 23:09 
GeneralMy vote of 5 Pin
WESTSEYI3-Feb-12 23:53
WESTSEYI3-Feb-12 23:53 
GeneralRe: My vote of 5 Pin
Ratish Philip5-Feb-12 19:25
Ratish Philip5-Feb-12 19:25 
GeneralMy vote of 5 Pin
Kanasz Robert20-Jan-12 1:50
professionalKanasz Robert20-Jan-12 1:50 
GeneralRe: My vote of 5 Pin
Ratish Philip20-Jan-12 2:14
Ratish Philip20-Jan-12 2:14 
Question5 from me Pin
Nick Polyak22-Dec-11 9:22
mvaNick Polyak22-Dec-11 9:22 
AnswerRe: 5 from me Pin
Ratish Philip13-Jan-12 4:53
Ratish Philip13-Jan-12 4:53 
GeneralMy vote of 5 Pin
hoernchenmeister24-Aug-11 3:25
hoernchenmeister24-Aug-11 3:25 
GeneralRe: My vote of 5 Pin
Ratish Philip25-Aug-11 0:53
Ratish Philip25-Aug-11 0:53 
GeneralMy vote of 5 Pin
Vivek Krishnamurthy24-Jun-11 6:47
Vivek Krishnamurthy24-Jun-11 6:47 
GeneralRe: My vote of 5 Pin
Ratish Philip25-Jun-11 3:34
Ratish Philip25-Jun-11 3:34 
GeneralMy vote of 5 Pin
T_uRRiCA_N7-Jun-11 8:18
professionalT_uRRiCA_N7-Jun-11 8:18 
GeneralRe: My vote of 5 Pin
Ratish Philip7-Jun-11 14:43
Ratish Philip7-Jun-11 14:43 
GeneralMy vote of 5 Pin
Sparkling_ouc6-Jun-11 22:35
Sparkling_ouc6-Jun-11 22:35 
GeneralRe: My vote of 5 Pin
Ratish Philip7-Jun-11 0:03
Ratish Philip7-Jun-11 0:03 
Thank you!
cheers,
Ratish Philip
Blog: http://wpfspark.wordpress.com/

GeneralNice! Pin
Meshack Musundi1-Jun-11 21:02
professionalMeshack Musundi1-Jun-11 21:02 

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.