views:

669

answers:

5

I've been trying to smoothly animate some Windows Form location but I'm having some issues if I want the speed to be variable. In other words, if I want to allow the user to select the preferred speed for animation.

I've found the following article that helped me quite a bit to perform the animation I was looking for, for my form. It seems better in every way than a BackgroundWorker or Threads approach I tried in the past: http://www.vcskicks.com/animated-windows-form.html

My only problem now, is to maintain a smooth animation if I want o have different speeds for the animation. There are two values that are important in my code, FPS and PX. FPS represents frames per second (what else) and PX represents the number of pixels to move the form.

Problem 1) To have the smoothest possible animation, I would prefer to have the form move 1px at a time but I don't think that I will be able to move the form as fast as I want like that. Increasing the FPS value to a very high value doesn't seem to take any effect, it's like there's a limit and above that limit, there will be no visible differences. I'm sure there's a good explanation for that.

My question here is: Do you have any good solution for this problem or the only solution is to change the PX value and move the form by more than 1px if I want a faster movement?

Problem 2) If the solution for the question above is to change the PX value accordingly, I found out (by testing different values) that a FPS value equal to 300 would suffice my needs to move the form as slow and as fast as I want it to. Then, if I wanted 10 speeds, moving the form by 1, 2, 3, 4, 5, 6, 7, 8, 9 and 10 pixels provides slow and fast smooth animations, just as I want it. If I wanted 5 speeds, I could use 2, 4, 6, 8, 10, for instance.

My question here is: Is there any problem to use a value like 300 for FPS? Are there any bad consequences for such a value?

And here's my current code:

public partial class Form1 : Form {

    bool dir = true;

    public Form1() {
        InitializeComponent();

        Location = new Point(1280/2 - Width, 800/2 - Height/2);
    }

    private void button1_Click(object sender, EventArgs e) {
        double FPS = 300;
        int PX = 1;
        long lastTicks = 0;
        long currentTicks = 0;
        double interval = (double)Stopwatch.Frequency / FPS;

        while(dir ? Left <= 1280/2 : Left >= 1280/2 - Width) {
            Application.DoEvents();

            currentTicks = Stopwatch.GetTimestamp();

            if(currentTicks >= lastTicks + interval) {
                lastTicks = Stopwatch.GetTimestamp();

                this.Location = new Point(dir ? Left + PX : Left - PX, Top);

                this.Invalidate(); //refreshes the form
            }

            Thread.Sleep(1); //frees up the cpu
        }

        dir = !dir;
    }

}

Note: This is just sample code, for testing purposes, not real code but be my guest if you want to point out some very important things that I should consider when porting this to the real application.

+1  A: 

I think Windows limits the repaint rate, so it wouldn't matter if you cranked the FPS up to insane values; if you want to see higher frame rates you'll probably have to result to something like XNA/DirectX animation.

You could use a Timer, and write an elapsed event handler that would both move & invalidate your form. To that end you wouldn't have to do the Thread.Sleep or the book-keeping with the last ticks and interval calcs, and it would happen with a regular cadence. Additionally, instead of the conditional code around the 'dir' boolean, you could negate the PX value when you want to change directions (and do additions-only instead of the ternary operator on dir); this is possible since subtraction is the same as adding a negative.

This should make your animation easier to extend to do other things. For fun, you could also create a PY to move vertically as well. Whatever the case, I hope you have some fun with it. :)

devgeezer
A Timer doesn't allow the accuracy I'm looking for. And I'm sure you didn't even read the link I posted because the Thread.Sleep() is there only for a reason and that reason is not to control the animation speed, but like the comment says, to free the CPU. Without that line, the CPU usage will be 100% while animating.
Nazgulled
I just read the link, a lot shorter than I expected. I'm trying to help here. I would expect the winforms timer to be of a longer duration than the Threading.Timer class because it probably works off the WM_TIMER windows event. Barring timers, how about overriding OnPaint() and calculating the new position based on the time between OnPaint() calls? This seems cleaner to me than doing the entire animation in one method call. I'd also guess that this is for control animation and that you're not trying to do this for a game of some kind, right?
devgeezer
OnPaint will only be called when needed, not when I need to move the form. Either way, I don't think that's close to a proper solution to do any kind of animation. But this may be just me... What's so bad in doing an animation in one method call? How is that "dirty"? It seems pretty clean to me and uses the system clock to produce a smooth animation across all systems. Seems the perfect solution actually. And that's what I'm going to use (most likely), so, no point in discussing this any further. Either way, this has nothing to do with my questions/problems.
Nazgulled
A: 

Any more ideas?

I'm really surprised to see only one answer about this... I normally have lots of answers lol. This doesn't seem a very difficult subject, is it? :P

Yes, I know everybody has things to attend to... :)

Nazgulled
+2  A: 

Try this implementation of my prior suggestion (without accounting for FPS):

public partial class Form1 : Form
{
    private bool go;
    private int dx = 1;
    public Form1()
    {
        InitializeComponent();
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (go)
        {
            this.Location = new Point(this.Location.X + dx, this.Location.Y);
            if (Location.X < 10 || Location.X > 1200)
            {
                go = false;
                dx = -dx;
            }
            else
            {
                this.Invalidate();
            }
        }
    }
    private void button1_Click(object sender, EventArgs e)
    {
        go = true;
        this.Invalidate();
    }
}

I think you will likely have to go outside of winforms to get higher FPS.

devgeezer
A: 

Super Over-Kill Solution

Summary: make an animation of your window moving, and play it back (full-screen, perhaps)

Details:
Suppose your window is 100x100, and is @0,0
Take a screen-shot of the, well, screen : (0,0)-(200,200).
Use the screen-shot to create a clip, that depicts moving your window from 0,0 to 100,100
Then create a borderless window (0,0)-(200,200), and put a video-player on it, and replay the animation of your window moving inside.

Needless to say your window will be static. But you can get the best animation there is. You can even add some effects to your window moving, like bullets in the matrix, or Necromunga ships.

A: 

If you got the results you want with a FPS value of 300 then I would go with that. The concept of Frames Per Second for WinForms is not the same as graphic-intensive video games, so a high value won't be a problem. The FPS value here simply adjusts how often the code is executed.