All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

510 lines
15 KiB

/************************************************************************
Extended WPF Toolkit
Copyright (C) 2010-2012 Xceed Software Inc.
This program is provided to you under the terms of the Microsoft Public
License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
This program can be provided to you by Xceed Software Inc. under a
proprietary commercial license agreement for use in non-Open Source
projects. The commercial version of Extended WPF Toolkit also includes
priority technical support, commercial updates, and many additional
useful WPF controls if you license Xceed Business Suite for WPF.
Visit http://xceed.com and follow @datagrid on Twitter.
**********************************************************************/
using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Xceed.Wpf.Toolkit
{
[TemplatePart( Name = PART_TimeListItems, Type = typeof( ListBox ) )]
public class TimePicker : Control
{
private const string PART_TimeListItems = "PART_TimeListItems";
#region Members
ListBox _timeListBox;
private DateTimeFormatInfo DateTimeFormatInfo
{
get;
set;
}
internal static readonly TimeSpan EndTimeDefaultValue = new TimeSpan( 23, 59, 0 );
internal static readonly TimeSpan StartTimeDefaultValue = new TimeSpan( 0, 0, 0 );
internal static readonly TimeSpan TimeIntervalDefaultValue = new TimeSpan( 1, 0, 0 );
#endregion //Members
#region Properties
#region AllowSpin
public static readonly DependencyProperty AllowSpinProperty = DependencyProperty.Register( "AllowSpin", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( true ) );
public bool AllowSpin
{
get
{
return ( bool )GetValue( AllowSpinProperty );
}
set
{
SetValue( AllowSpinProperty, value );
}
}
#endregion //AllowSpin
#region EndTime
public static readonly DependencyProperty EndTimeProperty = DependencyProperty.Register( "EndTime", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( EndTimeDefaultValue, new PropertyChangedCallback( OnEndTimeChanged ), new CoerceValueCallback( OnCoerceEndTime ) ) );
private static object OnCoerceEndTime( DependencyObject o, object value )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
return timePicker.OnCoerceEndTime( ( TimeSpan )value );
else
return value;
}
private static void OnEndTimeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnEndTimeChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
}
protected virtual TimeSpan OnCoerceEndTime( TimeSpan value )
{
// TODO: Keep the proposed value within the desired range.
return value;
}
protected virtual void OnEndTimeChanged( TimeSpan oldValue, TimeSpan newValue )
{
if( _timeListBox != null )
_timeListBox.ItemsSource = GenerateTimeListItemsSource();
}
public TimeSpan EndTime
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return ( TimeSpan )GetValue( EndTimeProperty );
}
set
{
SetValue( EndTimeProperty, value );
}
}
#endregion //EndTime
#region Format
public static readonly DependencyProperty FormatProperty = DependencyProperty.Register( "Format", typeof( TimeFormat ), typeof( TimePicker ), new UIPropertyMetadata( TimeFormat.ShortTime, OnFormatChanged ) );
public TimeFormat Format
{
get
{
return ( TimeFormat )GetValue( FormatProperty );
}
set
{
SetValue( FormatProperty, value );
}
}
private static void OnFormatChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnFormatChanged( ( TimeFormat )e.OldValue, ( TimeFormat )e.NewValue );
}
protected virtual void OnFormatChanged( TimeFormat oldValue, TimeFormat newValue )
{
}
#endregion //Format
#region FormatString
public static readonly DependencyProperty FormatStringProperty = DependencyProperty.Register( "FormatString", typeof( string ), typeof( TimePicker ), new UIPropertyMetadata( default( String ), OnFormatStringChanged ) );
public string FormatString
{
get
{
return ( string )GetValue( FormatStringProperty );
}
set
{
SetValue( FormatStringProperty, value );
}
}
private static void OnFormatStringChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnFormatStringChanged( ( string )e.OldValue, ( string )e.NewValue );
}
protected virtual void OnFormatStringChanged( string oldValue, string newValue )
{
if( string.IsNullOrEmpty( newValue ) )
throw new ArgumentException( "CustomFormat should be specified.", FormatString );
}
#endregion //FormatString
#region IsOpen
public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register( "IsOpen", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( false ) );
public bool IsOpen
{
get
{
return ( bool )GetValue( IsOpenProperty );
}
set
{
SetValue( IsOpenProperty, value );
}
}
#endregion //IsOpen
#region ShowButtonSpinner
public static readonly DependencyProperty ShowButtonSpinnerProperty = DependencyProperty.Register( "ShowButtonSpinner", typeof( bool ), typeof( TimePicker ), new UIPropertyMetadata( true ) );
public bool ShowButtonSpinner
{
get
{
return ( bool )GetValue( ShowButtonSpinnerProperty );
}
set
{
SetValue( ShowButtonSpinnerProperty, value );
}
}
#endregion //ShowButtonSpinner
#region StartTime
public static readonly DependencyProperty StartTimeProperty = DependencyProperty.Register( "StartTime", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( StartTimeDefaultValue, new PropertyChangedCallback( OnStartTimeChanged ), new CoerceValueCallback( OnCoerceStartTime ) ) );
private static object OnCoerceStartTime( DependencyObject o, object value )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
return timePicker.OnCoerceStartTime( ( TimeSpan )value );
else
return value;
}
private static void OnStartTimeChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnStartTimeChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
}
protected virtual TimeSpan OnCoerceStartTime( TimeSpan value )
{
// TODO: Keep the proposed value within the desired range.
return value;
}
protected virtual void OnStartTimeChanged( TimeSpan oldValue, TimeSpan newValue )
{
if( _timeListBox != null )
_timeListBox.ItemsSource = GenerateTimeListItemsSource();
}
public TimeSpan StartTime
{
// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
get
{
return ( TimeSpan )GetValue( StartTimeProperty );
}
set
{
SetValue( StartTimeProperty, value );
}
}
#endregion //StartTime
#region TimeInterval
public static readonly DependencyProperty TimeIntervalProperty = DependencyProperty.Register( "TimeInterval", typeof( TimeSpan ), typeof( TimePicker ), new UIPropertyMetadata( TimeIntervalDefaultValue, OnTimeIntervalChanged ) );
public TimeSpan TimeInterval
{
get
{
return ( TimeSpan )GetValue( TimeIntervalProperty );
}
set
{
SetValue( TimeIntervalProperty, value );
}
}
private static void OnTimeIntervalChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnTimeIntervalChanged( ( TimeSpan )e.OldValue, ( TimeSpan )e.NewValue );
}
protected virtual void OnTimeIntervalChanged( TimeSpan oldValue, TimeSpan newValue )
{
if( _timeListBox != null )
_timeListBox.ItemsSource = GenerateTimeListItemsSource();
}
#endregion //TimeInterval
#region Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof( DateTime? ), typeof( TimePicker ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged ) );
public DateTime? Value
{
get
{
return ( DateTime? )GetValue( ValueProperty );
}
set
{
SetValue( ValueProperty, value );
}
}
private static void OnValueChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimePicker timePicker = o as TimePicker;
if( timePicker != null )
timePicker.OnValueChanged( ( DateTime? )e.OldValue, ( DateTime? )e.NewValue );
}
protected virtual void OnValueChanged( DateTime? oldValue, DateTime? newValue )
{
//TODO: refactor this
if( newValue.HasValue && _timeListBox != null )
{
var items = _timeListBox.ItemsSource;
foreach( TimeItem item in items )
{
if( item.Time == newValue.Value.TimeOfDay )
{
int index = _timeListBox.Items.IndexOf( item );
if( _timeListBox.SelectedIndex != index )
_timeListBox.SelectedIndex = index;
break;
}
}
}
RoutedPropertyChangedEventArgs<object> args = new RoutedPropertyChangedEventArgs<object>( oldValue, newValue );
args.RoutedEvent = ValueChangedEvent;
RaiseEvent( args );
}
#endregion //Value
#region Watermark
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register( "Watermark", typeof( object ), typeof( TimePicker ), new UIPropertyMetadata( null ) );
public object Watermark
{
get
{
return ( object )GetValue( WatermarkProperty );
}
set
{
SetValue( WatermarkProperty, value );
}
}
#endregion //Watermark
#region WatermarkTemplate
public static readonly DependencyProperty WatermarkTemplateProperty = DependencyProperty.Register( "WatermarkTemplate", typeof( DataTemplate ), typeof( TimePicker ), new UIPropertyMetadata( null ) );
public DataTemplate WatermarkTemplate
{
get
{
return ( DataTemplate )GetValue( WatermarkTemplateProperty );
}
set
{
SetValue( WatermarkTemplateProperty, value );
}
}
#endregion //WatermarkTemplate
#endregion //Properties
#region Constructors
static TimePicker()
{
DefaultStyleKeyProperty.OverrideMetadata( typeof( TimePicker ), new FrameworkPropertyMetadata( typeof( TimePicker ) ) );
}
public TimePicker()
{
DateTimeFormatInfo = DateTimeFormatInfo.GetInstance( CultureInfo.CurrentCulture );
Keyboard.AddKeyDownHandler( this, OnKeyDown );
Mouse.AddPreviewMouseDownOutsideCapturedElementHandler( this, OnMouseDownOutsideCapturedElement );
}
#endregion //Constructors
#region Base Class Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if( _timeListBox != null )
_timeListBox.SelectionChanged -= TimeListBox_SelectionChanged;
_timeListBox = GetTemplateChild( PART_TimeListItems ) as ListBox;
if( _timeListBox != null )
{
_timeListBox.SelectionChanged += TimeListBox_SelectionChanged;
_timeListBox.ItemsSource = GenerateTimeListItemsSource();
}
}
#endregion //Base Class Overrides
#region Event Handlers
private void OnKeyDown( object sender, KeyEventArgs e )
{
switch( e.Key )
{
case Key.Escape:
case Key.Tab:
{
CloseTimePicker();
break;
}
}
}
private void OnMouseDownOutsideCapturedElement( object sender, MouseButtonEventArgs e )
{
CloseTimePicker();
}
void TimeListBox_SelectionChanged( object sender, SelectionChangedEventArgs e )
{
if( e.AddedItems.Count > 0 )
{
TimeItem selectedTimeListItem = ( TimeItem )e.AddedItems[ 0 ];
var time = selectedTimeListItem.Time;
;
var date = Value ?? DateTime.MinValue;
Value = new DateTime( date.Year, date.Month, date.Day, time.Hours, time.Minutes, time.Seconds, time.Milliseconds );
}
CloseTimePicker();
}
#endregion //Event Handlers
#region Events
//Due to a bug in Visual Studio, you cannot create event handlers for nullable args in XAML, so I have to use object instead.
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent( "ValueChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventHandler<object> ), typeof( TimePicker ) );
public event RoutedPropertyChangedEventHandler<object> ValueChanged
{
add
{
AddHandler( ValueChangedEvent, value );
}
remove
{
RemoveHandler( ValueChangedEvent, value );
}
}
#endregion //Events
#region Methods
private void CloseTimePicker()
{
if( IsOpen )
IsOpen = false;
ReleaseMouseCapture();
}
public IEnumerable GenerateTimeListItemsSource()
{
TimeSpan time = StartTime;
TimeSpan endTime = EndTime;
if( endTime <= time )
{
endTime = EndTimeDefaultValue;
time = StartTimeDefaultValue;
}
TimeSpan timeInterval = TimeInterval;
if( time != null && endTime != null && timeInterval != null && timeInterval.Ticks > 0 )
{
while( time <= endTime )
{
yield return new TimeItem( DateTime.MinValue.Add( time ).ToString( GetTimeFormat(), CultureInfo.CurrentCulture ), time );
time = time.Add( timeInterval );
}
yield break;
}
}
private string GetTimeFormat()
{
switch( Format )
{
case TimeFormat.Custom:
return FormatString;
case TimeFormat.LongTime:
return DateTimeFormatInfo.LongTimePattern;
case TimeFormat.ShortTime:
return DateTimeFormatInfo.ShortTimePattern;
default:
return DateTimeFormatInfo.ShortTimePattern;
}
}
#endregion //Methods
}
}