Home > Blockchain >  Winforms element movement lag
Winforms element movement lag

Time:01-02

I am currently working on my Course project and i need to make a Rythm game.

The issue im facing is in the note movment. Currently notes are represented by 5px tall buttons, that move down at 40 px a tick. I have a timer that moves every button in note list every tick and deletes them if they reached the border. Issue becomes apparent when there is more than 2 moving notes at the same time, time between every tick becomes longer the more notes there are on the screen.

Here is the code for note movment:

private void timer3_Tick(object sender, EventArgs e)
        {
            foreach (Button Note in AllNotes.ToList()){
                
                Note.Top -= 30;
          
                if (Note.Top < 0 || AllNotes.Count > 3)
                {
                    AllNotes.Remove(Note);
                    this.Controls.Remove(Note);
                }
            }
        }

Can such thing be fixed by turning on some feature like DoubleBuffering, or do i need to remake it completly in a different way?

One of the requirments is that it must be make in WinForms and not any advanced engine like Unity.

CodePudding user response:

In your code, there is a unit of work that we'll call "move notes" and you're doing this at set intervals. The tricky thing is that we really don't know how long it takes to move the notes. Obviously moving 10,000 notes will take longer than moving 10, and if it takes (hypothetically) a whole second to move them all and you're attempting to to this 10 times a second then things can get backed up and execute more and more slowly.

One thing you could try is a different kind of timer loop, basically:

  1. Move all the notes, no matter how long it takes.
  2. Wait for some time interval to expire.
  3. Repeat

Here's how this could look in the MainForm class:

public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();
    protected override void onl oad(EventArgs e)
    {
        base.OnLoad(e);
        _timerTask = Task.Run(() => execTimer(), _cts.Token);
        Disposed  = (sender, e) => _cts.Cancel();
    }
    private Task _timerTask;
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private void execTimer()
    {
        while(true)
        {
            // This is on a worker task. Blocking here
            // isn't going to block the UI thread.
            Thread.Sleep(TimeSpan.FromMilliseconds(100));
            if (_cts.IsCancellationRequested) break;

            // Execute some 'work' synchronously. This prevents
            // work from piling up if it can't be completed.
            TimerTick?.Invoke(this, EventArgs.Empty);
            if (_cts.IsCancellationRequested) break;
        }
    }
    internal static event EventHandler TimerTick;
    .
    .
    .
}

Your current code is iterating a list of Notes and instructing them to move one by one. Have you considered making the notes smart enough to move themselves? The way you would do this is to make a Note class that can respond to the timer events fired by the main form.

class Note : Button
{
    public Note(Point location)
    {
        Size = new Size(25, 25);    // Arbitrary for testing
        BackColor = Color.Black;
        Location = location;
        // Subscribe to the timer tick
        MainForm.TimerTick  = onTimerTick;
    }
    private void onTimerTick(object sender, EventArgs e)
    {
        BeginInvoke(new Action(() =>
        {
            // Execute the move
            Location = new Point(Location.X, Location.Y   10); 

            // Detect whether the location has moved off of the
            // main form and if so have the Note remove itself.
            if(!Parent.ClientRectangle.Contains(Location))
            {
                MainForm.TimerTick -= onTimerTick;
                Parent.Controls.Remove(this);
            }               
        }));
    }
}

To make sure that the motion doesn't get bogged down I did a simple test where I'm adding a Note for every mouse click.

screenshot

public partial class MainForm : Form
{
    .
    .
    .
    protected override void onm ouseClick(MouseEventArgs e)
    {
        base.OnMouseClick(e);
        var button = new Note(e.Location)
        {
            Name = $"button{  _buttonIndex}",
        };
        Controls.Add(button);
        Text = $"{  _buttonCount} Active Notes";
    }
    protected override void OnControlRemoved(ControlEventArgs e)
    {
        base.OnControlRemoved(e);
        if(e.Control is Note)
        {
            Text = $"{--_buttonCount} Active Notes";
        }
    }
    private int
        _buttonCount = 0,
        _buttonIndex = 0;
}
  • Related