2

I'm making C# windows forms application in which I want to make smooth animation movement of a black bar over a white background. I tried the following code but the movement is flickering and not achieving smooth rapid movement even on 1 ms interval timer.

I tried the following code :

    public Form1()
    {
        InitializeComponent();
        DoubleBuffered = true;
        System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
        timer1.Interval = 30;
        timer1.Tick += Timer1_Tick;
        timer1.Enabled = true;
    }
    int x_position = 0;
    int bar_width = 40;
    int speed = 1;
    private void Timer1_Tick(object sender, EventArgs e)
    {
        x_position += speed;
        if (x_position + bar_width > this.Width) x_position = 0;
        Invalidate();
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
    }

The result is not smooth movement at all. I found on another questions something like Invalidate(true) or DoubleBuffered = true; but they didn't solve the issue.

Waiting for help. Thank you

EgyEast
  • 1,542
  • 6
  • 28
  • 44
  • Possible duplicate of [How to create a smooth animation using C# Windows forms?](https://stackoverflow.com/questions/14499803/how-to-create-a-smooth-animation-using-c-sharp-windows-forms) – jazb Oct 14 '19 at 07:37
  • 4
    Really smoth animation is not really possible in winforms as it won't let you synch your code with the monitor refresh. Doublebuffering the form is needed to avoid flicker and will work. Using this.CreateGraphics(); and then gF.Clear will guarantee flicker to return though. Draw in the Paint with e.Graphics instead! - Also: Timer has a resolution on 25-30 ms only and will not allow a reliable frame rate! – TaW Oct 14 '19 at 09:06
  • @Taw Thank you for tour note about the timer resolution. I Tried loop with application.DoEvents and the speed increased a bit. However Double buffering couldn't remove flickering with me at all. I edited the code in the question to the last update – EgyEast Oct 14 '19 at 15:22
  • 1
    Using CreateGraphics will never do. You need to use the Paint event.. – TaW Oct 14 '19 at 15:39
  • @Taw I edited the code according to your notes but something wrong preventing it from running well. Could you please revise the code ? – EgyEast Oct 14 '19 at 17:41
  • I would simply put an Invalidate() into a Timer.Tick. Any reason to Clear to white? – TaW Oct 14 '19 at 17:43
  • @Taw The timer Tick resolution is more than 25ms (as you said). This makes the movement relatively slow. though i used loop instead while controlling time with Thread.sleep. I Clear to white to redraw the rectangle every time. if removed this part all form gradually goes black till bar reach end of form. – EgyEast Oct 14 '19 at 18:01
  • this.invalidate() instead cause much more flickering. – EgyEast Oct 14 '19 at 18:05
  • _The timer Tick as you said resolution is more than 25ms. This makes the movement relatively slow._ No, it doesn't. You should keep track of positions by using floats. 25ms is ca. 40Hz. Anything from 16Hz looks like movement to the human eyes. But there will be some moments of 'stutter' now and then. Can't be avoided easily..Paint will clear the canvas by itself.Remove the Clear! And remove the loop for the Timer.. – TaW Oct 14 '19 at 18:05
  • Using timer makes movement relatively slow. Without using Clear method screen gradually goes black with bar movement. – EgyEast Oct 14 '19 at 22:30
  • 2
    Remove `while (true)`, `Thread.Sleep(3)` and `Application.DoEvents()` (the latter being a real killer) from inside the Paint `event`. This destroys any attempt to make anything work. It's `Timer.Tick` event that modifieds the positions values and calls `Invalidate()` (which raises the `Paint` event). The Timer interval cannot be less than 30 ms (you have to paint stuff in the meantime). – Jimi Oct 14 '19 at 23:06
  • 1
    Also, you don't need to `.Clear(Color.White);`. You're painting a single shape, the previous one won't persist. You're drawing on a control's surface, not inside a Bitmap (where the drawing would persist instead). – Jimi Oct 14 '19 at 23:12
  • @Jimi I edited code as you said but this results in more flickering movement of the black bar. Hope you can test the code and revise if possible. You'd save me a lot of pain. – EgyEast Oct 14 '19 at 23:25
  • 1
    Nope. You didn't do what I said. In the `Timer.Tick` event you need to redefine the location of the object, then call `Invalidate()`. This raises the Paint event of the Form. **In the Paint event handler you draw the your shape(s)**, using the current values to position it. Don't use `Control.CreateGraphics()` to draw on a control's surface. That method is not used for this kind of operations. **The Timer interval cannot be set to less than 30**. – Jimi Oct 14 '19 at 23:30
  • @Jimi Thank you for your help. I modified code according to your guide and flickering went a way. Last thing i want movement of bar to be more rapid. Isn't there a way other than increasing increment of x_position by more than 1 pixel per Tick (which is speed in my code) ? as this make movement less smooth ? – EgyEast Oct 14 '19 at 23:36
  • 1
    You can set the `speed` value to whatever you want. Test it to find the values that fit your expectations. Maybe, use some TrackBar controls to change these values on-the-fly, so you can see the effects of a change in real-time. – Jimi Oct 14 '19 at 23:38
  • Ok, thank you very much for your help. – EgyEast Oct 14 '19 at 23:39

1 Answers1

2

The Winforms API, and the native Win32 window-messaging API on which it's built, is simply not designed for "smooth animation". It will be difficult, if not impossible, to achieve perfectly smooth animation.

That said, much can be done to improve on your attempt above:

  • Don't use CreateGraphics(). Instead, always draw in response to the Paint event.
  • Don't use any of the .NET timer implementations to schedule rendering. Instead, draw as quickly as you can, taking into account time between frames to update your object position.

Applying these ideas, here's a version of your code that works much better:

const float speedXPerSecond = 1000f / 30;
const int bar_width = 40;

public Form1()
{
    InitializeComponent();
    DoubleBuffered = true;
}

float x_position = 0;
TimeSpan _lastFrameTime = TimeSpan.Zero;
Stopwatch _frameTimer = Stopwatch.StartNew();

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    TimeSpan currentFrameTime = _frameTimer.Elapsed;
    float distance = (float)(currentFrameTime - _lastFrameTime).TotalSeconds * speedXPerSecond;

    x_position += distance;
    while (x_position > this.Width) x_position -= this.Width;
    e.Graphics.FillRectangle(Brushes.Black, x_position, 0, bar_width, 500);
    _lastFrameTime = currentFrameTime;

    Invalidate();
}

You can apply this general technique to any interactive scenario. A real game will have other elements to this general "render loop", including user input and updating game state (e.g. based on a physics model). These will add overhead to the render loop, and will necessarily reduce frame rate. But for any basic game on any reasonably recent hardware (e.g. built in the last couple of decades), the net frame rate will still be well above that needed for acceptably smooth game-play.

Do note that in the managed .NET/Winforms context, there will always be limits to the success of this approach as compared to using a lower-level API. In particular, without some extra work on your part, garbage collection will interrupt periodically, causing the frame rate to stutter slightly, as well unevenness in thread scheduling and the thread's message queue.

But it's been my experience that people asking this sort of question don't need things to be absolutely perfect. They need "good enough", so that they can move on to learning about other topics involved in game development.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136