代码之家  ›  专栏  ›  技术社区  ›  dbkk

从UI线程强制更新GUI

  •  67
  • dbkk  · 技术社区  · 15 年前

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

    我所做的大致是:

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

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

    我用另一个线程为操作解决了这个问题,但它变得复杂,我想简化代码。

    11 回复  |  直到 6 年前
        1
  •  90
  •   Community Marino Di Clemente    7 年前

    起初,我想知道为什么运营商还没有将其中一个回答标记为答案,但在我自己尝试了之后,它仍然不起作用,我更深入地挖掘了一点,发现这个问题比我想象的要多得多。

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

    最后,对于记录,我可以通过执行以下操作来更新标签:

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

    虽然据我所知,这远远不是一个优雅和正确的做法。这是一个可以工作也可以不工作的黑客,这取决于线程有多忙。

        2
  •  14
  •   Dror Helper    15 年前

    呼叫 label.Invalidate 然后 label.Update() -通常只在退出当前函数后进行更新,但调用update会强制它在代码中的特定位置进行更新。 从 MSDN :

    “无效”方法控制绘制或重新绘制的内容。更新方法控制何时进行绘制或重新绘制。如果同时使用invalidate和update方法而不是调用refresh,则重新绘制的内容取决于所使用的invalidate重载。update方法只是强制立即绘制控件,但invalidate方法控制调用update方法时绘制的内容。

        3
  •  13
  •   Scoregraphic    15 年前

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

        4
  •  4
  •   Community Marino Di Clemente    7 年前

    我刚刚偶然发现了同一个问题,发现了一些有趣的信息,我想把我的两分钱放在这里,并把它加在这里。

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

    但对于短任务来说,虽然线程不会造成伤害,但实际上并不需要线程。

    我创建了一个带有一个按钮和一个标签的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。我还注意到,在我的测试应用程序中,从上面开始,在单步执行它的同时,但仅在某些情况下。

    (对于以下测试,重要的是不要将Visual Studio设置为全屏,您必须能够在旁边同时看到您的小应用程序窗口,您不必在用于调试的Visual Studio窗口和应用程序窗口之间切换以查看发生的情况。启动应用程序,在 label1->Text ... ,将应用程序窗口放在vs窗口旁边,并将鼠标光标放在vs窗口上。)

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

    2. 当我跨过第一行,然后将鼠标移动很多并单击某个地方,然后再进一步,可能会设置新文本并调用update()函数,但用户界面不会更新/重新绘制,旧文本将保留在那里,直到button1 ou click()函数完成。窗口没有重新喷漆,而是标记为“不响应”!它也没有帮助添加 this->Update(); 更新整个表单。

    3. 正在添加 Application::DoEvents(); 为用户界面提供更新/重新绘制的机会。无论如何,您必须注意用户不能在用户界面上按按钮或执行其他不允许的操作!因此: Try to avoid DoEvents()! ,最好使用线程(我认为在.NET中很简单)。
      但是( @JAGD,4月2日10: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命令仅在消息队列为空时由系统发送。但由于第二种情况下已经填写了消息队列,因此不会发送消息队列,因此标签和申请表也不会重新绘制。

    <>笑话>结论:因此您只需防止用户使用鼠标即可;-)<gt;/jokey>

        5
  •  3
  •   Rick2047    15 年前

    你可以试试这个

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

    看看它是否有效。

        6
  •  2
  •   pixelgrease    11 年前

    更新用户界面后,启动要使用长时间运行操作执行的任务:

    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及更高版本中有效。

        7
  •  1
  •   Mike Hall    15 年前

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

        9
  •  1
  •   rmc00    8 年前

    我想我得到了答案,从上面提炼出来,并做了一些实验。

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

    我尝试减小值,即使在调试模式下屏幕也会更新,但这对设置不起作用。 progressBar.Value progressBar.Maximum ,因为您不能将进度条值设置为高于最大值,所以我首先设置 进度条值 progressBar.Maximum - 1,然后设置 progressBar.Maxiumum 平等 progressBar.Valu 他们说有不止一种方法可以杀死一只猫。有时候我想杀了比尔·盖茨或者现在的任何人:哦)。

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

        10
  •  0
  •   Community Marino Di Clemente    7 年前

    我对财产也有同样的问题 Enabled 我发现了一个 first chance exception 因为它而长大 非线程安全 . 我找到了“如何从C中的另一个线程更新GUI”的解决方案?在这里 https://stackoverflow.com/a/661706/1529139 它工作!

        11
  •  0
  •   user3029478    6 年前

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

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