3

I am working on a WinForm project where I have a label in a for loop. I want to show the label each time after executing the label.text statement. But it doesn't show for every time, rather it shows after for loop is finished.

I tried to achieve this by using Thread.Sleep(). But I can't. Please help me. NOTE :- lblProgress is a Label

Here's my coding.

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);
}
Icemanind
  • 47,519
  • 50
  • 171
  • 296

4 Answers4

5

Whenever you create a WinForm application, it is spun up into a new process and a new thread is created. Any updates to the User Interface are all done on the same thread as your process. This means when your application is doing "busy work", your UI will be blocked because they are on the same thread. What this means is that, in order to achieve what it is you're trying to achieve, you have to do a little extra work.

First step we need to do is create a function for your work routine (we could use an anonymous function, but since you are new to C#, I think it'll be easier to understand if we break it out), like this:

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)
    }
}

Next, we need to create a new thread that executes our DoWork() function. Its unclear what the "trigger" is for doing your work, but I'm going to assume its a button click:

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

So now, whenever someone click the button, we will start a new thread that executes our DoWork function in that thread. The new thread spawns, then execution is immediate returned and our GUI will now update in real time as our thread is executing in the background.

But wait! We still have one more problem to take care of. The problem is that Window's form controls are not thread safe and if we try to update a control from another thread, other then the GUI's thread, we will get a cross-thread operation error. The key to fixing this is to use InvokeRequired and Invoke.

First, we need to make another function that does just the label update:

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

In your form class, we also need to create a new delegate:

public partial class Form1 : Form
{
    private delegate void ProgressCallback(int progress);

    // ..
    // The rest of your code
    // ..
}

Finally, change your DoWork() method to something like this:

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)
    }
}

This uses the label's (derived from Control) InvokeRequired property to determine if an Invoke is required. It returns true or false. If its false, we can just call our SetProgressLabel() function like we'd normally do. If its true, we must use Invoke to call our function instead.

Congratulations! You just made your first thread safe application.

Now, just as an aside note, you are not properly releasing and disposing of your objects. I recommend you change your DoWork() code to something like this:

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)
            }
        }
    }
}

Because I wrapped your IDisposable's into using blocks, the resources will automatically be disposed of once it goes out of scope.

Icemanind
  • 47,519
  • 50
  • 171
  • 296
2

Although threading would be the more ideal solution another solution is:

Application.DoEvents()

this will give the UI thread time to update.

Example

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();
}
Ian Jowett
  • 189
  • 18
  • Although this sometimes works, `DoEvents` is asynchronous which means it terminates before the application has actually processed any outstanding events, so if you're using it in a procedure with many sequential statements, calling `DoEvents` causes a huge disturbance whenever it's called. Basically, if you find yourself needing to call `DoEvents` anywhere, think about starting another thread instead, or using asynchronous delegates. – Icemanind Dec 17 '14 at 16:57
  • I accept its not an elegant approach and threading is the more idea l, however for the scope of the question was trying to offer a simple solution. The author of the question is new to coding and the "DoEvents()" is maybe a beginners approach, and hopefully they will refactor at a later date to include a worker thread. – Ian Jowett Dec 18 '14 at 11:50
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();

                }
            });
Deepak Mishra
  • 2,984
  • 1
  • 26
  • 32
  • This is still going to lock up the UI for 1000 ms intervals because you do a `Thread.Sleep` on the UI tread. – Scott Chamberlain Dec 17 '14 at 14:20
  • But why put it in the UI thread, put it before the `task` then just pass the value of `i` at the end (btw, you might have issues with variable captures, you may want to declare a new variable inside the for loop and use that inside the anonymous delegate.) – Scott Chamberlain Dec 17 '14 at 15:56
  • actually looking at it again it won't work, you will have the wrong value of `i` for `sourceTable.Rows[i].Field(0);` They are all likely to point at the last or near to last row due to `i` having the wrong value, you need to do a `var j = i;` inside the for loop and use `j` inside the anonomous delegate. – Scott Chamberlain Dec 17 '14 at 16:04
  • You are still missing the issue to fix the [variable capture](http://stackoverflow.com/questions/271440/captured-variable-in-a-loop-in-c-sharp). (The `var j = i;` then using `lblProgress.Text = "Hello World" + j;`) – Scott Chamberlain Dec 17 '14 at 16:47
  • I understood your point but it's working in VS 2013 without assigning to another variable. – Deepak Mishra Dec 17 '14 at 17:31
0

If you are executing the mentioned code on the UI thread, UI will be refreshed only after entire for loop is executed. Based on your needs, progress bar/background worker kind of set up looks suitable.

danish
  • 5,550
  • 2
  • 25
  • 28