6

I am working on a very simple stopwatch using WPF but using the System.Diagnostics stopwatch was very slow and not reliable at all, compared to my system clock every 1 second from my application was 3 seconds on an actual clock.

I did some search about stopwatch being slow, found lots of results but no solution to it, so I decided to come up with my own counter.

Here is an sample of what I came up with:

System.Windows.Threading.DispatcherTimer _update;
DateTime _started;
bool isRunning = false;

The update thread:

_update = new System.Windows.Threading.DispatcherTimer(new TimeSpan(0, 0, 0, 0, 1), System.Windows.Threading.DispatcherPriority.Normal, delegate
{
    if (isRunning)
        iTimer.Content = new DateTime((DateTime.Now - _started).Ticks).ToString("HH:mm:ss");
}, this.Dispatcher);

I have 2 buttons, bToggle which is resposible for starting, stopping and resuming it and another button called bReset.

private void bReset_Click(object sender, RoutedEventArgs e)
{
    isRunning = false;
    iTimer.Content = "00:00:00";
    bToggle.Content = "Start";
}

private void bToggle_Click(object sender, RoutedEventArgs e)
{
    if ((string)bToggle.Content == "Start")
    {
        isRunning = true;
        _started = DateTime.Now;
        bToggle.Content = "Stop";
    }
    else if ((string)bToggle.Content == "Resume")
    {
        isRunning = true;
        bToggle.Content = "Stop";
    }
    else
    {
        isRunning = false;
        bToggle.Content = "Resume";
    }
}

It works fine to start and reset but since I am using the actual time if I stop and resume, it will jump the seconds until the actual time.

How could I solve this problem or is there an alternative to stopwatch that actually have a good accuracy on the current time ?

Guapo
  • 3,446
  • 9
  • 36
  • 63
  • What were you using `StopWatch` for? – Oded May 04 '12 at 10:48
  • For a stopwatcher ? To put it simple it counts from 0 until u stop or reset. – Guapo May 04 '12 at 10:49
  • 1
    It just sounds like you were trying to use it as a timer. – Oded May 04 '12 at 10:50
  • Something like this http://www.sport-impianti.com/wp-content/gallery/atletica/4103.jpg but as a desktop application. Either way if u look at the above code, its a fully working on, I just need to figure out how to do the stop/resume function or if there is a better way to control it. – Guapo May 04 '12 at 10:50
  • `StopWatch` has issues on some hardware(for example constant offsets between cores), but I never heard of a multiplicative error. Can you post your `StopWatch` code? – CodesInChaos May 04 '12 at 10:51
  • Now you are being silly. The `StopWatch` class is intended to count time differences. Timers are intended to execute pieces of code on a schedule. It is not clear what you are trying to do. – Oded May 04 '12 at 10:52
  • CodeInChaos I would but I already got ride of it and moved to the above one after several tries and no useful results from searchs. – Guapo May 04 '12 at 10:53
  • Oded LOL man, I am doing a http://en.wikipedia.org/wiki/Stopwatch this my sound confusing as I was trying to use the StopWatch class to make a StopWatch, but thats what I am doing a Stopwatch for desktop that can be stopped and resumed. – Guapo May 04 '12 at 10:55
  • Again, it sounds like you need a timer. Timers can be stopped and resumed. – Oded May 04 '12 at 11:03
  • I wrote a stopwatch/clock/countdown timer recently in WPF to be used at the back of a church to help the speaker know how long they've been preaching. You're welcome to the code if you want it. – Ian May 04 '12 at 11:04
  • @Oded that's why I was looking for an Alternative or solution to my current code but the Timers class hangs my UI. – Guapo May 04 '12 at 11:08
  • @Guapo - There are _several_ different timer classes. You need to use the right one and your UI will not hang. – Oded May 04 '12 at 11:10
  • @Guapo: To choose the right timer, you could maybe take a look into [this article](http://msdn.microsoft.com/en-us/magazine/cc164015.aspx). – Oliver May 04 '12 at 11:16

3 Answers3

2

You need to intruduce an additional variable TimeSpan accumulatedTime in which you save the elapsed interval whenever someone clicks stop.

And:

iTimer.Content = (new DateTime((DateTime.Now - _started).Ticks) + accumulatedTime).ToString("HH:mm:ss");
usr
  • 168,620
  • 35
  • 240
  • 369
  • very appreciated, I have resolved it with the follow: `new DateTime((DateTime.Now.Subtract(accumulatedTime) - _tempo).Ticks).ToString("HH:mm:ss");` – Guapo May 04 '12 at 11:07
1

Use System.Timers.Timer

You can start and stop this, and set a counter in the timers tick event.

This is a simple example:

public partial class MainWindow : Window
{
    private Timer _timer;
    private int _time;

    public MainWindow()
    {
        InitializeComponent();

        _time = 0;

        _timer = new Timer(1000);
        _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
    }

    void _timer_Elapsed(object sender, ElapsedEventArgs e)
    {

        Dispatcher.Invoke(new Action(() =>
                                         {
                                             _time++;
                                             tbTime.Text = _time.ToString();
                                         }));

    }

    private void btnStartStop_Click(object sender, RoutedEventArgs e)
    {
        if (_timer.Enabled)
        {
            _timer.Stop();
        }
        else
        {
            _timer.Start();
        }
    }
}

Xaml:

<Grid>
    <StackPanel>
        <Button Name="btnStartStop" Content="start/stop timer" Click="btnStartStop_Click" />
        <TextBlock Name="tbTime" Text="00:00" />
    </StackPanel>
</Grid>
jrb
  • 1,708
  • 2
  • 13
  • 20
  • the fact I am using a thread is so it doesn't hang my application UI – Guapo May 04 '12 at 11:07
  • this dosn't lock your UI, but if you want to be safe put the timer in a backgroundworker and forward the tick event to worker progresschanged event – jrb May 04 '12 at 11:11
  • jrb if you are constantly moving the app window it does. – Guapo May 04 '12 at 11:15
0

I think this line is your problem:

_update = new System.Windows.Threading.DispatcherTimer(new TimeSpan(0, 0, 0, 0, 1), System.Windows.Threading.DispatcherPriority.Normal

It tells WPF that you want your delegate to execute 1000 times per second, and to schedule it at the second-highest priority. That's a higher priority than Render and DataBind, and I think both Render and DataBind are needed to actually display the updated value you put in iTimer.Content.

You should set the DispatcherPriority to Background and use a lower frequency (e.g. 20 ms - humans can't see anything faster than 50 fps, anyway.)

Niki
  • 15,662
  • 5
  • 48
  • 74
  • With the code in my question it updates just fine but using the stopwatch class it was not so that would cut out that line as being the problem in my opinion. – Guapo May 04 '12 at 11:25