Search code examples
c#winformsc#-4.0timing

Adding delay while taking in account execution time


So, I'm trying to create a method that animates the movement of a control on a form. I generate all the points the control is going to travel to beforehand, like this:

private static List<decimal> TSIncrement(int durationInMilliseconds, decimal startPoint, decimal endPoint)
    {
        List<decimal> tempPoints = new List<decimal>();
        decimal distance = endPoint - startPoint;
        decimal increment = distance / durationInMilliseconds;
        decimal tempPoint = (decimal)startPoint;

        for (decimal i = durationInMilliseconds; i > 0; i--)
        {
            tempPoint += increment;
            tempPoints.Add(tempPoint);
        }
        return tempPoints;
    }

This outputs a list with as many points as there are milliseconds in the duration of the animation. I think you can guess what I'm doing afterwards:

public static void ControlAnimation(Control control, Point locationEndpoint, int delay)
    {
        if (delay > 0)
        {
            List<decimal> tempXpoints = TSIncrement(delay, control.Location.X, locationEndpoint.X);
            List<decimal> tempYpoints = TSIncrement(delay, control.Location.Y, locationEndpoint.Y);

            for (int i = 0; i < delay; i++)
            {
                control.Location = new Point((int)Math.Round(tempXpoints[i]), (int)Math.Round(tempYpoints[i]));
                Thread.Sleep(1); //I won't leave this obviously, it's just easier for now
            }
        }
    }

In the actual method, I go through this list of points and use those to create the new location of the control (I actually use two lists for the abscissa and the ordinate).

My problem lies in creating one millisecond of delay between each shifting. Since the code in the loop takes a bit of time to execute, I usually end up with approximately 5 seconds more duration.

I tried using a stopwatch to measure the time it takes to set control.location, and subtracting that to the 1 millisecond delay. The stopwatch adds some delay as well though, since I gotta start, stop and reset it everytime.

So what should I do, and how could I improve my code? Any feedback is greatly appreciated :)


Solution

  • You won't get a reliable delay below around 50 milliseconds in WinForms, so that is the delay I used below:

    private Random R = new Random();
    
    private async void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        label1.AutoSize = false;
        label1.Size = button2.Size;
        Point p = new Point(R.Next(this.Width - button2.Width), R.Next(this.Height - button2.Height));
        label1.Location = p;
        label1.SendToBack();
    
        await MoveControl(button2, p, R.Next(2000, 7001));
    
        button1.Enabled = true;
    }
    
    
    private Task MoveControl(Control control, Point LocationEndPoint, int delayInMilliseconds)
    {       
        return Task.Run(new Action(() =>
        {
            decimal p;
            int startX = control.Location.X;
            int startY = control.Location.Y;
            int deltaX = LocationEndPoint.X - startX;
            int deltaY = LocationEndPoint.Y - startY;
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
    
            while(sw.ElapsedMilliseconds < delayInMilliseconds)
            {
                System.Threading.Thread.Sleep(50);
                p = Math.Min((decimal)1.0, (decimal)sw.ElapsedMilliseconds / (decimal)delayInMilliseconds);
                control.Invoke((MethodInvoker)delegate {
                    control.Location = new Point(startX + (int)(p * deltaX), startY + (int)(p * deltaY));
                });
            }
        }));
    }