|

Introduction
Progress disk is a simple control that I wrote after I saw the one in SQL Server 2005. It can be used to replace the old progress bar with a nice and colorful mix of gradient colors.
Idea
The .NET 2.0 class library has the Graphics object, which is responsible for drawing 2D shapes. It has primitive shapes such as point, line, rectangle, ellipse, pie, etc. I used the DrawPie method to draw my control, which is formed by drawing 3 pie layers over top of each other:
- The first layer in the bottom: a pie having a start angle of zero and a sweep angle of 360 degrees to draw a full circle that serves as the background of the control
- The second layer in the middle: a pie that represent the blocks of the disk; 12 blocks (pie pieces) have been drawn, each having a sweep angle of 25 degrees and a separating angle of 5
- The third layer in the top: similar to the first layer, but its function is to control the size of the blocks; the smaller the circle drawn, the bigger the blocks appear (i.e. having an inner circle with radius zero would give blocks having the shape of slices of a pizza)
Colors
I used a linear gradient brush to fill in the blocks: 2 colors for the inactive blocks and 2 for the active block. Properties added to keep the control flexible include:
Value: The current value of the progress disk
BackGroundColor: The color of the background
ActiveForeColor1 & ActiveForeColor2: These 2 for the block of current value
InactiveForeColor1 & ActiveForeColor2: These 2 for the rest of the blocks
SquareSize: The side length of the square containing the control
BlockSize: An enum for 6 different sizes
SliceNumber: The number of slices in the control (added by Coyotelapa)
Code
4 GraphicsPath objects were used to draw the control, one for each layer and an extra one to color the block with the current value.
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
region = new Region(ClientRectangle);
if (backGrndColor == Color.Transparent)
{
region.Exclude(bkGroundPath2);
Region = region;
}
e.Graphics.FillPath(new SolidBrush(backGrndColor), bkGroundPath1);
e.Graphics.FillPath(
new LinearGradientBrush(new Rectangle(0, 0, size, size),
inactiveforeColor1, inactiveforeColor2,
value * 360 / 12, true), valuePath);
e.Graphics.FillPath(
new LinearGradientBrush(new Rectangle(0, 0, size, size),
activeforeColor1, activeforeColor2,
value * 360 / 12, true), freGroundPath);
e.Graphics.FillPath(new SolidBrush(backGrndColor), bkGroundPath2);
base.OnPaint(e);
}
private void Render()
{
bkGroundPath1.Reset();
bkGroundPath2.Reset();
valuePath.Reset();
freGroundPath.Reset();
bkGroundPath1.AddPie(new Rectangle(0, 0, size, size), 0, 360);
if (sliceCount == 0)
{
sliceCount = 12;
}
float sliceAngle = 360 / sliceCount;
float sweepAngle = sliceAngle - 5;
for (int i = 0; i < sliceCount; i++)
{
if (value != i)
{
valuePath.AddPie(0, 0, size, size, i * sliceAngle,
sweepAngle);
}
}
bkGroundPath2.AddPie(
(size / 2 - size * blockRatio), (size / 2 - size * blockRatio),
(blockRatio * 2 * size), (blockRatio * 2 * size), 0, 360);
freGroundPath.AddPie(new Rectangle(0, 0, size, size),
value * sliceAngle, sweepAngle);
Invalidate();
}
History
- 16 July, 2006 - Original version posted
- 29 July, 2006 - Updated after adding improvements made by Coyotelapa and fixing some bugs
- 31 May, 2007 - Article edited and moved to the main CodeProject.com article base
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 28 (Total in Forum: 28) (Refresh) | FirstPrevNext |
|
|
 |
|
|
This is a very useful control and very well done! Thanks for that, Amr.
I realize this may be a very slight complaint, but if you change the BorderStyle property of the ProgressDisk control to "FixedSingle", you'll notice that the drawn ProgressDisk is clipped on the bottom and right sides. In contrast, the top and left sides appear to have a single pixel cushion from the border. I'm sure it's a relatively simple fix, but I thought I'd point it out, since you can see the clipping even without the border present. Kinda looks like the client area isn't quite large enough for what you draw.
Again, thanks for the control! Matt
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Amr Elsehemy® wrote: Thank you very much made my day
No problem
Amr Elsehemy® wrote: custom controls check these DigitalCounter[^] and SuperToolTip[^] Why not post them up here on Code Project, as well?
"The clue train passed his station without stopping." - John Simmons / outlaw programmer
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Great effort, thanks man for this control , really amazing and looks really better than the one in the .NET framework
Ahmed Mohammed AL-Sayed Ain Shams University - Cairo, Egypt Faculty of cumputer and information sciences MCAD .NET ig@ahmedsayed-ig.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just so you know: for the copyright notice in your assembly info to be valid it needs to have the word Copyright or the (c) symbol, the year(s), and the Author's name (you're missing this part). You may also want to specify the license, like Copyright (c) 2006 Pixelated. All rights reserved. Something like that. If you don't really care if people use your code it's no problem, but if you ever wanted to sue for copyright infringement, that may be a problem.
-Tyler Menezes
-Tyler Menezes CEO and Chairman of Programs and Websites and MyWebsFree.com
"If a plane is on a convayer belt moving backwards at the same speed that it moves fowards, will it take off?"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you very much for that advice , I would use it later, but this control was made to be useful for others to learn and share code . I dont mind any one using the code for his own its a free control.
Thank you.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
The subject says it all but anyways: it rocks! I'm going to use it because the GUI is awesome. Look for NoFooling 2.0, it will have it in it.
-Tyler Menezes CEO and Chairman of Programs and Websites and MyWebsFree.com
"If a plane is on a convayer belt moving backwards at the same speed that it moves fowards, will it take off?"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Sorry, should have said. It's an educational program. www.nofooling.org.
-Tyler Menezes CEO and Chairman of Programs and Websites and MyWebsFree.com
"If a plane is on a convayer belt moving backwards at the same speed that it moves fowards, will it take off?"
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Great idea on the control!
In trying it out, I thought, "Why not include the timer within the control itself, so the developer doesn't have to add a timer to his/her form to make it work?" To do so, I added a timer called 'tmrDisk' and a couple of new properties. One property (SweepRate) to set the rate at wich the value changes. It really just sets the interval on the 'tmrDisk'. The other (IsRunning) turns the timer on or off. I'd rather have used the control's Enabled property, but I'm a complete C# novice and when I tried to override the Enabled property I ran into trouble (ideas welcome here):
private int sweepRate;
[DefaultValue(100)] public int SweepRate { get { return sweepRate; } set { sweepRate = value; if (sweepRate < 10) sweepRate = 100; tmrDisk.Interval = sweepRate; } }
private bool isRunning;
[DefaultValue(true)] public bool IsRunning { get { return isRunning; } set { isRunning = value; tmrDisk.Enabled = IsRunning; } }
And the Tick event handler for the timer:
private void tmrDisk_Tick(object sender, EventArgs e) { this.value = (this.Value + 1) % sliceCount; Render(); }
cheers,
Andrew.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,
first: nice Control second: when you increase the SliceCount more than 12 (eg. 60) your Control doesn't work correctly. (there are some spaces between the first and the last slices and the the active slice-change doesn't shown the right count way)
Best Regards Tim
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, this issue can be fixed by changing frmTest.timer1_Tick like this:
OLD: private void timer1_Tick(object sender, EventArgs e) { progressDisk1.Value = (progressDisk1.Value + 1) % 12; }
NEW: private void timer1_Tick(object sender, EventArgs e) { progressDisk1.Value = (progressDisk1.Value + 1) % progressDisk1.SliceCount; }
However, this is just a quick fix . Control should be modifed so it does not depend on how external code modifies ProgressDisk.Value. Or maybe ProgressDisk.Value should even be made private property and be hidden from external code and new method is introduced which would do exactly what timer1_Tick does at the moment?
For example:
public void DoAnimationStep() { this.Value = (this.Value + 1) % this.SliceCount; }
Best Regards
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I'm sending you slightly improved version of your control.
Changes:
1. I've introduced SliceCount property which determines number of slices shown 2. Fixed the "Resize problem" 3. I've also removed creation of new graphic paths every time Render() is called
Regards
Code :
[DefaultProperty("BlockSize")] public partial class ProgressDisk : UserControl { private GraphicsPath bkGroundPath1 = new GraphicsPath(); private GraphicsPath bkGroundPath2 = new GraphicsPath(); private GraphicsPath valuePath = new GraphicsPath(); private GraphicsPath freGroundPath = new GraphicsPath(); private int sliceCount;
private int value;
[DefaultValue(0)] public int Value { get { return value; } set { this.value = value; Render(); } }
private Color backGrndColor = Color.White;
[DefaultValue(typeof (Color), "White")] public Color BackGroundColor { get { return backGrndColor; } set { backGrndColor = value; Render(); } }
private Color activeforeColor1 = Color.Blue;
[DefaultValue(typeof (Color), "Blue")] public Color ActiveForeColor1 { get { return activeforeColor1; } set { activeforeColor1 = value; Render(); } }
private Color activeforeColor2 = Color.LightBlue;
[DefaultValue(typeof (Color), "LightBlue")] public Color ActiveForeColor2 { get { return activeforeColor2; } set { activeforeColor2 = value; Render(); } }
private Color inactiveforeColor1 = Color.Green;
[DefaultValue(typeof (Color), "Green")] public Color InactiveForeColor1 { get { return inactiveforeColor1; } set { inactiveforeColor1 = value; Render(); } }
private Color inactiveforeColor2 = Color.LightGreen;
[DefaultValue(typeof (Color), "LightGreen")] public Color InactiveForeColor2 { get { return inactiveforeColor2; } set { inactiveforeColor2 = value; Render(); } }
private int size = 50;
[DefaultValue(50)] public int SquareSize { get { return size; } set { size = value; Size = new Size(size, size); } }
private float blockRatio = .4f; private BlockSize bs = BlockSize.Small;
[DefaultValue(typeof (BlockSize), "Small")] public BlockSize BlockSize { get { return bs; } set { bs = value; switch (bs) { case BlockSize.XSmall: blockRatio = 0.49f; break; case BlockSize.Small: blockRatio = 0.4f; break; case BlockSize.Medium: blockRatio = 0.3f; break; case BlockSize.Large: blockRatio = 0.2f; break; case BlockSize.XLarge: blockRatio = 0.1f; break; case BlockSize.XXLarge: blockRatio = 0.01f; break; default: break; } } }
[DefaultValue(12)] public int SliceCount { get { return sliceCount; } set { sliceCount = value; } }
public ProgressDisk() { InitializeComponent(); // CheckForIllegalCrossThreadCalls = false; Render(); }
private Region region = new Region();
protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; region = new Region(ClientRectangle); if (backGrndColor == Color.Transparent) { region.Exclude(bkGroundPath2); Region = region; }
e.Graphics.FillPath(new SolidBrush(backGrndColor), bkGroundPath1); e.Graphics.FillPath( new LinearGradientBrush(new Rectangle(0, 0, size, size), inactiveforeColor1, inactiveforeColor2, value*360/12, true), valuePath); e.Graphics.FillPath( new LinearGradientBrush(new Rectangle(0, 0, size, size), activeforeColor1, activeforeColor2, value*360/12, true), freGroundPath); e.Graphics.FillPath(new SolidBrush(backGrndColor), bkGroundPath2);
base.OnPaint(e); }
private void Render() { // bkGroundPath1 = new GraphicsPath(); // bkGroundPath2 = new GraphicsPath(); // valuePath = new GraphicsPath(); // freGroundPath = new GraphicsPath(); bkGroundPath1.Reset(); bkGroundPath2.Reset(); valuePath.Reset(); freGroundPath.Reset(); bkGroundPath1.AddPie(new Rectangle(0, 0, size, size), 0, 360); //just in case... if (sliceCount == 0) { sliceCount = 12; } float sliceAngle = 360/sliceCount; float sweepAngle = sliceAngle - 5; for (int i = 0; i < sliceCount; i++) { if (value != i) { valuePath.AddPie(0, 0, size, size, i*sliceAngle, sweepAngle); } } bkGroundPath2.AddPie( (size/2 - size*blockRatio), (size/2 - size*blockRatio), (blockRatio*2*size), (blockRatio*2*size), 0, 360); freGroundPath.AddPie(new Rectangle(0, 0, size, size), value * sliceAngle, sweepAngle); Invalidate(); }
protected override void OnSizeChanged(EventArgs e) { size = Math.Max(Width, Height); Size = new Size(size, size); Render(); base.OnSizeChanged(e); }
protected override void OnResize(EventArgs e) { size = Math.Max(Width, Height); Size = new Size(size, size); Render(); base.OnResize(e); } }
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Hi ,
great work man
There's a slight problem when resizing - the upper left corner seems a bit smaller
Cheers, Tzvi
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yes i tried dealing with that problem but the addPie function only accepts a rectangle object not a rectangleF object which differs in that pixel when an odd size is used because of integer truncation i ll try to find a fix for that. thank u
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Any block size combined with a transparent background results in a transparent background and a XXL Block Size...
protected internal static readonly ... and I wish the list could continue ...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yes as I mentioned up it was a 3 layer pie charts if the background is Transparent that means no background layers so the pizza slices left without a cover.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
That is not a very efficient bug fix... Why not use some regions in order to allow transparent background colors... This will wnhabce it's functionlality instead of trimming it.
protected internal static readonly ... and I wish the list could continue ...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|