从线程更新表单时表单中的空白组件

Blank components in a Form while updating it from a thread

我制作了一个在屏幕上向左移动的表单,但是表单上的所有组件都是空白的。

我把动作的代码放在评论里了,一切正常,所以问题出在我的动作代码上,但我不知道是什么问题。

System.Threading.Thread thread;

private void Form1_Load(object sender, EventArgs e)
{
    thread = new System.Threading.Thread(loop);
    thread.Start();
}

void loop()
{
    this.BeginInvoke((Action)delegate () {
        int x = 0;
        int y = 0;
        int MoveRate = 1;
        Point TopLeft = this.Location;
        Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
        while (true)
        {
            x = x + MoveRate;
            this.Location = new Point(x, 150);
            System.Threading.Thread.Sleep(10);
        }
    });     
}

这应该会使表单向左移动,但是表单上的组件是空白的。

让我们看看loop()方法:

void loop()
{
    this.BeginInvoke((Action)delegate () {
        int x = 0;
        int y = 0;
        int MoveRate = 1;
        Point TopLeft = this.Location;
        Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
        while (true)
        {
            x = x + MoveRate;
            this.Location = new Point(x, 150);
            System.Threading.Thread.Sleep(10);
        }
    });     
}

此代码立即在主 UI 线程上调用委托。委托运行一个永不退出的 while(true) 循环。循环一开始执行,UI 线程就被完全控制,没有希望响应其他事件消息,包括绘制事件。

试试这个:

void loop()
{
    int x = 0;
    int MoveRate = 1;

    while(true)
    {
        x += MoveRate;
        this.BeginInvoke((Action)delegate () { this.Location = new Point(x, 150); });
        System.Threading.Thread.Sleep(16);
    } 
}

它们都是相同的代码(除了没有做任何事情的东西),但现在经过安排以便在循环内调用委托。 UI 线程只被短暂阻塞,然后控制 returns 回到循环线程,循环线程会 Sleep 一会儿,然后再打扰 UI。调用非常简单,我可以将其重写为单行。

请注意,我还增加了睡眠时间,因为这样仍然可以让您每秒播放 60 帧。

同一过程的 Async 变体,以测试不同的东西。
(你的线程没有按预期工作的主要原因已经解释过了。如果你使用一个线程然后在闭环中调用 UI 线程,它或多或少就像没有你的代码 运行 完全在不同的线程中:表单没有时间更新自身或其控件)。

当窗体滚动到当前屏幕边界之外时,此方法将终止滚动过程。满足此条件时,退出 while 循环,Task 结束,将 Form 移动到屏幕中央。

任务在 Shown 事件中启动。我认为它比 Load 事件更合适(表格已准备好在此处呈现)。

请注意,此任务或线程均未对 Form.FormClosing 事件添加任何检查,以取消异步过程:如果在执行滚动时关闭窗体,则很可能会出现异常(表格已被处理,因此不再处理)。

private async Task Scroller(int ScreenWidth)
{
    int x = 0;
    int MoveRate = 2;

    while (true)
    {
        x += MoveRate;
        this.BeginInvoke(new MethodInvoker(() => { this.Location = new Point(x, 150);}));
        await Task.Delay(10);
        if (x > ScreenWidth) break;
    };
}

private async void Form_Shown(object sender, EventArgs e)
{
    int ScreenWidth = Screen.FromHandle(this.Handle).Bounds.Width;
    await this.Scroller(ScreenWidth);
    this.Location = new Point((ScreenWidth - this.Width) / 2 , 150);
}