Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a wpf countdown timer?

I want to create wpf countdown timer that display the result as hh:mm:ss into textbox, I would be thankful for anyone help.

like image 876
user2419978 Avatar asked May 25 '13 09:05

user2419978


People also ask

How do I make a countdown timer in C#?

Count Down Timer With the Timer Class in C# The Timer class) is used to execute a function inside a separate thread in C#. We can use the Timer function to create a count-down timer in C#. The Timer. Interval property sets the interval between each tick of the timer in milliseconds.


2 Answers

You can use DispatcherTimer class (msdn).

Duration of time you can hold in TimeSpan structure (msdn).

If you want formatting TimeSpan to hh:mm:ss you should invoke ToString method with "c" argument (msdn).

Example:

XAML:

<Window x:Class="CountdownTimer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Name="tbTime" />
    </Grid>
</Window>

Code-behind:

using System;
using System.Windows;
using System.Windows.Threading;

namespace CountdownTimer
{
    public partial class MainWindow : Window
    {
        DispatcherTimer _timer;
        TimeSpan _time;

        public MainWindow()
        {
            InitializeComponent();

            _time = TimeSpan.FromSeconds(10);

            _timer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
                {
                    tbTime.Text = _time.ToString("c");
                    if (_time == TimeSpan.Zero) _timer.Stop();
                    _time = _time.Add(TimeSpan.FromSeconds(-1));                    
                }, Application.Current.Dispatcher);

            _timer.Start();            
        }
    }
}
like image 66
kmatyaszek Avatar answered Oct 17 '22 06:10

kmatyaszek


There is nothing wrong with using DispatcherTimer for this purpose. However, IMHO the newer TPL-based async/await paradigm makes for code that is easier to write and read. It is also better to always use good MVVM practices for WPF programs, rather than to set UI element values directly from code-behind.

Here's an example of a program that implements a countdown-timer as described in the question, but using these more modern practices…

The view model is of course where the bulk of the interesting code resides, and even there the main thing is the single method _StartCountdown(), which implements the actual countdown:

ViewModel.cs:

class ViewModel
{
    private async void _StartCountdown()
    {
        Running = true;

        // NOTE: UTC times used internally to ensure proper operation
        // across Daylight Saving Time changes. An IValueConverter can
        // be used to present the user a local time.

        // NOTE: RemainingTime is the raw data. It may be desirable to
        // use an IValueConverter to always round up to the nearest integer
        // value for whatever is the least-significant component displayed
        // (e.g. minutes, seconds, milliseconds), so that the displayed
        // value doesn't reach the zero value until the timer has completed.

        DateTime startTime = DateTime.UtcNow, endTime = startTime + Duration;
        TimeSpan remainingTime, interval = TimeSpan.FromMilliseconds(100);

        StartTime = startTime;
        remainingTime = endTime - startTime;

        while (remainingTime > TimeSpan.Zero)
        {
            RemainingTime = remainingTime;
            if (RemainingTime < interval)
            {
                interval = RemainingTime;
            }

            // NOTE: arbitrary update rate of 100 ms (initialized above). This
            // should be a value at least somewhat less than the minimum precision
            // displayed (e.g. here it's 1/10th the displayed precision of one
            // second), to avoid potentially distracting/annoying "stutters" in
            // the countdown.

            await Task.Delay(interval);
            remainingTime = endTime - DateTime.UtcNow;
        }

        RemainingTime = TimeSpan.Zero;
        StartTime = null;
        Running = false;
    }

    private TimeSpan _duration;
    public TimeSpan Duration
    {
        get { return _duration; }
        set { _UpdateField(ref _duration, value); }
    }

    private DateTime? _startTime;
    public DateTime? StartTime
    {
        get { return _startTime; }
        private set { _UpdateField(ref _startTime, value); }
    }

    private TimeSpan _remainingTime;
    public TimeSpan RemainingTime
    {
        get { return _remainingTime; }
        private set { _UpdateField(ref _remainingTime, value); }
    }

    private bool _running;
    public bool Running
    {
        get { return _running; }
        private set { _UpdateField(ref _running, value, _OnRunningChanged); }
    }

    private void _OnRunningChanged(bool obj)
    {
        _startCountdownCommand.RaiseCanExecuteChanged();
    }

    private readonly DelegateCommand _startCountdownCommand;
    public ICommand StartCountdownCommand { get { return _startCountdownCommand; } }

    public ViewModel()
    {
        _startCountdownCommand = new DelegateCommand(_StartCountdown, () => !Running);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void _UpdateField<T>(ref T field, T newValue,
        Action<T> onChangedCallback = null,
        [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        T oldValue = field;

        field = newValue;
        onChangedCallback?.Invoke(oldValue);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

As noted in the comments, the above will work as-is, but if you want specific output, it's useful to have IValueConverter implementations to adjust the output to suit user-specific needs. Here are some examples of those:

UtcToLocalConverter.cs:

class UtcToLocalConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;

        if (value is DateTime)
        {
            DateTime dateTime = (DateTime)value;

            return dateTime.ToLocalTime();
        }

        return Binding.DoNothing;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;

        if (value is DateTime)
        {
            DateTime dateTime = (DateTime)value;

            return dateTime.ToUniversalTime();
        }

        return Binding.DoNothing;
    }
}

TimeSpanRoundUpConverter.cs:

class TimeSpanRoundUpConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is TimeSpan && parameter is TimeSpan))
        {
            return Binding.DoNothing;
        }

        return RoundUpTimeSpan((TimeSpan)value, (TimeSpan)parameter);
    }

    private static TimeSpan RoundUpTimeSpan(TimeSpan value, TimeSpan roundTo)
    {
        if (value < TimeSpan.Zero) return RoundUpTimeSpan(-value, roundTo);

        double quantization = roundTo.TotalMilliseconds, input = value.TotalMilliseconds;
        double normalized = input / quantization;
        int wholeMultiple = (int)normalized;
        double fraction = normalized - wholeMultiple;

        return TimeSpan.FromMilliseconds((fraction == 0 ? wholeMultiple : wholeMultiple + 1) * quantization);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

And of course, some XAML to define the UI (where none of the UI elements have names, nor does the code-behind need to access any of them explicitly):

MainWindow.xaml:

<Window x:Class="TestSO16748371CountdownTimer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:TestSO16748371CountdownTimer"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:ViewModel/>
  </Window.DataContext>

  <Window.Resources>
    <l:UtcToLocalConverter x:Key="utcToLocalConverter1"/>
    <l:TimeSpanRoundUpConverter x:Key="timeSpanRoundUpConverter1"/>
    <s:TimeSpan x:Key="timeSpanRoundTo1">00:00:01</s:TimeSpan>
  </Window.Resources>

  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition/>
    </Grid.RowDefinitions>

    <TextBlock Text="Duration: "/>
    <TextBox Text="{Binding Duration}" Grid.Column="1"/>

    <TextBlock Text="Start time:" Grid.Row="1"/>
    <TextBlock Text="{Binding StartTime, Converter={StaticResource utcToLocalConverter1}}" Grid.Row="1" Grid.Column="1"/>

    <TextBlock Text="Remaining time:" Grid.Row="2"/>
    <TextBlock Text="{Binding RemainingTime,
      StringFormat=hh\\:mm\\:ss,
      Converter={StaticResource timeSpanRoundUpConverter1},
      ConverterParameter={StaticResource timeSpanRoundTo1}}" Grid.Row="2" Grid.Column="1"/>

    <Button Content="Start Countdown" Command="{Binding StartCountdownCommand}" Grid.Row="3" VerticalAlignment="Top"/>

  </Grid>
</Window>
like image 32
Peter Duniho Avatar answered Oct 17 '22 06:10

Peter Duniho