How to animate dots in UserControl Paint event?

Viewed 105

In paint event because i want to be able to control the dots size colors and more properties.

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class LoadingLabel : UserControl
{
    public LoadingLabel()
    {
        InitializeComponent();
    }

    private void LoadingLabel_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.FillEllipse(Brushes.Red, 1, 1, 20, 20);
        Thread.Sleep(1);
        e.Graphics.FillEllipse(Brushes.Red, 1, 1, 0, 0);
        Thread.Sleep(1);
    }
}

I tried first to make a simple dot that is disappearing after some time and then show again but it's not working i see a red still dot(point).

later when this will work i want to make 3 dots animating like a loading animation.

This is what I've tried:

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class LoadingLabel : UserControl
{
    private bool animate = false;

    public LoadingLabel()
    {
        InitializeComponent();
        timer1.Enabled = true;
    }

    private void LoadingLabel_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        if (animate == false)
        {
            e.Graphics.FillEllipse(Brushes.Red, 1, 1, 20, 20);
        }
        else
        {
            e.Graphics.FillEllipse(Brushes.Red, 5, 1, 20, 20);
        }
    }

    int count = 0;
    private void timer1_Tick(object sender, EventArgs e)
    {
        count++;

        if(count == 10 && animate == false)
        {
            animate = true;
        }

        if(count == 20 && animate)
        {
            animate = false;
            count = 0;
        }
        this.Invalidate();
    }
}

the result is the first point draw then the second point draw but the first one is gone:

it looks like the point is moving to the right and back to the left.

point moving

but i want a loading effect with 3 points. and not moving point.

This is working with 3 points but it looks too complicated for 3 points. and if i want 100 points?

maybe i should use a loop inside the paint event ?

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class LoadingLabel : UserControl
{
    private int numofpoints = 0;

    public LoadingLabel()
    {
        InitializeComponent();
        timer1.Enabled = true;
    }

    private void LoadingLabel_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        if(numofpoints == 0)
        {
            e.Graphics.FillEllipse(Brushes.Red, 1, 1, 20, 20);
        }
        if(numofpoints == 1)
        {
            e.Graphics.FillEllipse(Brushes.Red, 5, 1, 20, 20);
        }
        if(numofpoints == 2)
        {
            e.Graphics.FillEllipse(Brushes.Red, 10, 1, 20, 20);
        }
    }

    int count = 0;
    private void timer1_Tick(object sender, EventArgs e)
    {
        count++;

        if(count == 10)
        {
            numofpoints = 0;
        }

        if(count == 20)
        {
            numofpoints = 1;
        }

        if(count == 30)
        {
            numofpoints = 2;
            count = 0;
        }
        this.Invalidate();
    }
}

Another update of what I've tried:

 using System.ComponentModel;
 using System.Drawing;
 using System.Drawing.Drawing2D;
    
 public partial class LoadingLabel : UserControl
 {
     private List<PointF> points = new List<PointF>();

     public LoadingLabel()
     {
         InitializeComponent();
         points.Add(new PointF(0, 0));
         timer1.Enabled = true;
     }

     private void LoadingLabel_Paint(object sender, PaintEventArgs e)
     {
         e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            
         for (int i = 0; i < points.Count; i++)
         {
             e.Graphics.FillEllipse(Brushes.Red, points[i].X, points[i].Y, 20, 20);
         }
     }

     int count = 0;
     private void timer1_Tick(object sender, EventArgs e)
     {
         count++;

         if (count < 3)
         {
             points.Add(new PointF(count * 20, 0));
             //points = new List<PointF>();
         }
         //this.Invalidate();
     }
 }

If i will make the instance in the tick event it will not draw anything. if i will use the Invalidate line it will make the points to be like blinking. what i want is to create a loading effect animation.

the result as the code now is still 3 points, and i want to animate them like in the link.

points

Something like this:

Animated dots

2 Answers

Since, based on the image you have posted, you want to animate a series of Dots, where only the active one changes color, your UserControl can define Properties that allow to specify the number of Dots, the Color of a Dot and the Color of the active Dot.

A Timer can be used to change the current active Dot, so the paint procedure knows when to change the color of one of the Dots.

The UserControl is automatically resized when the number of Dots specified changes.
Also when the UserControl is first created, it sets its MinimumSize, so the Dots are always visible.

You can expand on this template, adding more features.


Note these lines in the Constructor of the UserControl:

components = new Container();
dotsTimer = new Timer(components) { ... };

This instructs the Timer Component to add itself to the Components of the Parent container, so when the Parent is disposed, the Timer is also disposed and its event handler(s) removed.

Setting DoubleBuffered = true; avoids flickering when the Dots are drawn.


Call the Start() method to start the animation and the Stop() method to, well, stop it.

using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;

public partial class LoadingLabel : UserControl {

    private int m_NumberOfDots = 5;
    private Color m_DotColor = Color.Cyan;
    private Color m_DotActiveColor = Color.Blue;
    private float dotSize = 20.0f;
    private float dotSpacing = 20.0f;
    private int currentDot = 0;
    private Timer dotsTimer = null;

    public LoadingLabel()
    {
        InitializeComponent();
        components = new Container();
        dotsTimer = new Timer(components) { Interval = 200 };
        dotsTimer.Tick += DotsTimer_Tick;

        DoubleBuffered = true;
        Padding = new Padding(5);
    }

    [DefaultValue(5)]
    public int NumberOfDots {
        get => m_NumberOfDots;
        set {
            value = Math.Max(3, Math.Min(value, 7));
            if (m_NumberOfDots != value) {
                m_NumberOfDots = value;

                bool running = dotsTimer.Enabled;
                Stop();
                SetMinSize();
                if (running) Start();
            }
        }
    }

    [DefaultValue(typeof(Color), "Cyan")]
    public Color DotColor { 
        get => m_DotColor;
        set {
            m_DotColor = value;
            Invalidate();
        } 
    }

    [DefaultValue(typeof(Color), "Blue")]
    public Color DotActiveColor { 
        get => m_DotActiveColor;
        set {
            m_DotActiveColor = value;
            Invalidate();
        } 
    }

    protected override void OnPaint(PaintEventArgs e) {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        for (int dot = 0; dot < m_NumberOfDots; dot++) {
            var color = dot == currentDot ? DotActiveColor : DotColor;
            var pos = Padding.Left + (dotSize + dotSpacing) * dot;
            using (var brush = new SolidBrush(color)) {
                e.Graphics.FillEllipse(brush, pos, Padding.Top, dotSize, dotSize);
            }
        }
        base.OnPaint(e);
    }

    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        SetMinSize();
    }

    protected override void OnHandleDestroyed(EventArgs e) {
        Stop();
        base.OnHandleDestroyed(e);
    }

    private void DotsTimer_Tick(object sender, EventArgs e) {
        currentDot += 1;
        currentDot %= m_NumberOfDots;
        Invalidate();
    }

    public void Start() => dotsTimer.Start();

    public void Stop() {
        dotsTimer.Stop();
        currentDot = 0;
        Invalidate();
    }

    private void SetMinSize() {
        var width = Padding.Left + Padding.Right + 
            (dotSize * m_NumberOfDots) + (dotSpacing * (m_NumberOfDots - 1)) + 1;
        var height = Padding.Top + Padding.Bottom + dotSize + 1;
        MinimumSize = new Size((int)width, (int)height);
        Size = MinimumSize;
    }
}

This is how it works:

Animated Dot


On demand, this is the PasteBin of the custom ComboBox Control used to select a Color.

A single call to the Paint method/event should draw the control as it is supposed to look at that instant. If you wish to add animation, you should make the control redraw itself repeatedly and use some internal state to keep track of the animation.

Related