views:

1758

answers:

2

I wanted to make a simple Countdown-Application with C# to show as an example.

For the very first and basic version I use a Label to display the current time left in seconds and a Button to start the countdown. The Button's Click-Event is implemented like this:

private void ButtonStart_Click(object sender, RoutedEventArgs e)
    {
        _time = 60;
        while (_time > 0)
        {
            _time--;
            this.labelTime.Content = _time + "s";
            System.Threading.Thread.Sleep(1000);
        }
    }

Now when the user clicks the Button the time is actually counted down (as the application freezes (due to Sleep())) for the chosen amount of time but the Label's context is not refreshed.

Am I doing something generally wrong (when it comes to Threads) or is it just a problem with the UI?


Thank you for your answers! I now use a System.Windows.Threading.DispatcherTimer to do as you told me. Everything works fine so this question is officially answered ;)

For those who are interested: Here is my code (the essential parts)

public partial class WindowCountdown : Window
{
    private int _time;
    private DispatcherTimer _countdownTimer;

    public WindowCountdown()
    {
        InitializeComponent();
        _countdownTimer = new DispatcherTimer();
        _countdownTimer.Interval = new TimeSpan(0,0,1);
        _countdownTimer.Tick += new EventHandler(CountdownTimerStep);

    }

    private void ButtonStart_Click(object sender, RoutedEventArgs e)
    {
        _time = 10;
        _countdownTimer.Start();

    }

    private void CountdownTimerStep(object sender, EventArgs e)
    {
        if (_time > 0)
        {
            _time--;
            this.labelTime.Content = _time + "s";
        }
        else
            _countdownTimer.Stop();
    }
}
+9  A: 

Yes, event handlers should not block - they should return immediately. You should implement this by a Timer, BackgroundWorker or Thread (in this order of preference).

ripper234
+7  A: 

What you are seeing is the effect of a long-running message blocking the windows message queue/pump - which you more commonly associate with the white application screen and "not responding". Basically, if your thread is sleeping, it isn't responding to messages like "paint yourself". You need to make your change and yield control to the pump.

There are various ways of doing this (ripper234 does a good job of listing them). The bad way you'll often see is:

{ // your count/sleep loop

    // bad code - don't do this:
    Application.DoEvents();
    System.Threading.Thread.Sleep(1000);
}

I mention this only to highlight what not to do; this causes a lot of problems with "re-entrancy" and general code management. A better way is simply to use a Timer, or for more complex code, a BackgroundWorker. Something like:

using System;
using System.Windows.Forms;
class MyForm : Form {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.Run(new MyForm());
    }

    Timer timer;
    MyForm() {
        timer = new Timer();
        count = 10;
        timer.Interval = 1000;
        timer.Tick += timer_Tick;
        timer.Start();
    }
    protected override void Dispose(bool disposing) {
        if (disposing) {
            timer.Dispose();
        }
        base.Dispose(disposing);
    }
    int count;
    void timer_Tick(object sender, EventArgs e) {
        Text = "Wait for " + count + " seconds...";
        count--;
        if (count == 0)
        {
            timer.Stop();
        }
    }
}
Marc Gravell