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.
404 lines
13 KiB
404 lines
13 KiB
/*************************************************************************************
|
|
|
|
Toolkit for WPF
|
|
|
|
Copyright (C) 2007-2025 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the XCEED SOFTWARE, INC.
|
|
COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at
|
|
https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/
|
|
|
|
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
|
|
|
|
***********************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Data;
|
|
using System.Windows.Input;
|
|
using System.Windows.Threading;
|
|
|
|
namespace Xceed.Wpf.Toolkit.Primitives
|
|
{
|
|
public abstract class DateTimeUpDownBase<T> : UpDownBase<T>
|
|
{
|
|
#region Members
|
|
|
|
internal List<DateTimeInfo> _dateTimeInfoList = new List<DateTimeInfo>();
|
|
internal DateTimeInfo _selectedDateTimeInfo;
|
|
internal bool _fireSelectionChangedEvent = true;
|
|
internal bool _processTextChanged = true;
|
|
|
|
#endregion //Members
|
|
|
|
#region Properties
|
|
|
|
#region CurrentDateTimePart
|
|
|
|
public static readonly DependencyProperty CurrentDateTimePartProperty = DependencyProperty.Register( "CurrentDateTimePart", typeof( DateTimePart )
|
|
, typeof( DateTimeUpDownBase<T> ), new UIPropertyMetadata( DateTimePart.Other, OnCurrentDateTimePartChanged, OnCurrentDateTimePartCoerce ) );
|
|
|
|
private static object OnCurrentDateTimePartCoerce( DependencyObject d, object baseValue )
|
|
{
|
|
DateTimeUpDownBase<T> dateTimeUpDownBase = d as DateTimeUpDownBase<T>;
|
|
if( dateTimeUpDownBase != null )
|
|
return dateTimeUpDownBase.OnCurrentDateTimePartCoerce( baseValue );
|
|
return baseValue;
|
|
}
|
|
|
|
protected virtual object OnCurrentDateTimePartCoerce( object baseValue )
|
|
{
|
|
return baseValue;
|
|
}
|
|
|
|
public DateTimePart CurrentDateTimePart
|
|
{
|
|
get
|
|
{
|
|
return (DateTimePart)GetValue( CurrentDateTimePartProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( CurrentDateTimePartProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnCurrentDateTimePartChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
DateTimeUpDownBase<T> dateTimeUpDownBase = o as DateTimeUpDownBase<T>;
|
|
if( dateTimeUpDownBase != null )
|
|
dateTimeUpDownBase.OnCurrentDateTimePartChanged( (DateTimePart)e.OldValue, (DateTimePart)e.NewValue );
|
|
}
|
|
|
|
protected virtual void OnCurrentDateTimePartChanged( DateTimePart oldValue, DateTimePart newValue )
|
|
{
|
|
this.Select( this.GetDateTimeInfo( newValue ) );
|
|
}
|
|
|
|
#endregion //CurrentDateTimePart
|
|
|
|
#region Step
|
|
|
|
public static readonly DependencyProperty StepProperty = DependencyProperty.Register( "Step", typeof( int )
|
|
, typeof( DateTimeUpDownBase<T> ), new UIPropertyMetadata( 1, OnStepChanged ) );
|
|
public int Step
|
|
{
|
|
get
|
|
{
|
|
return (int)GetValue( StepProperty );
|
|
}
|
|
set
|
|
{
|
|
SetValue( StepProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnStepChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
var dateTimeUpDownBase = o as DateTimeUpDownBase<T>;
|
|
if( dateTimeUpDownBase != null )
|
|
dateTimeUpDownBase.OnStepChanged( (int)e.OldValue, (int)e.NewValue );
|
|
}
|
|
|
|
protected virtual void OnStepChanged( int oldValue, int newValue )
|
|
{
|
|
}
|
|
|
|
#endregion //Step
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
internal DateTimeUpDownBase()
|
|
{
|
|
this.InitializeDateTimeInfoList( this.Value );
|
|
this.Loaded += this.DateTimeUpDownBase_Loaded;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BaseClass Overrides
|
|
|
|
public override void OnApplyTemplate()
|
|
{
|
|
if( this.TextBox != null )
|
|
{
|
|
this.TextBox.SelectionChanged -= this.TextBox_SelectionChanged;
|
|
}
|
|
|
|
base.OnApplyTemplate();
|
|
|
|
if( this.TextBox != null )
|
|
{
|
|
this.TextBox.SelectionChanged += this.TextBox_SelectionChanged;
|
|
}
|
|
}
|
|
|
|
protected override void OnPreviewKeyDown( KeyEventArgs e )
|
|
{
|
|
int selectionStart = ( _selectedDateTimeInfo != null ) ? _selectedDateTimeInfo.StartPosition : 0;
|
|
int selectionLength = ( _selectedDateTimeInfo != null ) ? _selectedDateTimeInfo.Length : 0;
|
|
|
|
switch( e.Key )
|
|
{
|
|
case Key.Enter:
|
|
{
|
|
if( !IsReadOnly )
|
|
{
|
|
_fireSelectionChangedEvent = false;
|
|
BindingExpression binding = BindingOperations.GetBindingExpression( TextBox, System.Windows.Controls.TextBox.TextProperty );
|
|
binding.UpdateSource();
|
|
_fireSelectionChangedEvent = true;
|
|
}
|
|
return;
|
|
}
|
|
case Key.Add:
|
|
if( this.AllowSpin && !this.AllowTextInput && !this.IsReadOnly )
|
|
{
|
|
this.DoIncrement();
|
|
e.Handled = true;
|
|
}
|
|
_fireSelectionChangedEvent = false;
|
|
break;
|
|
case Key.Subtract:
|
|
if( this.AllowSpin && !this.AllowTextInput && !this.IsReadOnly )
|
|
{
|
|
this.DoDecrement();
|
|
e.Handled = true;
|
|
}
|
|
_fireSelectionChangedEvent = false;
|
|
break;
|
|
case Key.Right:
|
|
if( this.IsCurrentValueValid() )
|
|
{
|
|
this.PerformKeyboardSelection( selectionStart + selectionLength );
|
|
e.Handled = true;
|
|
}
|
|
_fireSelectionChangedEvent = false;
|
|
break;
|
|
case Key.Left:
|
|
if( this.IsCurrentValueValid() )
|
|
{
|
|
this.PerformKeyboardSelection( selectionStart > 0 ? selectionStart - 1 : 0 );
|
|
e.Handled = true;
|
|
}
|
|
_fireSelectionChangedEvent = false;
|
|
break;
|
|
default:
|
|
{
|
|
_fireSelectionChangedEvent = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
base.OnPreviewKeyDown( e );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Hanlders
|
|
|
|
private void TextBox_SelectionChanged( object sender, RoutedEventArgs e )
|
|
{
|
|
if( _fireSelectionChangedEvent )
|
|
{
|
|
this.PerformMouseSelection();
|
|
}
|
|
else
|
|
_fireSelectionChangedEvent = true;
|
|
}
|
|
|
|
private void DateTimeUpDownBase_Loaded( object sender, RoutedEventArgs e )
|
|
{
|
|
this.InitSelection();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected virtual void InitializeDateTimeInfoList( T value )
|
|
{
|
|
}
|
|
|
|
protected virtual bool IsCurrentValueValid()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected virtual void PerformMouseSelection()
|
|
{
|
|
// Due to case 3424 - TimePicker/DateTimePicker : Selecting any time part always increments the first part
|
|
// Using DispatcherPriority.ApplicationIdle ensures that PerformMouseSelection() runs after any base class has finished handling the mouse click.
|
|
this.Dispatcher.BeginInvoke( DispatcherPriority.ApplicationIdle, new Action( () =>
|
|
{
|
|
var dateTimeInfo = this.GetDateTimeInfo( TextBox.SelectionStart );
|
|
|
|
if( ( dateTimeInfo != null ) && ( dateTimeInfo.Type == DateTimePart.Other ) )
|
|
{
|
|
this.Dispatcher.BeginInvoke( DispatcherPriority.Background, new Action( () =>
|
|
{
|
|
this.Select( this.GetDateTimeInfo( dateTimeInfo.StartPosition + dateTimeInfo.Length ) );
|
|
} ) );
|
|
return;
|
|
}
|
|
|
|
this.Select( dateTimeInfo );
|
|
} ) );
|
|
}
|
|
|
|
protected virtual bool IsLowerThan( T value1, T value2 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected virtual bool IsGreaterThan( T value1, T value2 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
internal DateTimeInfo GetDateTimeInfo( int selectionStart )
|
|
{
|
|
return _dateTimeInfoList.FirstOrDefault( ( info ) =>
|
|
( info.StartPosition <= selectionStart ) && ( selectionStart < ( info.StartPosition + info.Length ) ) );
|
|
}
|
|
|
|
internal DateTimeInfo GetDateTimeInfo( DateTimePart part )
|
|
{
|
|
return _dateTimeInfoList.FirstOrDefault( ( info ) => info.Type == part );
|
|
}
|
|
|
|
internal virtual void Select( DateTimeInfo info )
|
|
{
|
|
if( ( info != null ) && !info.Equals( _selectedDateTimeInfo ) && ( this.TextBox != null ) && !string.IsNullOrEmpty( this.TextBox.Text ) )
|
|
{
|
|
_fireSelectionChangedEvent = false;
|
|
this.TextBox.Select( info.StartPosition, info.Length );
|
|
_fireSelectionChangedEvent = true;
|
|
_selectedDateTimeInfo = info;
|
|
#if VS2008
|
|
this.CurrentDateTimePart = info.Type;
|
|
#else
|
|
this.SetCurrentValue( DateTimeUpDownBase<T>.CurrentDateTimePartProperty, info.Type );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
internal T CoerceValueMinMax( T value )
|
|
{
|
|
if( this.IsLowerThan( value, this.Minimum ) )
|
|
return this.Minimum;
|
|
else if( this.IsGreaterThan( value, this.Maximum ) )
|
|
return this.Maximum;
|
|
else
|
|
return value;
|
|
}
|
|
|
|
internal void ValidateDefaultMinMax( T value )
|
|
{
|
|
// DefaultValue is always accepted.
|
|
if( object.Equals( value, this.DefaultValue ) )
|
|
return;
|
|
|
|
if( this.IsLowerThan( value, this.Minimum ) )
|
|
throw new ArgumentOutOfRangeException( "Minimum", String.Format( "Value must be greater than MinValue of {0}", this.Minimum ) );
|
|
else if( this.IsGreaterThan( value, this.Maximum ) )
|
|
throw new ArgumentOutOfRangeException( "Maximum", String.Format( "Value must be less than MaxValue of {0}", this.Maximum ) );
|
|
}
|
|
|
|
internal T GetClippedMinMaxValue( T value )
|
|
{
|
|
if( this.IsGreaterThan( value, this.Maximum ) )
|
|
return this.Maximum;
|
|
else if( this.IsLowerThan( value, this.Minimum ) )
|
|
return this.Minimum;
|
|
return value;
|
|
}
|
|
|
|
protected internal virtual void PerformKeyboardSelection( int nextSelectionStart )
|
|
{
|
|
this.TextBox.Focus();
|
|
|
|
if( !this.UpdateValueOnEnterKey )
|
|
{
|
|
this.CommitInput();
|
|
}
|
|
|
|
int selectedDateStartPosition = ( _selectedDateTimeInfo != null ) ? _selectedDateTimeInfo.StartPosition : 0;
|
|
int direction = nextSelectionStart - selectedDateStartPosition;
|
|
if( direction > 0 )
|
|
{
|
|
this.Select( this.GetNextDateTimeInfo( nextSelectionStart ) );
|
|
}
|
|
else
|
|
{
|
|
this.Select( this.GetPreviousDateTimeInfo( nextSelectionStart - 1 ) );
|
|
}
|
|
}
|
|
|
|
private DateTimeInfo GetNextDateTimeInfo( int nextSelectionStart )
|
|
{
|
|
DateTimeInfo nextDateTimeInfo = this.GetDateTimeInfo( nextSelectionStart );
|
|
if( nextDateTimeInfo == null )
|
|
{
|
|
nextDateTimeInfo = _dateTimeInfoList.First();
|
|
}
|
|
|
|
DateTimeInfo initialDateTimeInfo = nextDateTimeInfo;
|
|
|
|
while( nextDateTimeInfo.Type == DateTimePart.Other )
|
|
{
|
|
nextDateTimeInfo = this.GetDateTimeInfo( nextDateTimeInfo.StartPosition + nextDateTimeInfo.Length );
|
|
if( nextDateTimeInfo == null )
|
|
{
|
|
nextDateTimeInfo = _dateTimeInfoList.First();
|
|
}
|
|
if( object.Equals( nextDateTimeInfo, initialDateTimeInfo ) )
|
|
throw new InvalidOperationException( "Couldn't find a valid DateTimeInfo." );
|
|
}
|
|
return nextDateTimeInfo;
|
|
}
|
|
|
|
private DateTimeInfo GetPreviousDateTimeInfo( int previousSelectionStart )
|
|
{
|
|
DateTimeInfo previousDateTimeInfo = this.GetDateTimeInfo( previousSelectionStart );
|
|
if( previousDateTimeInfo == null )
|
|
{
|
|
if( _dateTimeInfoList.Count > 0 )
|
|
{
|
|
previousDateTimeInfo = _dateTimeInfoList.Last();
|
|
}
|
|
}
|
|
|
|
DateTimeInfo initialDateTimeInfo = previousDateTimeInfo;
|
|
|
|
while( ( previousDateTimeInfo != null ) && ( previousDateTimeInfo.Type == DateTimePart.Other ) )
|
|
{
|
|
previousDateTimeInfo = this.GetDateTimeInfo( previousDateTimeInfo.StartPosition - 1 );
|
|
if( previousDateTimeInfo == null )
|
|
{
|
|
previousDateTimeInfo = _dateTimeInfoList.Last();
|
|
}
|
|
if( object.Equals( previousDateTimeInfo, initialDateTimeInfo ) )
|
|
throw new InvalidOperationException( "Couldn't find a valid DateTimeInfo." );
|
|
}
|
|
return previousDateTimeInfo;
|
|
}
|
|
|
|
private void InitSelection()
|
|
{
|
|
if( _selectedDateTimeInfo == null )
|
|
{
|
|
this.Select( ( this.CurrentDateTimePart != DateTimePart.Other ) ? this.GetDateTimeInfo( this.CurrentDateTimePart ) : this.GetDateTimeInfo( 0 ) );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|