没有及时更新GUI

我有一个更新GUI元素的类

public class UpdateLabelClass { static MainGUI theForm = (MainGUI)Application.OpenForms[0]; Label lblCurProgress = theForm.curProgress; public ProgressBarUpdate() { } public void UpdateLabel(String newLabel) { lblCurProgress.Text = newLabel; } } 

在其他类中,我创建了一个类的实例并调用UpdateLabel(someString);

现在的问题是,它跳过了更新标签的操作,所以我想“也许它甚至没有到达代码”,所以我在它之后放了一个MessageBox.Show(),它更新了标签。

有什么可能导致跳过标签更新,但是当我立即放置消息时执行它? 该计划是否会加快?

您很可能在主UI线程中不正确地运行长操作,这会阻止标签更新。 您可以通过调用DoEvents()来“修复”此问题:

 public void UpdateLabel(String newLabel) { lblCurProgress.Text = newLabel; Application.DoEvents(); } 

但这只是一个糟糕的设计之上的创可贴。 您应该正确地将该代码移动到后台线程并使用委托/ Invoke()来更新标签。

编辑:(回答后续问题)

默认情况下,您的应用程序在单个线程中运行。 这包括您为控制事件添加的代码,以及您无法看到的在幕后运行的代码,以使您的应用程序以您期望的方式响应。 诸如用户交互(鼠标点击,键盘按压等)和绘画消息(当控件被更改,窗口被遮挡)之类的东西被放入队列中。 只有在代码停止运行后才会处理队列中的那些待处理消息。 如果你有一个冗长的代码块运行,就像一个长循环,那么这些消息只是在队列中等待处理。 因此,在循环完成之后才会对标签进行更新。 DoEvents()所做的是告诉应用程序立即处理队列中的那些待处理消息,然后返回到当前正在执行的代码。 这允许标签像您期望的那样实时更新。

当遇到DoEvents()“修复”的情况时,它只是意味着您尝试在主UI线程中运行太多代码。 主UI线程应该专注于响应用户交互并保持显示更新。 控件事件处理程序中的代码应该简短而且甜蜜,以便主UI线程可以返回执行其主要工作。

正确的解决方法是将冗长的代码移动到不同的线程,从而允许主UI线程响应并保持自身更新。 对于许多场景,最简单的方法是在表单上放置BackgroundWorker()控件并连接DoWork(),ProgressChanged()和RunWorkerCompleted()事件。 *但是,您必须将WorkerReportsProgress()属性设置为true才能处理ProgressChanged()事件。 后两个事件已经封装到主UI线程中,因此您无需担心跨线程exception。 从DoWork()处理程序,您调用ReportProgress()并传递进​​度百分比值和可选的其他对象(它可以是任何东西)。 可以在ProgressChanged()事件中检索这些值,并用于更新GUI。 当DoWork()处理程序中的所有工作都已完成时,将触发RunWorkerCompleted()事件。

在你的情况下,你有一个独立的class级正在做这项工作。 您可以通过在该类中手动创建自己的线程来完成工作,从而镜像BackgroundWorker的工作。 如果要更新进度,请使您的类引发主窗体订阅的Custom Event 。 但是,当收到该事件时,它将在单独的线程的上下文中运行。 因此,有必要跨越线程边界“编组”调用,以便在更新控件之前代码在主UI线程中运行。 这是通过使用委托(方法的“指针”)和Invoke()方法来完成的。 *还有其他方法可以完成此任务,例如SynchronizationContext。

请参阅此处了解这些方法的一些示例。

最后,这是一个类的一个超级简单示例,它从一个单独的线程引发自定义事件:

 public partial class Form1 : Form { private Clock Clk; public Form1() { InitializeComponent(); Clk = new Clock(); Clk.CurrentTime += new Clock.TimeHack(Clk_CurrentTime); } private void Clk_CurrentTime(string hack) { if (label1.InvokeRequired) { Clock.TimeHack t = new Clock.TimeHack(Clk_CurrentTime); label1.Invoke(t, new object[] { hack }); } else { label1.Text = hack; } } } public class Clock { public delegate void TimeHack(string hack); public event TimeHack CurrentTime; private Thread t; private bool stopThread = false; public Clock() { t = new Thread(new ThreadStart(ThreadLoop)); t.IsBackground = true; // allow it to be shutdown automatically when the application exits t.Start(); } private void ThreadLoop() { while (!stopThread) { if (CurrentTime != null) { CurrentTime(DateTime.Now.ToString()); } System.Threading.Thread.Sleep(1000); } } public void Stop() { stopThread = true; } } 
 public void UpdateLabel(String newLabel) { lblCurProgress.Text = newLabel; lblCurProgress.Refresh(); }