1

我目前正在编写一个 WPF 应用程序,我想在其中包含一个倒数计时器。这是我的倒计时课:

 internal class CountDown : INotifyPropertyChanged
{
    private readonly DispatcherTimer _timer;
    private string _currentTimeString;
    private TimeSpan _runTime;
    private TimeSpan _timeleft;

    public CountDown(TimeSpan runTime)
    {
        if (runTime == null) throw new ArgumentNullException("runTime");
        _runTime = runTime;

        _timer = new DispatcherTimer();
        _timer.Interval = new TimeSpan(0, 0, 0, 0, 10);

        _timer.Tick += Update;
    }

    public CountDown(TimeSpan runTime, TimeSpan interval)
    {
        if (runTime == null) throw new ArgumentNullException("runTime");
        _runTime = runTime;

        _timer = new DispatcherTimer();

        if (interval == null) throw new ArgumentNullException("interval");
        _timer.Interval = interval;

        _timer.Tick += Update;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public string CurrentTimeString
    {
        get { return _currentTimeString; }
        private set
        {
            _currentTimeString = value;
            NotifyPropertyChanged();
        }
    }

    public void Start()
    {
        var task = new Task(_timer.Start);
        _timeleft = _runTime;
        task.Start();
    }

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    private void Update(object sender, EventArgs e)
    {
        _timeleft -= _timer.Interval;

        DateTime newTime = new DateTime();
        newTime = DateTime.MinValue;
        newTime += _timeleft;

        CurrentTimeString = newTime.ToString("mm:ss:ff");
    }
}

组合根:

public MainWindow()
    {
        CountDown countDown = new CountDown(new TimeSpan(0, 1, 0));

        InitializeComponent();
        tb1.DataContext = countDown; //tb1 = TextBlock
        countDown.Start();
    }

一切正常,除非我将间隔设置为 10 毫秒,然后它比实际秒慢。我怎样才能解决这个问题?

编辑:我还不能回答我自己的问题,所以就这样吧:我完全重写了我的课程而不使用任何计时器。发现这些对我来说不够准确。

public class CountDown : INotifyPropertyChanged
{
    private string _currentTimeString;
    private TimeSpan _runTime;
    private bool _shouldStop;
    private DateTime _timeToStop;
    private TimeSpan _updateInterval;

    public CountDown(TimeSpan runTime)
    {
        if (runTime == null) throw new ArgumentNullException("runTime");
        _runTime = runTime;

        _updateInterval = new TimeSpan(0, 0, 0, 0, 10);

        Tick += Update;
    }

    public CountDown(TimeSpan runTime, TimeSpan updateInterval)
    {
        if (runTime == null) throw new ArgumentNullException("runTime");
        _runTime = runTime;

        if (updateInterval == null) throw new ArgumentNullException("updateInterval");
        _updateInterval = updateInterval;

        Tick += Update;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public event Action Tick;

    public string CurrentTimeString
    {
        get { return _currentTimeString; }
        set
        {
            _currentTimeString = value;
            NotifyPropertyChanged();
        }
    }
    public void Start()
    {
        _shouldStop = false;
        _timeToStop = DateTime.Now + _runTime;
        var task = new Task(GenerateTicks);
        task.Start();
    }

    public void Stop()
    {
        _shouldStop = true;
    }

    private void GenerateTicks()
    {
        while (_shouldStop == false)
        {
            if (Tick != null)
                Tick();

            Thread.Sleep(_updateInterval);
        }
    }

    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    private void Update()
    {
        var timeLeft = _timeToStop - DateTime.Now;

        if (timeLeft <= TimeSpan.Zero)
        {
            _shouldStop = true;
            return;
        }

        var timeLeftDate = DateTime.MinValue + timeLeft;

        CurrentTimeString = timeLeftDate.ToString("mm:ss:ff");
    }
}
4

2 回答 2

1

首先,您不需要任务来完成倒计时。如果您使用每 50 毫秒计时一次的计时器,您将不会阻止任何内容。比 50 毫秒更快的滴答声没有意义,因为我猜你的倒计时显示的是小时、分钟或秒。毫秒对于计时器来说有点太多了,不是吗?即使您想显示毫秒范围,人眼也不会注意到倒计时是每 10 毫秒还是 50 毫秒更新一次。

接下来,如果您使用 DateTime 作为时基,它可能会更容易处理。它使计算实际剩余时间变得更容易。

using System;
using System.Timers;

public class Countdown
{
    private readonly TimeSpan countdownTime;
    private readonly Timer timer;
    private DateTime startTime;

    public Countdown(TimeSpan countdownTime)
    {
        this.countdownTime = countdownTime;
        this.timer = new Timer(10);
    }

    public string RemainingTime { get; private set; }

    public void Start()
    {
        this.startTime = DateTime.Now;
        this.timer.Start();
    }

    private void Timer_Tick(object state)
    {
        var now = DateTime.Now;
        var difference = now - this.startTime;
        var remaining = this.countdownTime - difference;
        if (remaining < TimeSpan.Zero)
        {
            this.timer.Stop();
            // Raise Event or something
        }

        this.RemainingTime = remaining.ToString("mm:ss:fff");
    }
}

对于这种情况,异步倒计时会有点过分。但是,如果您需要它,它很容易升级。

于 2014-07-27T23:08:47.750 回答
0

您的 Countdown 类在构造函数中有 2 个参数。在这种情况下,不会调用您的单参数构造函数(您使用 10 毫秒的构造函数)。您正在为第二个参数中的间隔提供 1s (new TimeSpan(0,0,1)),这就是您在运行它时在 UI 中看到的内容。

于 2014-07-27T22:19:38.603 回答