从UI线程强制GUI更新

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

我所做的大致是:

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

在操作之前,标签文本不会设置为“请稍候…”。

我解决了这个使用另一个线程的操作,但它变得毛茸茸的,我想简化代码。

起初,我想知道为什么OP还没有把答复当作答案,但是自己试了一下,仍然没有成效,我挖得更深一点,发现这个问题还有很多,我先说应该。

通过阅读类似的问题可以获得更好的理解: 为什么不能控制更新/刷新中间过程

最后,为了logging,我能够通过执行以下操作来更新我的标签:

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

尽pipe从我的理解来看,这并不是一个优雅而正确的做法。 根据线程的繁忙情况,这可能会或可能不会工作。

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

Invalidate方法pipe理什么被绘或重绘。 更新方法决定了绘画或重新绘制的时间。 如果一起使用Invalidate和Update方法而不是调用Refresh,则重新绘制的内容取决于您使用哪个Invalidate重载。 Update方法只是强制控件被立即绘制,但Invalidate方法控制着你在调用Update方法时被绘制的东西。

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

我只是偶然发现了同样的问题,并发现了一些有趣的信息,我想把我的两分钱添加到这里。

首先,正如其他人已经提到的那样,长时间运行的操作应该由一个线程来完成,这个线程可以是一个后台工作者,一个显式线程,一个来自线程池的线程或(因为.Net 4.0)一个任务: Stackoverflow 570537:更新标签,而在Windows窗体处理 ,使UI保持响应。

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

我用一个button和一个标签创build了一个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),看看发生了什么事。 在阅读了这篇文章WinForms中的multithreading之后,我发现了一些有趣的东西。 文章说,在第一页的底部,UI线程不能重新绘制用户界面,直到当前执行的function完成,窗口被标记为“不响应”而不是一段时间后,Windows标记。 我也注意到,在我的testing应用程序从上面通过它,但只在某些情况下。

(对于下面的testing,重要的是不要将Visual Studio设置为全屏,您必须能够在它旁边同时看到您的小应用程序窗口,您不必在用于debugging的Visual Studio窗口和您的应用程序窗口来查看会发生什么,启动应用程序,在label1->Text ...处设置一个断点,将应用程序窗口放在VS窗口旁,并将鼠标光标置于VS窗口上。

  1. 当我在应用程序启动后点击一次VS(将焦点放在那里并启用步进),然后在不移动鼠标的情况下单步执行,新文本被设置并且标签在update()函数中被更新。 这意味着,UI显然是重新粉刷的。

  2. 当我跨过第一行,然后移动鼠标并点击某个地方,然后再继续,新的文本可能被设置,update()函数被调用,但UI不会更新/重新绘制,而旧文本直到button1_click()函数结束。 窗口被重新标记为“不响应”! 它也没有帮助添加this->Update(); 更新整个表单。

  3. 添加Application::DoEvents(); 为UI提供了更新/重绘的机会。 无论如何,你必须小心,用户不能按下button或执行其他操作的UI是不允许的! 因此: 尽量避免DoEvents()! ,更好地使用线程(我认为在.Net中很简单)。
    但是( @Jagd,10年4月2日在19:25 ),你可以省略.refresh().invalidate()

我的解释如下:AFAIK winform仍然使用WINAPI函数。 此外MSDN文章关于System.Windows.Forms Control.Update方法是指WINAPI函数WM_PAINT。 有关WM_PAINT的MSDN文章在其第一句中指出,WM_PAINT命令只在消息队列为空时由系统发送。 但由于在第二种情况下已经填充了消息队列,所以不发送,因此标签和申请表格不被重新绘制。

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

你可以试试这个

 using System.Windows.Forms; // u need this to include. MethodInvoker updateIt = delegate { this.label1.Text = "Started..."; }; this.label1.BeginInvoke(updateIt); 

看看它是否有效。

在更新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及更高版本中起作用。

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

想想我有答案,从上面的蒸馏和一点实验。

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

我尝试减less值和屏幕更新即使在debugging模式,但是这将无法将progressBar.Value设置为progressBar.Maximum ,因为您不能设置进度栏的值超过最大值,所以我首先设置progressBar.Value progressBar.Maximum - 1,然后设置progressBar.Maxiumum等于progressBar.Valu e。 他们说有不止一种杀死猫的方法。 有时候我想杀掉比尔·盖茨或者现在的人:o)。

有了这个结果,我似乎甚至不需要Invalidate()Refresh()Update() ,或对进度条或其面板容器或父窗体执行任何操作。

我有属性Enabled相同的问题,我发现了一个first chance exception因为它是不是线程安全的 。 我find了关于“如何从C#中的另一个线程更新GUI”的解决scheme? 这里https://stackoverflow.com/a/661706/1529139它的工作原理!;