0

The original code comes from this answer:
How to animate dots in UserControl Paint event?

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

I want that the interval property will show when I'm dragging the control in form1 designer like the m_DotColor for example.

This line creates the problem in the DotsTimer_Tick event:

dotsTimer.Interval = TimerInterval;

but when I'm dragging the control now into the Form's Designer, the whole project freeze shut down and Visual Studio start over again and loading the project again.

A screenshot of the PropertyGrid, without the interval part in the tick event.
I removed the line from the Tick event. In the properties, the dot color and dot active color are listed in the properties; I want to change the Interval value in the same way.

properties without the interval line in the tick event what cause the problem

Screenshot of the control on form1 designer:

the control after dragging it in form1 designer

Now I can change the colors of the DotActiveColor and DotColor before running the program! The same I want to do with the Interval to be able to change the speed of the timer before running the program.

Jimi
  • 29,621
  • 8
  • 43
  • 61
jhon last
  • 109
  • 6
  • Remove Invalidate(); and see what happens. You do not want to Invalidate(); the form. – jdweng Sep 24 '22 at 14:58
  • You should not "start" anything inside a `Form` or `Control` constructor - move it to the `OnLoad` method. – Dai Sep 24 '22 at 15:00
  • @Dai i wanted to be able to change the speed of the loading label before running the program/. if i'm running it without the start and put the start in the OnLoad it's working but then i can't change the properties at runtime. the properties i can change only if the program is not running. – jhon last Sep 24 '22 at 15:05
  • @jdweng if i'm removing the invalidate in loading label user control then when dragging it to the form1 designer it won't move at all the animation won't move. – jhon last Sep 24 '22 at 15:06
  • @Dai i'm not. when i'm dragging the user control to form1 designer then i can change the properties values for example like in the screenshot i can change the dot color and dot color active and it will change the color of the loading label before running the program. the same i want to do with the interval. – jhon last Sep 24 '22 at 15:08
  • 1
    @jhonlast You mean you're (ab)using the WinForms designer to run your code _within the design surface_ to preview what the timer does? – Dai Sep 24 '22 at 15:09

1 Answers1

2

If you want to see in the designer what the animation is going to be, you can add a public Property that allows to start / stop the Timer at Design-Time.

Note that you have to initialize the backing Field of a Property to the value set as DefaultValue, as in here:

private int m_Interval = 200;

The DefaultValue attribute doesn't set the Field, it prevents the serialization of the Property value if it matches the value set as the default.


I've added a AnimationEnabled public Property that can be set in the PropertyGrid, to start and stop the animation on demand.

Do not start the Timer in the Constructor of your UserControl. If you want to see the animation when the UserControl is first created (when dropped on a Form), you may use the OnHandleCreated() override. I.e., don't start the Timer until your UC has a Handle.

Also, the System.Windows.Forms.Timer has an official maximum resolution (min. Interval) of 55ms, though it can work at 35ms. At 55ms it's already a quite fast animation anyway.

public partial class LoadingLabel : UserControl
{
    // [...]
    private Timer dotsTimer = null;
    private int m_Interval = 200;
    // [...]

    public LoadingLabel() {
        InitializeComponent();

        components = new Container();
        dotsTimer = new Timer(components) { Interval = m_Interval };
        dotsTimer.Tick += DotsTimer_Tick;

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

    [DefaultValue(false)]
    public bool AnimationEnabled { 
        get => dotsTimer.Enabled;
        set { 
            if (value) Start(); else Stop(); 
        }
    }

    [DefaultValue(200)]
    public int TimerInterval {
        get => m_Interval;
        set {
            value = Math.Max(55, Math.Min(value, 500));
            if (m_Interval != value) {
                m_Interval = value;
                dotsTimer.Interval = m_Interval;
            }
        }
    }

    [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();
        // Start the Timer here - eventually - and change the default value of 
        // AnimationEnabled to true
        // Start();
    }

    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 looks now at Design-Time:
Starting / stopping the Timer and changing the Interval

Dots Timer at Design-Time

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • 55 msec is a DOS/Win9x number. NT versions for the past 28 years tick at least at 16 msec (15.625 to be exact). – Hans Passant Sep 24 '22 at 22:36
  • @HansPassant Just reporting what's in the Remarks section of the Timer's documentation (still in the Windows Desktop 6 docs). That's why I've defined it the *official* value (the underlying code also takes its time) – Jimi Sep 24 '22 at 22:42
  • That was written at .NET 1.0 time, back when Win98 was still supported. No need to continue to quote inaccurate docs. – Hans Passant Sep 24 '22 at 23:10
  • @HansPassant `USER_TIMER_MINIMUM` is set to `0x0A`; this Timer uses the `WM_TIMER` message mode, which is low-priority (and needs to be handled in a Desktop app). Which Docs should I refer to, *officially*? Do you have an *official* source that states what's the *exact minimum interval* that can be trusted (to an extent)? My (no so recent) tests point to the 35ms I mentioned. But that's far for *official* – Jimi Sep 25 '22 at 00:02
  • @Jimi how can i add a ui button to the properties so if i made changes to some properties like changed colors the interval and then clicking the button it will save the changes so each time later when i drag the control again to the form1 designer it will be with the saved changes ? – jhon last Sep 25 '22 at 02:36
  • 1
    You have to describe how this Control is used. E.g., it's an UC created inside your own Project, or it has its own Project (and its own assembly) or this is an UC that is meant for distribution and used by 3rd parties. You can use the ApplicationSettings in most cases, but these details must be specified. You probably better ask a new question for this – Jimi Sep 25 '22 at 03:06