Winforms元件移动滞后

pwuypxnk  于 2023-01-02  发布在  其他
关注(0)|答案(1)|浏览(254)

我目前正在我的课程项目,我需要做一个节奏游戏。
我面临的问题是在笔记移动。目前笔记是由5px高的按钮表示,在40像素一个滴答声向下移动。我有一个计时器,移动笔记列表中的每个按钮每一个滴答声,并删除他们,如果他们到达边界。问题变得明显时,有超过2个移动笔记在同一时间,时间之间的每一个滴答声变得更长的笔记在屏幕上。
下面是音符移动的代码:

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);
                }
            }
        }

这样的事情可以通过打开一些功能,如双缓冲修复,还是我需要完全重做它在一个不同的方式?
其中一个要求是,它必须在WinForms中,而不是任何先进的引擎,如统一。

3xiyfsfu

3xiyfsfu1#

在你的代码中,有一个工作单元,我们称之为“移动音符”,你在设定的时间间隔内做这件事。棘手的是,我们真的不知道移动音符需要多长时间。显然,移动10,000个音符比移动10个音符要花更长的时间。如果(假设)需要一整秒才能将它们全部移动,而您尝试每秒移动10次,则会出现备份,执行速度会越来越慢。
你可以尝试一种不同类型的计时器循环,基本上是:
1.移动所有的音符,不管要花多长时间。
1.等待某个时间间隔到期。
1.重复
以下是MainForm类中的外观:

public partial class MainForm : Form
{
    public MainForm() => InitializeComponent();
    protected override void OnLoad(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;
    .
    .
    .
}

您当前的代码正在迭代一个Notes列表,并指示它们逐个移动。您是否考虑过使Notes足够智能,可以自动移动?您可以这样做:创建一个Note类,该类可以响应主窗体触发的计时器事件。

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);
            }               
        }));
    }
}

为了确保运动不会陷入困境,我做了一个简单的测试,每次单击鼠标都会添加一个Note

public partial class MainForm : Form
{
    .
    .
    .
    protected override void OnMouseClick(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;
}

相关问题