tags:

views:

127

answers:

5
private void button1_Click(object sender, RoutedEventArgs e)
{
        int i = 0;

        while (i < 500)
        {
            label1.Content = i.ToString();
        //  System.Threading.Thread.Sleep(2000);
            ++i;
        }
 }

I am trying to update the content of a Label each time a variable is incremented, but what happens is label1's Content is changed only once and only after the while loop terminates. I thought that the incrementation of the counter variable was so fast that the UI thread could not catch up with it, so I wanted to make the thread idle for 2 seconds, hoping to see label1 change value 500 times. It did not work either. Why?

A: 

The handler is hogging the event loop.

One option is to use Application.DoEvents method to allow the painting operations to execute. Note that this is considered bad practice, as is the Thread.Sleep which will make the UI unresponsive.

private void button1_Click(object sender, RoutedEventArgs e)
{
        int i = 0;

        while (i < 500)
        {
            label1.Content = i.ToString();
            System.Threading.Thread.Sleep(2000);
            Application.DoEvents();
            ++i;
        }
 }

Another option is to use Control.BeginInvoke for each label1.Content = .. operation.

Ani
`BeingInvoke` will not work. The UI thread is still blocked.
Nick Whaley
I should have made it clearer: execute the loop on another thread, making UI updates with Control.BeginInvoke.
Ani
A: 

Update your method like this:

private void button1_Click(object sender, RoutedEventArgs e)
{
        int i = 0;

        while (i < 500)
        {
            label1.Content = i.ToString();
            //Update the UI
            Application.DoEvents();
        //  System.Threading.Thread.Sleep(2000);
            ++i;
        }
 }

This is the easiest method to do this. But not a preferred one. The preferred one will be using the backgroundworker for such need.

Kangkan
A: 

"If we handled the entire search inside of the click event handler of the button, we would never give the UI thread a chance to handle other events. The UI would be unable to respond to input or process messages. It would never repaint and never respond to button clicks." - Source.

Anyway, what you'll want to do is create a worker thread that performs your for-loop and just update the UI using Dispatcher.BeginInvoke.

karmicpuppet
+5  A: 

As others have said, the problem is that you're blocking the UI thread.

Rather than use the suggestions with Application.DoEvents, I would suggest that you use a DispatcherTimer to schedule the update. Sample code:

DispatcherTimer timer = new DispatcherTimer();
timer.Tick += delegate
{
    label.Content = counter.ToString();
    counter++;
    if (counter == 500)
    {
        timer.Stop();
    }
};
timer.Interval = TimeSpan.FromMilliseconds(2000);
timer.Start();

(I'm not sure whether Application.DoEvents even works in WPF, and it's generally regarded as bad practice anyway. If it does work, I doubt that it's guaranteed to work in future versions. Mixing the two UIs in the same application just for the sake of this sounds like a really bad idea to me.)

You could use a Timer (either System.Threading.Timer or System.Timers.Timer) but in both cases you'd then need to marshal back to the dispatcher thread anyway. DispatcherTimer makes this simpler - the Tick event is always fired within the Dispatcher thread anyway.

EDIT: If you really want an equivalent of DoEvents, here's code which I've verified does work, and is based on the MSDN docs for Dispatcher.PushFrame explaining how to do the equivalent of Application.DoEvents but from WPF:

public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrames), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrames(object f)
{
    ((DispatcherFrame)f).Continue = false;

    return null;
}

private void ButtonClick(object sender, RoutedEventArgs e)
{
    for (int i = 0; i < 500; i++)
    {
        label.Content = i.ToString();
        DoEvents();
    }
}

I still wouldn't recommend this though - WPF (like Windows Forms) is designed on an event-driven way of working.

Jon Skeet
@drorhan: Sure, due to the timer. That's easily adjusted. I picked the timing due to what was in the question, admittedly commented out. Adjusting it to (say) a 20ms interval will make it count pretty quickly.
Jon Skeet
@drorhan: (Now you've edited the comment) This takes about 500 x 2s (1000 seconds, just under 17 minutes) to finish. That mirrors what the OP said about delaying by 2 seconds.
Jon Skeet
@drorhan: Why wouldn't it be? It's 2000ms (2 seconds) and it stops after 500 iterations. I wouldn't be surprised to see it be a little bit longer in reality, but it should be *roughly* right.
Jon Skeet
A: 

This is what do you search

  System.Windows.Forms.Application.DoEvents();

and add System.Windows.Forms to project reference

drorhan