从UI线程强制GUI更新

时间:2009-09-01 07:00:57

标签: c# winforms multithreading

在WinForms中,如何从UI线程强制立即更新UI?

我正在做的大致是:

label.Text = "Please Wait..."
try 
{
    SomewhatLongRunningOperation(); 
}
catch(Exception e)
{
    label.Text = "Error: " + e.Message;
    return;
}
label.Text = "Success!";

在操作之前,标签文本未设置为“Please Wait ...”。

我使用另一个线程来解决这个问题,但它变得毛茸茸,我想简化代码。

11 个答案:

答案 0 :(得分:91)

起初我想知道为什么OP还没有将其中一个回复标记为答案,但是在自己尝试并且仍然无法工作之后,我挖了一点,发现这个问题还有更多问题然后我首先应该假装。

通过阅读类似问题可以获得更好的理解:Why won't control update/refresh mid-process

最后,为了记录,我可以通过执行以下操作让我的标签更新:

private void SetStatus(string status) 
{
    lblStatus.Text = status;
    lblStatus.Invalidate();
    lblStatus.Update();
    lblStatus.Refresh();
    Application.DoEvents();
}

虽然从我的理解来看,这远非一种优雅和正确的做法。根据线程的繁忙程度,它可能会或可能不会起作用。

答案 1 :(得分:14)

调用label.Invalidate然后调用label.Update() - 通常更新只在您退出当前函数但调用Update强制它在代码中的特定位置更新后才会发生。 来自MSDN

  

Invalidate方法控制绘制或重新绘制的内容。 Update方法控制何时进行绘制或重新绘制。如果您同时使用Invalidate和Update方法而不是调用Refresh,那么重新绘制的内容取决于您使用的Invalidate的重载次数。 Update方法只是强制立即绘制控件,但Invalidate方法控制调用Update方法时绘制的内容。

答案 2 :(得分:13)

设置标签后调用Application.DoEvents(),但您应该在单独的线程中完成所有工作,这样用户可以关闭窗口。

答案 3 :(得分:4)

我刚刚遇到同样的问题,发现了一些有趣的信息,我想把我的两分钱加在这里。

首先,正如其他人已经提到的,长时间运行的操作应该由一个线程完成,该线程可以是后台工作者,显式线程,来自线程池的线程或(自.Net 4.0)任务: Stackoverflow 570537: update-label-while-processing-in-windows-forms,以便UI保持响应。

但是对于简短的任务来说,没有真正需要线程,尽管它当然不会受到伤害。

我创建了一个带有一个按钮和一个标签的winform来分析这个问题:

System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
{
  label1->Text = "Start 1";
  label1->Update();
  System::Threading::Thread::Sleep(5000); // do other work
}

我的分析是踩过代码(使用F10)并看看发生了什么。阅读完本文Multithreading in WinForms后,我发现了一些有趣的东西。文章在第一页的底部说,在当前执行的函数完成之前,UI线程无法重新绘制UI,并且窗口在一段时间后被Windows标记为“无响应”。我也注意到在我的测试应用程序中从上面踩过它,但仅在某些情况下。

(对于以下测试,重要的是不要将Visual Studio设置为全屏,您必须能够在其旁边的同一时间看到您的小应用程序窗口,您不必在Visual Studio窗口之间切换调试和你的应用程序窗口看看会发生什么。启动应用程序,在label1->Text ...设置一个断点,将应用程序窗口放在VS窗口旁边,然后将鼠标光标放在VS窗口上。)

  1. 当我在应用程序启动后在VS上单击一次(将focues放在那里并启用步进)并且单步执行它而不移动鼠标时,将设置新文本并在更新中更新标签()函数。这意味着,UI显然会被重新绘制。

  2. 当我跨过第一行,然后移动鼠标并点击某处,然后再往前走,可能会设置新文本并调用update()函数,但UI不会更新/重新绘制,旧文本保持不变,直到button1_click()函数完成。窗口被标记为“无响应”,而不是重新绘制!添加this->Update();以更新整个表单也没有帮助。

  3. 添加Application::DoEvents();可让UI有机会更新/重绘。无论如何,你必须注意用户不能按下按钮或在UI上执行不允许的其他操作!因此:Try to avoid DoEvents()!,更好地使用线程(我认为在.Net中非常简单) 但是( @Jagd,2010年4月2日19:25 )你可以省略.refresh().invalidate()

  4. 我的解释如下:AFAIK winform仍然使用WINAPI功能。另外MSDN article about System.Windows.Forms Control.Update method指的是WINAPI函数WM_PAINT。 MSDN article about WM_PAINT在其第一句中指出WM_PAINT命令仅在消息队列为空时由系统发送。但由于消息队列已在第二种情况下填写,因此不会发送,因此标签和申请表不会重新绘制。

    <>笑话>结论:所以你只需要让用户不要使用鼠标;-)<> / joke>

答案 4 :(得分:3)

你可以试试这个

using System.Windows.Forms; // u need this to include.

MethodInvoker updateIt = delegate
                {
                    this.label1.Text = "Started...";
                };
this.label1.BeginInvoke(updateIt);

看看它是否有效。

答案 5 :(得分:2)

更新UI后,启动要执行长时间运行的任务:

label.Text = "Please Wait...";

Task<string> task = Task<string>.Factory.StartNew(() =>
{
    try
    {
        SomewhatLongRunningOperation();
        return "Success!";
    }
    catch (Exception e)
    {
        return "Error: " + e.Message;
    }
});
Task UITask = task.ContinueWith((ret) =>
{
    label.Text = ret.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());

这适用于.NET 3.5及更高版本。

答案 6 :(得分:1)

想要“修复”并强制进行UI更新是非常诱人的,但最好的解决方法是在后台线程上执行此操作而不是绑定UI线程,以便它仍然可以响应事件。

答案 7 :(得分:1)

答案 8 :(得分:1)

想想我有答案,从上面提炼出来并进行一些实验。

progressBar.Value = progressBar.Maximum - 1;
progressBar.Maximum = progressBar.Value;

即使在调试模式下,我尝试递减值并更新屏幕,但这不能将progressBar.Value设置为progressBar.Maximum,因为您无法将进度条值设置为高于最大值,所以我首先将progressBar.Value设置为progressBar.Maximum - 1,然后将progressBar.Maxiumum设置为等于progressBar.Valu e。他们说杀猫的方法不止一种。有时我想杀死比尔盖茨或现在的任何人:o)。

通过此结果,我甚至不需要Invalidate()Refresh()Update(),或对进度条或其Panel容器或父窗体执行任何操作。< / p>

答案 9 :(得分:1)

如果只需要更新几个控件,那么.update()就足够了。

btnMyButton.BackColor=Color.Green; // it eventually turned green, after a delay
btnMyButton.Update(); // after I added this, it turned green quickly

答案 10 :(得分:0)

我在属性Enabled遇到了同样的问题,我发现first chance exception因为不是线程安全而引发了{{1}}。 我找到了关于“如何从C#中的另一个线程更新GUI?”的解决方案。在这里https://stackoverflow.com/a/661706/1529139它有效!