using System; using System.Data; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace Nuclex.Windows.Forms.Controls { /// Displays a progress spinner to entertain the user while waiting public partial class ProgressSpinner : UserControl { /// Number of dots the progress spinner will display private const int DotCount = 8; /// Size of a normal dot (only ever assumed by the trailing dot) private const int DotRadius = 4; /// /// The leading dot will be DotCount times this larger than a normal dot /// private const int ScaleFactor = 1; /// Initializes a new progress spinner public ProgressSpinner() { SetStyle( ( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor ), true ); InitializeComponent(); Disposed += new EventHandler(OnDisposed); if(!DesignMode) { StartSpinner(); } } /// Releases all resources owned by the control when it is destroyed /// Control that is being destroyed /// Not used private void OnDisposed(object sender, EventArgs arguments) { if(this.dotOutlinePen != null) { this.dotOutlinePen.Dispose(); this.dotOutlinePen = null; } if(this.dotFillBrush != null) { this.dotFillBrush.Dispose(); this.dotFillBrush = null; } } /// Starts the spinner's animation public void StartSpinner() { this.spinnerRunning = true; this.animationUpdateTimer.Enabled = true; } /// Stops the spinner's animation public void StopSpinner() { this.animationUpdateTimer.Enabled = false; this.spinnerRunning = false; } /// Color used to fill the dots public Color DotFillColor { get { return this.dotFillColor; } set { if(value != this.dotFillColor) { this.dotFillColor = value; if(this.dotFillBrush != null) { this.dotFillBrush.Dispose(); this.dotFillBrush = null; } } } } /// Color used for the dots' outline public Color DotOutlineColor { get { return this.dotOutlineColor; } set { if(value != this.dotOutlineColor) { this.dotOutlineColor = value; if(this.dotOutlinePen != null) { this.dotOutlinePen.Dispose(); this.dotOutlinePen = null; } } } } /// Called when the control is hidden or shown /// Not used protected override void OnVisibleChanged(EventArgs arguments) { base.OnVisibleChanged(arguments); this.animationUpdateTimer.Enabled = this.spinnerRunning && Visible; } /// Called when the control should redraw itself /// Provides access to the drawing surface and tools protected override void OnPaint(PaintEventArgs arguments) { if(Parent != null && this.BackColor == Color.Transparent) { using(var bmp = new Bitmap(Parent.Width, Parent.Height)) { Parent.Controls.Cast() .Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this)) .Where(c => c.Bounds.IntersectsWith(this.Bounds)) .OrderByDescending(c => Parent.Controls.GetChildIndex(c)) .ToList() .ForEach(c => c.DrawToBitmap(bmp, c.Bounds)); arguments.Graphics.DrawImage(bmp, -Left, -Top); } } if(this.dotOutlinePen == null) { this.dotOutlinePen = new Pen(this.dotOutlineColor); } if(this.dotFillBrush == null) { this.dotFillBrush = new SolidBrush(this.dotFillColor); } //e.Graphics.SmoothingMode = SmoothingMode.HighQuality; int diameter = Math.Min(Width, Height); PointF center = new PointF(diameter / 2.0f, diameter / 2.0f); int bigRadius = diameter / 2 - DotRadius - (DotCount - 1) * ScaleFactor; float unitAngle = 360 / DotCount; for(int index = 0; index < DotCount; ++index) { int dotIndex = (index + leadingDotIndex) % DotCount; var dotPosition = new PointF( center.X + (float)(bigRadius * Math.Cos(unitAngle * dotIndex * Math.PI / 180)), center.Y + (float)(bigRadius * Math.Sin(unitAngle * dotIndex * Math.PI / 180)) ); int currentDotRadius = DotRadius + index * ScaleFactor; PointF c1 = new PointF(dotPosition.X - currentDotRadius, dotPosition.Y - currentDotRadius); arguments.Graphics.FillEllipse(this.dotFillBrush, c1.X, c1.Y, 2 * currentDotRadius, 2 * currentDotRadius); arguments.Graphics.DrawEllipse(this.dotOutlinePen, c1.X, c1.Y, 2 * currentDotRadius, 2 * currentDotRadius); } } /// Called when the animation timer ticks to update the animation state /// Animation timer that has ticked /// Not used private void animationTimerTicked(object sender, EventArgs arguments) { this.leadingDotIndex = (this.leadingDotIndex + 1) % DotCount; // Advance the animation Invalidate(); // Request a redraw at the earliest opportune time } /// Whether the spinner has been started private bool spinnerRunning; /// Color in which the dots will be filled private Color dotFillColor = Color.RoyalBlue; /// Color that will be used for the dots' outline private Color dotOutlineColor = Color.White; /// Brush used to fill the dots private Brush dotFillBrush; /// Brush used for the dots' outline private Pen dotOutlinePen; /// Index of the currently leading dot private int leadingDotIndex = 0; } } // namespace Nuclex.Windows.Forms.Controls