C# 强制从 UI 线程更新 GUI

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1360944/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:38:07  来源:igfitidea点击:

Force GUI update from UI Thread

c#winformsmultithreading

提问by dbkk

In WinForms, how do I force an immediate UI update from UI thread?

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

What I'm doing is roughly:

我正在做的大致是:

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

Label text does not get set to "Please Wait..." before the operation.

操作前标签文本未设置为“请稍候...”。

I solved this using another thread for the operation, but it gets hairy and I'd like to simplify the code.

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

采纳答案by Jagd

At first I wondered why the OP hadn't already marked one of the responses as the answer, but after trying it myself and still have it not work, I dug a little deeper and found there's much more to this issue then I'd first supposed.

起初我想知道为什么 OP 还没有将其中一个响应标记为答案,但是在自己尝试之后仍然无法正常工作,我挖得更深一些,发现这个问题还有很多,然后我首先应该。

A better understanding can be gained by reading from a similar question: Why won't control update/refresh mid-process

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

Lastly, for the record, I was able to get my label to update by doing the following:

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

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

Though from what I understand this is far from an elegant and correct approach to doing it. It's a hack that may or may not work depending upon how busy the thread is.

尽管据我所知,这远不是一种优雅而正确的方法。这是一个黑客,可能会或可能不会起作用,具体取决于线程的繁忙程度。

回答by Scoregraphic

Call Application.DoEvents()after setting the label, but you should do all the work in a separate thread instead, so the user may close the window.

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

回答by Dror Helper

Call label.Invalidateand then label.Update()- usually the update only happens after you exit the current function but calling Update forces it to update at that specific place in code. From MSDN:

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

The Invalidate method governs what gets painted or repainted. The Update method governs when the painting or repainting occurs. If you use the Invalidate and Update methods together rather than calling Refresh, what gets repainted depends on which overload of Invalidate you use. The Update method just forces the control to be painted immediately, but the Invalidate method governs what gets painted when you call the Update method.

Invalidate 方法控制要绘制或重新绘制的内容。Update 方法控制绘制或重新绘制的时间。如果您同时使用 Invalidate 和 Update 方法而不是调用 Refresh,则重绘的内容取决于您使用的 Invalidate 重载。Update 方法只是强制立即绘制控件,但 Invalidate 方法控制调用 Update 方法时绘制的内容。

回答by Mike Hall

It's very tempting to want to "fix" this and force a UI update, but the best fix is to do this on a background thread and not tie up the UI thread, so that it can still respond to events.

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

回答by Rick2047

you can try this

你可以试试这个

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

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

See if it works.

看看它是否有效。

回答by pixelgrease

After updating the UI, start a task to perform with the long running operation:

更新 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());

This works in .NET 3.5 and later.

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

回答by Tobias Knauss

I've just stumbled over the same problem and found some interesting information and I wanted to put in my two cents and add it here.

我刚刚偶然发现了同样的问题并发现了一些有趣的信息,我想投入我的两分钱并将其添加到这里。

First of all, as others have already mentioned, long-running operations should be done by a thread, which can be a background worker, an explicit thread, a thread from the threadpool or (since .Net 4.0) a task: Stackoverflow 570537: update-label-while-processing-in-windows-forms, so that the UI keeps responsive.

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

But for short tasks there is no real need for threading although it doesn't hurt of course.

但是对于短期任务,虽然它当然不会受到伤害,但实际上并不需要线程。

I have created a winform with one button and one label to analyze this problem:

我创建了一个带有一个按钮和一个标签的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
}

My analysis was stepping over the code (using F10) and seeing what happened. And after reading this article Multithreading in WinFormsI have found something interesting. The article says at the bottom of the first page, that the UI thread can not repaint the UI until the currently executed function finishes and the window is marked by Windows as "not responding" instead after a while. I have also noticed that on my test application from above while stepping through it, but only in certain cases.

我的分析是跳过代码(使用 F10)并查看发生了什么。在阅读了这篇文章WinForms 中的多线程后,我发现了一些有趣的东西。文章在第一页的底部说,直到当前执行的函数完成并且窗口被Windows标记为“未响应”之后,UI线程才能重新绘制UI。我还注意到在我的测试应用程序中逐步执行它时,但仅在某些情况下。

(For the following test it is important to not have Visual Studio set to fullscreen, you must be able to see your little application window at the same time next to it, You must not have to switch between the Visual Studio window for debugging and your application window to see what happens. Start the application, set a breakpoint at label1->Text ..., put the application window beside the VS window and place the mouse cursor over the VS window.)

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

  1. When I click once on VS after app start (to put the focues there and enable stepping) and step through it WITHOUT moving the mouse, the new text is set and the label is updated in the update() function.This means, the UI is repainted obviously.

  2. When I step over the first line, then move the mouse around a lot and click somewhere, then step further, the new text is likely set and the update() function is called, but the UI is not updated/repainted and the old text remains there until the button1_click() function finishes. Instead of repainting, the window is marked as "not responsive"! It also doesn't help to add this->Update();to update the whole form.

  3. Adding Application::DoEvents();gives the UI a chance to update/repaint. Anyway you have to take care that the user can not press buttons or perform other operations on the UI that are not permitted!! Therefore: Try to avoid DoEvents()!, better use threading (which I think is quite simple in .Net).
    But (@Jagd, Apr 2 '10 at 19:25) you can omit .refresh()and .invalidate().

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

  2. 当我跨过第一行时,然后将鼠标移动很多并单击某处,然后再进一步,可能设置了新文本并调用了 update() 函数,但 UI 未更新/重绘,旧文本保持在那里,直到 button1_click() 函数完成。窗口没有重新绘制,而是被标记为“无响应”!添加this->Update();更新整个表单也无济于事。

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

My explanations is as following: AFAIK winform still uses the WINAPI function. Also MSDN article about System.Windows.Forms Control.Update methodrefers to WINAPI function WM_PAINT. The MSDN article about WM_PAINTstates in its first sentence that the WM_PAINT command is only sent by the system when the message queue is empty. But as the message queue is already filled in the 2nd case, it is not send and thus the label and the application form are not repainted.

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

<>joke> Conclusion: so you just have to keep the user from using the mouse ;-) <>/joke>

<>joke> 结论:所以你只需要阻止用户使用鼠标 ;-) <>/joke>

回答by 56ka

I had the same problem with property Enabledand I discovered a first chance exceptionraised because of it is not thread-safe. I found solution about "How to update the GUI from another thread in C#?" here https://stackoverflow.com/a/661706/1529139And it works !

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

回答by Graham Bennett

Think I have the answer, distilled from the above and a little experimentation.

我想我有答案,从上面提炼出来的和一些实验。

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

I tried decrementing the value and the screen updated even in debug mode, but that would not work for setting progressBar.Valueto progressBar.Maximum, because you cannot set the progress bar value above the maximum, so I first set the progressBar.Valueto progressBar.Maximum -1, then set progressBar.Maxiumumto equal progressBar.Value. They say there is more than one way of killing a cat. Sometimes I'd like to kill Bill Gates or whoever it is now :o).

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

With this result, I did not even appear to need to Invalidate(), Refresh(), Update(), or do anything to the progress bar or its Panel container or the parent Form.

有了这个结果,我甚至没有出现需要Invalidate()Refresh()Update(),或做任何事情的进度条或它的面板容器或父窗体。