2013-05-25 155 views

答えて

21

DispatcherTimerクラス(msdn)を使用できます。

TimeSpanの構造(msdn)で保持できる時間。

TimeSpanhh:mm:ssをフォーマットする場合は、 "c"引数(msdn)を使用してToStringメソッドを呼び出す必要があります。

例:

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> 

コードビハインド:

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();    
     } 
    } 
} 
3

この目的のためにDispatcherTimerを使うことには何も問題はありません。しかし、IMHOの最新のTPLベースのasync/awaitパラダイムは、読み書きが容易なコードを作成します。また、コードビハインドから直接UI要素の値を設定するのではなく、WPFプログラムに常に良いMVVMの方法を使用する方が良いです。

質問に記載されているようなカウントダウンタイマーを実装していますが、これらのより現代的なプラクティス&hellipを使用したプログラムの例です。

興味深いコードの大部分が存在する場所ビューモデルはもちろんであり、さらにはそこに主なものは、実際のカウントダウン実装単方法_StartCountdown()ある:

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)); 
    } 
} 

コメントに記載されているとおり、上記はそのまま動作しますが、特定の出力が必要な場合は、IValueConverterの実装でユーザー固有のニーズに合わせて出力を調整すると便利です。

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(); 
    } 
} 

そしてもちろん、いくつかのXAMLは、UIを定義するところなし(ここではそれらのいくつかの例がありますコードエレメントは明示的にそれらのいずれかにアクセスする必要はありません)。

メインウィンドウ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> 
関連する問題