using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Threading; namespace DTS.Common.Utils { /// /// Utility class used to display spinning animation circle to indicate /// system is busy with background process /// /// Adapting from open source found via Google so it doesn' conform to our /// coding formatting or commenting rules /// public class BusyWaitAnimation { // Constants ========================================================= private const double NumberOfDegreesInCircle = 360; private const double NumberOfDegreesInHalfCircle = NumberOfDegreesInCircle / 2; private const int DefaultInnerCircleRadius = 8; private const int DefaultOuterCircleRadius = 10; private const int DefaultNumberOfSpoke = 10; private const int DefaultSpokeThickness = 4; private readonly Color _defaultColor = Color.DarkGray; private const int MacOsxInnerCircleRadius = 5; private const int MacOsxOuterCircleRadius = 11; private const int MacOsxNumberOfSpoke = 12; private const int MacOsxSpokeThickness = 2; private const int FireFoxInnerCircleRadius = 6; private const int FireFoxOuterCircleRadius = 7; private const int FireFoxNumberOfSpoke = 9; private const int FireFoxSpokeThickness = 4; private const int Ie7InnerCircleRadius = 8; private const int Ie7OuterCircleRadius = 9; private const int Ie7NumberOfSpoke = 24; private const int Ie7SpokeThickness = 4; // Enumeration ======================================================= public enum StylePresets { MacOsx, Firefox, Ie7, Custom } // Attributes ======================================================== private readonly DispatcherTimer _timer; private bool _isTimerActive; private int _numberOfSpoke; private int _spokeThickness; private int _progressValue; private int _outerCircleRadius; private int _innerCircleRadius; private PointF _centerPoint; private Color _color; private Color[] _colors; private double[] _angles; private StylePresets _stylePreset; // Properties ======================================================== /// /// Gets or sets the lightest color of the circle. /// /// The lightest color of the circle. [TypeConverter("System.Drawing.ColorConverter"), Category("LoadingCircle"), Description("Sets the color of spoke.")] public Color Color { get => _color; set { _color = value; GenerateColorsPallet(); Invalidate(); } } /// /// Background color /// public Color BgColor { get; set; } /// /// Gets or sets the outer circle radius. /// /// The outer circle radius. [Description("Gets or sets the radius of outer circle."), Category("LoadingCircle")] public int OuterCircleRadius { get { if (_outerCircleRadius == 0) { _outerCircleRadius = DefaultOuterCircleRadius;} return _outerCircleRadius; } set { _outerCircleRadius = value; Invalidate(); } } /// /// Gets or sets the inner circle radius. /// /// The inner circle radius. [Description("Gets or sets the radius of inner circle."), Category("LoadingCircle")] public int InnerCircleRadius { get { if (_innerCircleRadius == 0) _innerCircleRadius = DefaultInnerCircleRadius; return _innerCircleRadius; } set { _innerCircleRadius = value; Invalidate(); } } /// /// Gets or sets the number of spoke. /// /// The number of spoke. [Description("Gets or sets the number of spoke."), Category("LoadingCircle")] public int NumberSpoke { get { if (_numberOfSpoke == 0) _numberOfSpoke = DefaultNumberOfSpoke; return _numberOfSpoke; } set { if (_numberOfSpoke != value && _numberOfSpoke > 0) { _numberOfSpoke = value; GenerateColorsPallet(); GetSpokesAngles(); Invalidate(); } } } /// /// Gets or sets a value indicating whether this is active. /// /// true if active; otherwise, false. [Description("Gets or sets the number of spoke."), Category("LoadingCircle")] public bool Active { get => _isTimerActive; set { _isTimerActive = value; ActiveTimer(); } } /// /// Gets or sets the spoke thickness. /// /// The spoke thickness. [Description("Gets or sets the thickness of a spoke."), Category("LoadingCircle")] public int SpokeThickness { get { if (_spokeThickness <= 0) _spokeThickness = DefaultSpokeThickness; return _spokeThickness; } set { _spokeThickness = value; Invalidate(); } } /// /// Gets or sets the rotation speed. /// /// The rotation speed. [Description("Gets or sets the rotation speed. Higher the slower."), Category("LoadingCircle")] public long RotationSpeed { get => _timer.Interval.Ticks; set { if (value > 0) _timer.Interval = TimeSpan.FromMilliseconds(value); } } /// /// Quickly sets the style to one of these presets, or a custom style if desired /// /// The style preset. [Category("LoadingCircle"), Description("Quickly sets the style to one of these presets, or a custom style if desired"), DefaultValue(typeof(StylePresets), "Custom")] public StylePresets StylePreset { get => _stylePreset; set { _stylePreset = value; switch (_stylePreset) { case StylePresets.MacOsx: SetCircleAppearance(MacOsxNumberOfSpoke, MacOsxSpokeThickness, MacOsxInnerCircleRadius, MacOsxOuterCircleRadius); break; case StylePresets.Firefox: SetCircleAppearance(FireFoxNumberOfSpoke, FireFoxSpokeThickness, FireFoxInnerCircleRadius, FireFoxOuterCircleRadius); break; case StylePresets.Ie7: SetCircleAppearance(Ie7NumberOfSpoke, Ie7SpokeThickness, Ie7InnerCircleRadius, Ie7OuterCircleRadius); break; case StylePresets.Custom: SetCircleAppearance(DefaultNumberOfSpoke, DefaultSpokeThickness, DefaultInnerCircleRadius, DefaultOuterCircleRadius); break; } } } static int _xxx = 0; /// /// Construtor Initializes a new instance of the class. /// public BusyWaitAnimation() { _xxx++; _color = _defaultColor; BgColor = Color.LightGray; GenerateColorsPallet(); GetSpokesAngles(); GetControlCenterPoint(); _timer = new DispatcherTimer(); _timer.Tick += new EventHandler(aTimer_Tick); ActiveTimer(); } /// /// Handles the Tick event of the aTimer control. /// /// The source of the event. /// The instance containing the event data. void aTimer_Tick(object sender, EventArgs e) { _progressValue = ++_progressValue % _numberOfSpoke; Invalidate(); } /// /// Raises the event. /// /// A that contains the event data. private void OnPaint(Graphics g) { if (_numberOfSpoke <= 0) return; _backgroundGraphics.Clear(BgColor); g.SmoothingMode = SmoothingMode.HighQuality; var intPosition = _progressValue; for (var intCounter = 0; intCounter < _numberOfSpoke; intCounter++) { intPosition = intPosition % _numberOfSpoke; DrawLine(_backgroundGraphics, GetCoordinate(_centerPoint, _innerCircleRadius, _angles[intPosition]), GetCoordinate(_centerPoint, _outerCircleRadius, _angles[intPosition]), _colors[intCounter], _spokeThickness); intPosition++; } g.DrawImage(_backgroundImage, new Point(_drawArea.Left, _drawArea.Top)); } // Methods =========================================================== /// /// Darkens a specified color. /// /// Color to darken. /// The percent of darken. /// The new color generated. private Color Darken(Color objColor, int intPercent) { int intRed = objColor.R; int intGreen = objColor.G; int intBlue = objColor.B; return Color.FromArgb(intPercent, Math.Min(intRed, byte.MaxValue), Math.Min(intGreen, byte.MaxValue), Math.Min(intBlue, byte.MaxValue)); } /// /// Generates the colors pallet. /// private void GenerateColorsPallet() { _colors = GenerateColorsPallet(_color, Active, _numberOfSpoke); } /// /// Generates the colors pallet. /// /// Color of the lightest spoke. /// if set to true the color will be shaded on X spoke. /// /// An array of color used to draw the circle. private Color[] GenerateColorsPallet(Color color, bool bhadeColor, int nbSpoke) { var objColors = new Color[NumberSpoke]; // Value is used to simulate a gradient feel... For each spoke, the // color will be darken by value in intIncrement. var bytIncrement = (byte)(byte.MaxValue / NumberSpoke); //Reset variable in case of multiple passes byte percentageOfDarken = 0; for (var intCursor = 0; intCursor < NumberSpoke; intCursor++) { if (bhadeColor) { if (intCursor == 0 || intCursor < NumberSpoke - nbSpoke) objColors[intCursor] = color; else { // Increment alpha channel color percentageOfDarken += bytIncrement; // Ensure that we don't exceed the maximum alpha // channel value (255) if (percentageOfDarken > byte.MaxValue) percentageOfDarken = byte.MaxValue; // Determine the spoke forecolor objColors[intCursor] = Darken(color, percentageOfDarken); } } else objColors[intCursor] = color; } return objColors; } /// /// Gets the control center point. /// private void GetControlCenterPoint() { // m_CenterPoint = GetControlCenterPoint(this); _centerPoint = new PointF(_drawArea.Width / 2, _drawArea.Height / 2 - 1); } /// /// Draws the line with GDI+. /// /// The Graphics object. /// The point one. /// The point two. /// Color of the spoke. /// The thickness of spoke. private void DrawLine(Graphics graphics, PointF pointOne, PointF pointTwo, Color color, int lineThickness) { using (var objPen = new Pen(new SolidBrush(color), lineThickness)) { objPen.StartCap = LineCap.Round; objPen.EndCap = LineCap.Round; graphics.DrawLine(objPen, pointOne, pointTwo); } } /// /// Gets the coordinate. /// /// The Circle center. /// The radius. /// The angle. /// private PointF GetCoordinate(PointF circleCenter, int radius, double angle) { var dblAngle = Math.PI * angle / NumberOfDegreesInHalfCircle; return new PointF(circleCenter.X + radius * (float)Math.Cos(dblAngle), circleCenter.Y + radius * (float)Math.Sin(dblAngle)); } /// /// Gets the spokes angles. /// private void GetSpokesAngles() { _angles = GetSpokesAngles(NumberSpoke); } /// /// Gets the spoke angles. /// /// The number spoke. /// An array of angle. private double[] GetSpokesAngles(int numberSpoke) { var angles = new double[numberSpoke]; var angle = NumberOfDegreesInCircle / numberSpoke; for (var shtCounter = 0; shtCounter < numberSpoke; shtCounter++) angles[shtCounter] = (shtCounter == 0 ? angle : angles[shtCounter - 1] + angle); return angles; } /// /// Actives the timer. /// private void ActiveTimer() { if (_isTimerActive) _timer.Start(); else { _timer.Stop(); _progressValue = 0; } GenerateColorsPallet(); Invalidate(); } /// /// Sets the circle appearance. /// /// The number spoke. /// The spoke thickness. /// The inner circle radius. /// The outer circle radius. public void SetCircleAppearance(int numberSpoke, int spokeThickness, int innerCircleRadius, int outerCircleRadius) { NumberSpoke = numberSpoke; SpokeThickness = spokeThickness; InnerCircleRadius = innerCircleRadius; OuterCircleRadius = outerCircleRadius; Invalidate(); } #region new methods private Rectangle _drawArea; private Graphics _graphics; private int _hDc = int.MinValue; private Bitmap _backgroundImage; private Graphics _backgroundGraphics; private void Invalidate() { if (_graphics != null) { _graphics.DrawImageUnscaled(_backgroundImage, new Point(_drawArea.Left, _drawArea.Top)); OnPaint(_graphics); } } public Graphics GDIGraphics => _graphics; public int hDC { set { if (_graphics != null) _graphics.Dispose(); _hDc = value; _graphics = Graphics.FromHdc(new IntPtr(value)); } get => _hDc; } public void SetDrawArea(Rectangle rect) { _drawArea = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height); GetControlCenterPoint(); _backgroundImage = new Bitmap(rect.Width, rect.Height); _backgroundGraphics = Graphics.FromImage(_backgroundImage); _backgroundGraphics.SmoothingMode = SmoothingMode.HighQuality; } #endregion } }