如何在for循环中每次更新标签

我正在开发一个WinForm项目,我在for循环中有一个标签。 我想在执行label.text语句后每次都显示标签。 但它并不是每次都显示,而是在for循环结束后显示。

我试图通过使用Thread.Sleep()来实现这一点。 但我不能。 请帮我。 注意: – lblProgress是一个标签

这是我的编码。

 for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout= sourceTable.Rows[i].Field(0); dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString); dest.Open(); destcmd = new SqlCommand(checkout, dest); destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); lblProgress.Text = "Hello World"+i; Thread.Sleep(10000); } 

无论何时创建WinForm应用程序,它都会转换为新进程并创建新线程。 用户界面的任何更新都在与您的进程相同的线程上完成。 这意味着当您的应用程序正在“忙碌工作”时,您的UI将被阻止,因为它们位于同一个线程上。 这意味着,为了实现你想要实现的目标,你必须做一些额外的工作。

我们需要做的第一步是为你的工作例程创建一个函数(我们可以使用一个匿名函数,但是因为你是C#的新手,我认为如果我们分解它会更容易理解),如下所示:

 private void DoWork() { for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout= sourceTable.Rows[i].Field(0); dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString); dest.Open(); destcmd = new SqlCommand(checkout, dest); destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); lblProgress.Text = "Hello World"+i; Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second) } } 

接下来,我们需要创建一个执行DoWork()函数的新线程。 它不清楚你的工作的“触发器”是什么,但我将假设它点击一下按钮:

 private void button1_click(object sender, EventArgs e) { var work = new Thread(DoWork); work.Start(); } 

所以现在,只要有人点击按钮,我们就会启动一个新线程,在该线程中执行我们的DoWorkfunction。 新线程产生,然后立即返回执行,我们的GUI现在将在我们的线程在后台执行时实时更新。

可是等等! 我们还有一个问题要处理。 问题是Window的表单控件不是线程安全的,如果我们尝试从另一个线程更新控件,而不是GUI的线程,我们将得到一个跨线程操作错误。 解决这个问题的关键是使用InvokeRequiredInvoke

首先,我们需要创建另一个只执行标签更新的函数:

 private void SetProgressLabel(int progress) { lblProgress.Text = "Hello World" + progress; } 

在您的表单类中,我们还需要创建一个新的委托:

 public partial class Form1 : Form { private delegate void ProgressCallback(int progress); // .. // The rest of your code // .. } 

最后,将您的DoWork()方法更改为以下内容:

 private void DoWork() { for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout= sourceTable.Rows[i].Field(0); dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString); dest.Open(); destcmd = new SqlCommand(checkout, dest); destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); if (lblProgress.InvokeRequired) { lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i }); } else { SetProgressLabel(i); } Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second) } } 

这使用标签(派生自ControlInvokeRequired属性来确定是否需要Invoke 。 它返回truefalse 。 如果它是false ,我们就像我们通常那样调用我们的SetProgressLabel()函数。 如果是true ,我们必须使用Invoke来调用我们的函数。

恭喜! 您刚刚制作了第一个线程安全的应用程序

现在,作为旁注,您没有正确释放和处理您的对象。 我建议您将DoWork()代码更改为以下内容:

 private void DoWork() { for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout = sourceTable.Rows[i].Field(0); using (dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString)) { dest.Open(); using (destcmd = new SqlCommand(checkout, dest)) { destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); if (lblProgress.InvokeRequired) { lblProgress.Invoke(new ProgressCallback(SetProgressLabel), new object[] { i }); } else { SetProgressLabel(i); } Thread.Sleep(1000); // I changed this from 10000 to 1000 (10 seconds down to 1 second) } } } } 

因为我将IDisposable包装成using块,所以一旦资源超出范围,资源将自动处理掉。

 var ui = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => { for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout = sourceTable.Rows[i].Field(0); dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString); dest.Open(); destcmd = new SqlCommand(checkout, dest); destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); var task = Task.Factory.StartNew(() => { //Thread.Sleep(1000); lblProgress.Text = "Hello World" + i; }, CancellationToken.None, TaskCreationOptions.None, ui); task.Wait(); } }); 

虽然线程化将是更理想的解决方案,但另一种解决方案是:

Application.DoEvents()

这将为UI线程提供更新时间。

 for (int i = 1; i <= sourceTable.Rows.Count - 1; i++) { string checkout; checkout= sourceTable.Rows[i].Field(0); dest = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["local"].ConnectionString); dest.Open(); destcmd = new SqlCommand(checkout, dest); destcmd.ExecuteNonQuery(); dest.Close(); prcmail(); prcmessagecheck(); lblProgress.Text = "Hello World"+i; Application.DoEvents(); } 

如果您在UI线程上执行上述代码,则只有在执行完整的for循环后才会刷新UI。 根据您的需要,进度条/后台工作者类型设置看起来合适。