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
