Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't System.Timers.Timer update non-busy UI?

I have been looking for a reason, but about every single topic on stackoverflow I could find suggested a busy UI as the reason. I have very basic code, just for testings and it simply won't update the UI, even though I am sure it cannot be busy doing something else:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    static double t = 0;
    static double dt = 0.1;

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Timer timer = new Timer(5000);
        timer.Enabled = true;
        timer.Elapsed += new ElapsedEventHandler(Update);
        timer.Start();
    }

    void Update(object sender, ElapsedEventArgs e)
    {
        t = t + dt;
        testBlock1.Text = (t).ToString();
    }
}

I debugged it and set a breakpoint to the textBlock1.Text update and it did break every 5 seconds, but the UI is never updated. As can be seen in the code, when I move my mouse over the Button I use to start the timer, the button shows its typical "mouse-over-button" animation. Why does that happen, but not the text update?

If anyone can lead me to a good topic on stackoverflow, please do so and I will delete my question here, but I couldn't find any topic explaining why this approach doesn't update the UI even though the UI is not busy doing anyting else.

like image 699
philkark Avatar asked Nov 30 '22 05:11

philkark


2 Answers

System.Timers.Timer won't by default marshal back to the UI thread. In Windows Forms you can make it do so easily enough:

Timer timer = new Timer(5000);
timer.SynchronizingObject = this;

... but WPF UI elements don't implement ISynchronizeInvoke, which would stop this from working for you in WPF.

You could use the Dispatcher to marshal over to the UI thread within the handler, or you could just use a DispatcherTimer to start with.

like image 73
Jon Skeet Avatar answered Dec 04 '22 04:12

Jon Skeet


For WPF, you should use DispatcherTimer - Also with the above code you posted, i am getting a cross thread error since elapsed event is raised on other thread and not on UI thread.

private void button1_Click(object sender, RoutedEventArgs e)
{
   DispatcherTimer timer = new DispatcherTimer();
   timer.Interval = new TimeSpan(0, 0, 5);
   timer.Tick += new EventHandler(timer_Tick);
   timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
   t = t + dt;
   txt.Text = (t + dt).ToString();
}

EDIT

Also, you can marshal it on UI Dispatcher with your existing code like this -

void Update(object sender, ElapsedEventArgs e)
{
    App.Current.Dispatcher.Invoke((Action)delegate()
    {
        t = t + dt;
        txt.Text = (t + dt).ToString();
    });
}
like image 45
Rohit Vats Avatar answered Dec 04 '22 02:12

Rohit Vats