在WinForms中,如何从UI线程强制立即更新UI?
我正在做的大致是:
label.Text = "Please Wait..."
try
{
SomewhatLongRunningOperation();
}
catch(Exception e)
{
label.Text = "Error: " + e.Message;
return;
}
label.Text = "Success!";
在操作之前,标签文本未设置为“Please Wait ...”。
我使用另一个线程来解决这个问题,但它变得毛茸茸,我想简化代码。
答案 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窗口上。)
当我在应用程序启动后在VS上单击一次(将focues放在那里并启用步进)并且单步执行它而不移动鼠标时,将设置新文本并在更新中更新标签()函数。这意味着,UI显然会被重新绘制。
当我跨过第一行,然后移动鼠标并点击某处,然后再往前走,可能会设置新文本并调用update()函数,但UI不会更新/重新绘制,旧文本保持不变,直到button1_click()函数完成。窗口被标记为“无响应”,而不是重新绘制!添加this->Update();
以更新整个表单也没有帮助。
添加Application::DoEvents();
可让UI有机会更新/重绘。无论如何,你必须注意用户不能按下按钮或在UI上执行不允许的其他操作!因此:Try to avoid DoEvents()!,更好地使用线程(我认为在.Net中非常简单)
但是( @Jagd,2010年4月2日19:25 )你可以省略.refresh()
和.invalidate()
。
我的解释如下: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)
尝试调用label.Invalidate()
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invalidate(VS.80).aspx
答案 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它有效!