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.
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 Property |
Type |
Description |
Default Value |
AlphaTicksPercentage |
Double |
Gets 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 |
Interval |
Double |
Gets or sets the duration (in milliseconds) of moving the spoke(tick) to the next position. |
60 ms |
IsIndeterminate |
Boolean |
Gets or sets whether the SprocketControl is a normal Progress control or an indeterminate one. |
True |
LowestAlpha |
Int32 |
Gets or sets the lowest Opacity value that must be used while rendering the SprocketControl's spokes. |
0 |
Progress |
Double |
Gets or sets progress of the SprocketControl . Used when IsIndeterminate =False . Value Range: 0 -100 . |
0 |
Rotation |
Direction (enum) |
Gets or sets the direction of Rotation - clockwise or anticlockwise. |
Direction.CLOCKWISE |
StartAngle |
Double |
Gets or sets the Angle (in degrees) at which the first spoke(tick) should be drawn. |
270 |
TickColor |
Color |
Gets or sets the color of each spoke(tick) in the Control. |
RGB(58,58,58) |
TickCount |
Int32 |
Gets or sets the number of spokes(ticks) in the SprocketControl . |
12 |
TickStyle |
PenLineCap (enum) |
Gets or sets the shape at the end of each spoke(tick) - Flat, Round, Square or Triangle. |
PenLineCap.Round |
TickWidth |
Double |
Gets or sets the width of each spoke(tick). |
3 |
The StartAngle
dependency property follows the following convention for specifying the angle in degrees.
SprocketControl Demystified
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
.
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:
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
private void CalculateSpokesPoints()
{
spokes = new List<spoke>();
angleIncrement = (360 / (double)TickCount);
alphaChange = (int)((255 - ALPHA_LOWER_LIMIT) / (double)TickCount);
renderStartAngle = StartAngle;
double width = (this.Width < this.Height) ? this.Width : this.Height;
centerPoint = new Point(this.Width / 2, this.Height / 2);
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)));
Spoke spoke = new Spoke(pt1, pt2);
spokes.Add(spoke);
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
).
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;
int progressSpokes = (int)Math.Floor((Progress * TickCount) / 100.0);
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;
}
}
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
.
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