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

时间:2014-12-17 06:46:34

标签: c# winforms

我正在开发一个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<string>(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);
}

4 个答案:

答案 0 :(得分:5)

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

我们需要做的第一步是为您的工作例程创建一个函数(我们可以使用匿名函数,但由于您是C#的新手,我认为如果我们将其解析,它会更容易理解) ,像这样:

private void DoWork()
{
    for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
    {

        string checkout;
        checkout= sourceTable.Rows[i].Field<string>(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()函数的新线程。目前还不清楚&#34;触发&#34;是为了你的工作,但我会假设点击一下按钮:

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

所以现在,只要有人点击按钮,我们就会启动一个新线程,在该线程中执行我们的DoWork函数。新线程产生,然后立即返回执行,我们的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<string>(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<string>(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块中,因此一旦资源超出范围,资源将自动处理。

答案 1 :(得分:1)

var ui = TaskScheduler.FromCurrentSynchronizationContext();

            Task.Factory.StartNew(() =>
            {
                for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
                {
                        string checkout;
                        checkout = sourceTable.Rows[i].Field<string>(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();

                }
            });

答案 2 :(得分:1)

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

Application.DoEvents()

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

实施例

for (int i = 1; i <= sourceTable.Rows.Count - 1; i++)
{

string checkout;
checkout= sourceTable.Rows[i].Field<string>(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();
}

答案 3 :(得分:0)

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