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.
504 lines
15 KiB
504 lines
15 KiB
/*************************************************************************************
|
|
|
|
Extended WPF Toolkit
|
|
|
|
Copyright (C) 2007-2013 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
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at http://xceed.com/wpf_toolkit
|
|
|
|
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
|
|
|
|
***********************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Controls.Primitives;
|
|
using Xceed.Wpf.Toolkit.Core.Utilities;
|
|
using System.Collections.Generic;
|
|
using Xceed.Wpf.Toolkit.Primitives;
|
|
using System.Linq;
|
|
|
|
namespace Xceed.Wpf.Toolkit
|
|
{
|
|
[TemplatePart( Name = PART_TimeListItems, Type = typeof( ListBox ) )]
|
|
public class TimePicker : DateTimePickerBase
|
|
{
|
|
private const string PART_TimeListItems = "PART_TimeListItems";
|
|
|
|
#region Members
|
|
|
|
private ListBox _timeListBox;
|
|
private bool _isListBoxInvalid = true;
|
|
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 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 )
|
|
{
|
|
ValidateTime( value );
|
|
return value;
|
|
}
|
|
|
|
protected virtual void OnEndTimeChanged( TimeSpan oldValue, TimeSpan newValue )
|
|
{
|
|
InvalidateListBoxItems();
|
|
}
|
|
|
|
public TimeSpan EndTime
|
|
{
|
|
get
|
|
{
|
|
return ( TimeSpan )GetValue( EndTimeProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( EndTimeProperty, value );
|
|
}
|
|
}
|
|
|
|
#endregion //EndTime
|
|
|
|
#region Format
|
|
|
|
protected override void OnFormatChanged( DateTimeFormat oldValue, DateTimeFormat newValue )
|
|
{
|
|
base.OnFormatChanged( oldValue, newValue );
|
|
InvalidateListBoxItems();
|
|
}
|
|
|
|
#endregion //Format
|
|
|
|
#region MaxDropDownHeight
|
|
|
|
public static readonly DependencyProperty MaxDropDownHeightProperty = DependencyProperty.Register( "MaxDropDownHeight", typeof( double ), typeof( TimePicker ), new UIPropertyMetadata( 130d, OnMaxDropDownHeightChanged ) );
|
|
public double MaxDropDownHeight
|
|
{
|
|
get
|
|
{
|
|
return ( double )GetValue( MaxDropDownHeightProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( MaxDropDownHeightProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnMaxDropDownHeightChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
TimePicker timePicker = o as TimePicker;
|
|
if( timePicker != null )
|
|
timePicker.OnMaxDropDownHeightChanged( ( double )e.OldValue, ( double )e.NewValue );
|
|
}
|
|
|
|
protected virtual void OnMaxDropDownHeightChanged( double oldValue, double newValue )
|
|
{
|
|
// TODO: Add your property changed side-effects. Descendants can override as well.
|
|
}
|
|
|
|
#endregion
|
|
|
|
#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 )
|
|
{
|
|
ValidateTime( value );
|
|
return value;
|
|
}
|
|
|
|
protected virtual void OnStartTimeChanged( TimeSpan oldValue, TimeSpan newValue )
|
|
{
|
|
InvalidateListBoxItems();
|
|
}
|
|
|
|
public TimeSpan StartTime
|
|
{
|
|
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 object OnCoerceTimeInterval( DependencyObject o, object value )
|
|
{
|
|
TimePicker timePicker = o as TimePicker;
|
|
if( timePicker != null )
|
|
return timePicker.OnCoerceTimeInterval( ( TimeSpan )value );
|
|
else
|
|
return 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 TimeSpan OnCoerceTimeInterval( TimeSpan value )
|
|
{
|
|
ValidateTime( value );
|
|
|
|
if( value.Ticks == 0L )
|
|
throw new ArgumentException( "TimeInterval must be greater than zero" );
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
protected virtual void OnTimeIntervalChanged( TimeSpan oldValue, TimeSpan newValue )
|
|
{
|
|
InvalidateListBoxItems();
|
|
}
|
|
|
|
#endregion //TimeInterval
|
|
|
|
#endregion //Properties
|
|
|
|
#region Constructors
|
|
|
|
static TimePicker()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata( typeof( TimePicker ), new FrameworkPropertyMetadata( typeof( TimePicker ) ) );
|
|
FormatProperty.OverrideMetadata( typeof( TimePicker ), new UIPropertyMetadata( DateTimeFormat.ShortTime ) );
|
|
}
|
|
|
|
#endregion //Constructors
|
|
|
|
#region Base Class Overrides
|
|
|
|
protected override void OnFormatStringChanged( string oldValue, string newValue )
|
|
{
|
|
if( this.Format == DateTimeFormat.Custom )
|
|
{
|
|
InvalidateListBoxItems();
|
|
}
|
|
base.OnFormatStringChanged( oldValue, newValue );
|
|
}
|
|
|
|
protected override void OnMaximumChanged( DateTime? oldValue, DateTime? newValue )
|
|
{
|
|
base.OnMaximumChanged( oldValue, newValue );
|
|
this.InvalidateListBoxItems();
|
|
}
|
|
|
|
|
|
protected override void OnMinimumChanged( DateTime? oldValue, DateTime? newValue )
|
|
{
|
|
base.OnMinimumChanged( oldValue, newValue );
|
|
this.InvalidateListBoxItems();
|
|
}
|
|
|
|
protected override void OnValueChanged( DateTime? oldValue, DateTime? newValue )
|
|
{
|
|
base.OnValueChanged( oldValue, newValue );
|
|
|
|
// ListBox content may be affected if value's date changed and the date was
|
|
// or is equal to Minimum or Maximum value.
|
|
bool invalidate = false;
|
|
|
|
if( DateTimeUtilities.IsSameDate( this.Minimum, oldValue )
|
|
!= DateTimeUtilities.IsSameDate( this.Minimum, newValue ) )
|
|
{
|
|
invalidate = true;
|
|
}
|
|
|
|
if( DateTimeUtilities.IsSameDate( this.Maximum, oldValue )
|
|
!= DateTimeUtilities.IsSameDate( this.Maximum, newValue ) )
|
|
{
|
|
invalidate = true;
|
|
}
|
|
|
|
// A value change can affect the display of the listbox items
|
|
// if the Date part of the value has changed. This is the case when
|
|
// the display text of the items contains part of the Date values.
|
|
if( oldValue.GetValueOrDefault().Date != newValue.GetValueOrDefault().Date )
|
|
{
|
|
invalidate = true;
|
|
}
|
|
|
|
if( invalidate )
|
|
{
|
|
//Invalidate the entire listbox content
|
|
this.InvalidateListBoxItems();
|
|
}
|
|
else
|
|
{
|
|
//Just update the selected item
|
|
this.UpdateListBoxSelectedItem();
|
|
}
|
|
}
|
|
|
|
protected override void Popup_Opened( object sender, EventArgs e )
|
|
{
|
|
base.Popup_Opened( sender, e );
|
|
|
|
if( _timeListBox != null )
|
|
{
|
|
this.UpdateListBoxItems();
|
|
|
|
TimeSpan time = ( Value != null ) ? Value.Value.TimeOfDay : StartTimeDefaultValue;
|
|
TimeItem nearestItem = this.GetNearestTimeItem( time );
|
|
if( nearestItem != null )
|
|
{
|
|
_timeListBox.ScrollIntoView( nearestItem );
|
|
ListBoxItem listBoxItem = ( ListBoxItem )_timeListBox.ItemContainerGenerator.ContainerFromItem( nearestItem );
|
|
if( listBoxItem != null )
|
|
{
|
|
listBoxItem.Focus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnApplyTemplate()
|
|
{
|
|
base.OnApplyTemplate();
|
|
|
|
if( _timeListBox != null )
|
|
{
|
|
_timeListBox.SelectionChanged -= TimeListBox_SelectionChanged;
|
|
_timeListBox.MouseUp -= TimeListBox_MouseUp;
|
|
}
|
|
|
|
_timeListBox = GetTemplateChild( PART_TimeListItems ) as ListBox;
|
|
|
|
if( _timeListBox != null )
|
|
{
|
|
_timeListBox.SelectionChanged += TimeListBox_SelectionChanged;
|
|
_timeListBox.MouseUp += TimeListBox_MouseUp;
|
|
|
|
InvalidateListBoxItems();
|
|
}
|
|
}
|
|
|
|
#endregion //Base Class Overrides
|
|
|
|
#region Event Handlers
|
|
|
|
private 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 ?? this.ContextNow;
|
|
|
|
Value = new DateTime( date.Year, date.Month, date.Day, time.Hours, time.Minutes, time.Seconds, time.Milliseconds, date.Kind );
|
|
}
|
|
}
|
|
|
|
private void TimeListBox_MouseUp( object sender, MouseButtonEventArgs e )
|
|
{
|
|
ClosePopup( true );
|
|
}
|
|
|
|
#endregion //Event Handlers
|
|
|
|
#region Methods
|
|
|
|
private void ValidateTime( TimeSpan time )
|
|
{
|
|
if( time.TotalHours >= 24d )
|
|
throw new ArgumentException( "Time value cannot be greater than or equal to 24 hours." );
|
|
}
|
|
|
|
public IEnumerable GenerateTimeListItemsSource()
|
|
{
|
|
TimeSpan time = StartTime;
|
|
TimeSpan endTime = EndTime;
|
|
|
|
if( endTime <= time )
|
|
{
|
|
endTime = EndTimeDefaultValue;
|
|
time = StartTimeDefaultValue;
|
|
}
|
|
|
|
//Limit the content of the list to the Minimum or Maximum
|
|
//if the date is set to the minimum or maximum.
|
|
if( this.Value.HasValue )
|
|
{
|
|
DateTime date = this.Value.Value;
|
|
DateTime minDate = this.Minimum.GetValueOrDefault( DateTime.MinValue );
|
|
DateTime maxDate = this.Maximum.GetValueOrDefault( DateTime.MaxValue );
|
|
TimeSpan minTime = minDate.TimeOfDay;
|
|
TimeSpan maxTime = maxDate.TimeOfDay;
|
|
|
|
if( date.Date == minDate.Date && time.Ticks < minTime.Ticks )
|
|
{
|
|
time = minTime;
|
|
}
|
|
|
|
if( date.Date == maxDate.Date && endTime.Ticks > maxTime.Ticks )
|
|
{
|
|
endTime = maxTime;
|
|
}
|
|
|
|
if( endTime < time )
|
|
{
|
|
time = endTime;
|
|
}
|
|
}
|
|
|
|
|
|
TimeSpan timeInterval = TimeInterval;
|
|
List<TimeItem> timeItemList = new List<TimeItem>();
|
|
|
|
if( time != null && endTime != null && timeInterval != null && timeInterval.Ticks > 0 )
|
|
{
|
|
while( time <= endTime )
|
|
{
|
|
timeItemList.Add( this.CreateTimeItem( time ) );
|
|
time = time.Add( timeInterval );
|
|
}
|
|
}
|
|
return timeItemList;
|
|
}
|
|
|
|
protected virtual TimeItem CreateTimeItem( TimeSpan time )
|
|
{
|
|
var date = Value ?? this.ContextNow;
|
|
string formatString = this.GetFormatString( (DateTimeFormat)this.Format );
|
|
return new TimeItem( date.Date.Add( time ).ToString( formatString, CultureInfo ), time );
|
|
}
|
|
|
|
private void UpdateListBoxSelectedItem()
|
|
{
|
|
if( _timeListBox != null )
|
|
{
|
|
TimeItem time = null;
|
|
if( Value != null )
|
|
{
|
|
time = this.CreateTimeItem( Value.Value.TimeOfDay );
|
|
if( !_timeListBox.Items.Contains( time ) )
|
|
{
|
|
time = null;
|
|
}
|
|
}
|
|
|
|
_timeListBox.SelectedItem = time;
|
|
}
|
|
}
|
|
|
|
private void InvalidateListBoxItems()
|
|
{
|
|
_isListBoxInvalid = true;
|
|
if( IsOpen )
|
|
{
|
|
UpdateListBoxItems();
|
|
}
|
|
}
|
|
|
|
private void UpdateListBoxItems()
|
|
{
|
|
if( _timeListBox != null )
|
|
{
|
|
if( _isListBoxInvalid )
|
|
{
|
|
_timeListBox.ItemsSource = GenerateTimeListItemsSource();
|
|
UpdateListBoxSelectedItem();
|
|
_isListBoxInvalid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private TimeItem GetNearestTimeItem( TimeSpan time )
|
|
{
|
|
if( _timeListBox != null )
|
|
{
|
|
int itemCount = _timeListBox.Items.Count;
|
|
for( int i = 0; i < itemCount; i++ )
|
|
{
|
|
TimeItem timeItem = _timeListBox.Items[ i ] as TimeItem;
|
|
if( timeItem != null )
|
|
{
|
|
if( timeItem.Time >= time )
|
|
return timeItem;
|
|
}
|
|
}
|
|
|
|
//They are all less than the searched time.
|
|
//Return the last one. (Should also be the greater one.)
|
|
if( itemCount > 0 )
|
|
{
|
|
return _timeListBox.Items[ itemCount - 1 ] as TimeItem;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion //Methods
|
|
}
|
|
}
|
|
|