2

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

Jimi
  • 29,621
  • 8
  • 43
  • 61
jhon last
  • 109
  • 6
  • 2
    Never use Thread.Sleep() in a Paint event handler. You need a Timer, its Tick event handler needs to change state (modify dot location) and call this.Invalidate() to trigger another Paint event. – Hans Passant Sep 20 '22 at 14:36
  • i used timer and a flag bool variable and it's working. – jhon last Sep 20 '22 at 15:12
  • ok it's working but if i add another drawellipse line in the else when the flag is true it looks like it's moving the point to the side but i want to make 3 points and not to move them but to display each time the other one like this : . then .. then ... – jhon last Sep 20 '22 at 15:15
  • That's 3 distinct states. Obviously a *bool* no longer can represent that, use an int. – Hans Passant Sep 20 '22 at 15:17
  • @HansPassant edited my question once again with the latest code i tried. this time it's working with 3 points but it looks too complicated. what should i do instead adding more and more draw ellipse lines ? for example if i want to animated 100 points instead 3 ? should i use a for loop in the paint event ? – jhon last Sep 20 '22 at 15:26
  • Simply use a `List`; in the Timer event, add PointF elements to it - increasing X of the amount you prefer - while the `Count()` is less than the number of points you want to animate. After that, `Clear()` the List. In the `OnPaint` override, loop the List and draw those points. – Jimi Sep 20 '22 at 18:53
  • @Jimi i tried your solution and edited my question added to the bottom what i tried last. so now i can make 3 points but they are still and not animated like a loading effect. how can i animate them ? – jhon last Sep 21 '22 at 02:30

2 Answers2

5

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.

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Where did you add the ComboBox of the Dor Color and the Active Dot Color ? did you add them in form1 designer over the loading label control after dragged it to the form1 designer or added them in the loading label user control designer ? – jhon last Sep 21 '22 at 07:17
  • 1
    Those are two custom ComboBox Controls. All Controls you see there are child of a Form. I used these ComboBoxes for the animation you see here, to showcase the two properties of the UserControl that allow to set the dots' colors. You can do the same using the PropertyGrid at design-time, but see the notes about those two Properties (I'll edit it anyway). – Jimi Sep 21 '22 at 07:52
  • Working great. just one last thing. how did you make the custom combobxes so the colors are show inside ? it's not just adding color items , how did you make that it's showing the colors inside the combobx on the left side ? – jhon last Sep 21 '22 at 22:17
  • 1
    See the edit, at the bottom. I've posted a stripped-down version (because it's actually part of a Framework) of the ComboBox used to select a Color – Jimi Sep 21 '22 at 22:48
0

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.

M Kloster
  • 679
  • 4
  • 13