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.

544 lines
18 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.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Xceed.Wpf.Toolkit.Primitives;
namespace Xceed.Wpf.Toolkit
{
public class TimeSpanUpDown : DateTimeUpDownBase<TimeSpan?>
{
#region Constructors
static TimeSpanUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata( typeof( TimeSpanUpDown ), new FrameworkPropertyMetadata( typeof( TimeSpanUpDown ) ) );
MaximumProperty.OverrideMetadata( typeof( TimeSpanUpDown ), new FrameworkPropertyMetadata( TimeSpan.MaxValue ) );
MinimumProperty.OverrideMetadata( typeof( TimeSpanUpDown ), new FrameworkPropertyMetadata( TimeSpan.MinValue ) );
DefaultValueProperty.OverrideMetadata( typeof( TimeSpanUpDown ), new FrameworkPropertyMetadata( TimeSpan.Zero ) );
}
public TimeSpanUpDown()
{
DataObject.AddPastingHandler( this, this.OnPasting );
}
#endregion //Constructors
#region Properties
#region FractionalSecondsDigitsCount
public static readonly DependencyProperty FractionalSecondsDigitsCountProperty = DependencyProperty.Register( "FractionalSecondsDigitsCount", typeof( int ), typeof( TimeSpanUpDown ), new UIPropertyMetadata( 0, OnFractionalSecondsDigitsCountChanged, OnCoerceFractionalSecondsDigitsCount ) );
public int FractionalSecondsDigitsCount
{
get
{
return ( int )GetValue( FractionalSecondsDigitsCountProperty );
}
set
{
SetValue( FractionalSecondsDigitsCountProperty, value );
}
}
private static object OnCoerceFractionalSecondsDigitsCount( DependencyObject o, object value )
{
TimeSpanUpDown timeSpanUpDown = o as TimeSpanUpDown;
if( timeSpanUpDown != null )
{
int digitsCount = (int)value;
if( digitsCount < 0 || digitsCount > 3 )
throw new ArgumentException( "Fractional seconds digits count must be between 0 and 3." );
}
return value;
}
private static void OnFractionalSecondsDigitsCountChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
TimeSpanUpDown timeSpanUpDown = o as TimeSpanUpDown;
if( timeSpanUpDown != null )
timeSpanUpDown.OnFractionalSecondsDigitsCountChanged( ( int )e.OldValue, ( int )e.NewValue );
}
protected virtual void OnFractionalSecondsDigitsCountChanged( int oldValue, int newValue )
{
var value = this.UpdateValueOnEnterKey
? (this.TextBox != null) ? this.ConvertTextToValue( this.TextBox.Text ) : null
: this.Value;
this.InitializeDateTimeInfoList( value );
this.SyncTextAndValueProperties( false, this.Text );
}
#endregion //FractionalSecondsDigitsCount
#endregion
#region BaseClass Overrides
protected override void OnCultureInfoChanged( CultureInfo oldValue, CultureInfo newValue )
{
var value = this.UpdateValueOnEnterKey
? (this.TextBox != null) ? this.ConvertTextToValue( this.TextBox.Text ) : null
: this.Value;
this.InitializeDateTimeInfoList( value );
}
protected override void SetValidSpinDirection()
{
ValidSpinDirections validDirections = ValidSpinDirections.None;
if( !this.IsReadOnly )
{
if( this.IsLowerThan( this.Value, this.Maximum ) || !this.Value.HasValue || !this.Maximum.HasValue)
validDirections = validDirections | ValidSpinDirections.Increase;
if( this.IsGreaterThan( this.Value, this.Minimum ) || !this.Value.HasValue || !this.Minimum.HasValue )
validDirections = validDirections | ValidSpinDirections.Decrease;
}
if( this.Spinner != null )
this.Spinner.ValidSpinDirection = validDirections;
}
protected override void OnIncrement()
{
this.Increment( this.Step );
}
protected override void OnDecrement()
{
this.Increment( -this.Step );
}
protected override string ConvertValueToText()
{
if( this.Value == null )
return string.Empty;
return this.ParseValueIntoTimeSpanInfo( this.Value );
}
protected override TimeSpan? ConvertTextToValue( string text )
{
if( string.IsNullOrEmpty( text ) )
return null;
TimeSpan timeSpan = TimeSpan.Parse( text );
if( this.ClipValueToMinMax )
{
return this.GetClippedMinMaxValue( timeSpan );
}
this.ValidateDefaultMinMax( timeSpan );
return timeSpan;
}
protected override void OnPreviewTextInput( TextCompositionEventArgs e )
{
e.Handled = !this.IsNumber( e.Text );
base.OnPreviewTextInput( e );
}
protected override void OnPreviewKeyDown( KeyEventArgs e )
{
if( e.Key == Key.Space )
{
e.Handled = true;
}
base.OnPreviewKeyDown( e );
}
protected override void OnTextChanged( string previousValue, string currentValue )
{
if( !_processTextChanged )
return;
if( String.IsNullOrEmpty( currentValue ) )
{
if( !this.UpdateValueOnEnterKey )
{
this.Value = null;
}
return;
}
TimeSpan current = this.Value.HasValue ? this.Value.Value : new TimeSpan();
TimeSpan result;
var success = TimeSpan.TryParse( currentValue, out result );
currentValue = result.ToString();
// When text is typed, if UpdateValueOnEnterKey is true,
// Sync Value on Text only when Enter Key is pressed.
if( ( _isTextChangedFromUI && !this.UpdateValueOnEnterKey )
|| !_isTextChangedFromUI )
{
this.SyncTextAndValueProperties( true, currentValue );
}
}
protected override void OnValueChanged( TimeSpan? oldValue, TimeSpan? newValue )
{
//whenever the value changes we need to parse out the value into out DateTimeInfo segments so we can keep track of the individual pieces
//but only if it is not null
if( newValue != null )
{
var value = this.UpdateValueOnEnterKey
? (this.TextBox != null) ? this.ConvertTextToValue( this.TextBox.Text ) : null
: this.Value;
this.InitializeDateTimeInfoList( value );
}
base.OnValueChanged( oldValue, newValue );
}
protected override void PerformMouseSelection()
{
var value = this.UpdateValueOnEnterKey
? (this.TextBox != null) ? this.ConvertTextToValue( this.TextBox.Text ) : null
: this.Value;
this.InitializeDateTimeInfoList( value );
base.PerformMouseSelection();
}
protected override void InitializeDateTimeInfoList( TimeSpan? value )
{
var lastDayInfo = _dateTimeInfoList.FirstOrDefault( x => x.Type == DateTimePart.Day );
bool hasDay = lastDayInfo != null;
var negInfo = _dateTimeInfoList.FirstOrDefault( x => x.Type == DateTimePart.Other );
bool hasNegative = (negInfo != null) && (negInfo.Content == "-");
_dateTimeInfoList.Clear();
if( value.HasValue && value.Value.TotalMilliseconds < 0 )
{
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Other, Length = 1, Content = "-", IsReadOnly = true } );
// Negative has been added, move TextBox.Selection to keep it on current DateTimeInfo
if( !hasNegative && (this.TextBox != null) )
{
_fireSelectionChangedEvent = false;
this.TextBox.SelectionStart++;
_fireSelectionChangedEvent = true;
}
}
if( value.HasValue && value.Value.Days != 0 )
{
int dayLength = Math.Abs( value.Value.Days ).ToString().Length;
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Day, Length = dayLength, Format = "dd" } );
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Other, Length = 1, Content = ".", IsReadOnly = true } );
if( this.TextBox != null )
{
//number of digits for days has changed when selection is not on date part, move TextBox.Selection to keep it on current DateTimeInfo
if( hasDay && (dayLength != lastDayInfo.Length) && (_selectedDateTimeInfo.Type != DateTimePart.Day) )
{
_fireSelectionChangedEvent = false;
this.TextBox.SelectionStart = Math.Max( 0, this.TextBox.SelectionStart + (dayLength - lastDayInfo.Length) );
_fireSelectionChangedEvent = true;
}
// Day has been added, move TextBox.Selection to keep it on current DateTimeInfo
else if( !hasDay )
{
_fireSelectionChangedEvent = false;
this.TextBox.SelectionStart += (dayLength + 1);
_fireSelectionChangedEvent = true;
}
}
}
// Day has been removed, move TextBox.Selection to keep it on current DateTimeInfo
else if( hasDay )
{
_fireSelectionChangedEvent = false;
this.TextBox.SelectionStart = Math.Max( hasNegative ? 1 : 0, this.TextBox.SelectionStart - (lastDayInfo.Length + 1) );
_fireSelectionChangedEvent = true;
}
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Hour24, Length = 2, Format = "hh" } );
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Other, Length = 1, Content = ":", IsReadOnly = true } );
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Minute, Length = 2, Format = "mm" } );
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Other, Length = 1, Content = ":", IsReadOnly = true } );
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Second, Length = 2, Format = "ss" } );
if( this.FractionalSecondsDigitsCount > 0 )
{
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Other, Length = 1, Content = ".", IsReadOnly = true } );
string fraction = new string( 'f', this.FractionalSecondsDigitsCount );
//If the "f" custom format specifier is used alone, specify "%f" so that it is not misinterpreted as a standard format string.
if( fraction.Length == 1 )
{
fraction = "%" + fraction;
}
_dateTimeInfoList.Add( new DateTimeInfo() { Type = DateTimePart.Millisecond, Length = this.FractionalSecondsDigitsCount, Format = fraction } );
}
if( value.HasValue )
{
this.ParseValueIntoTimeSpanInfo( value );
}
}
protected override bool IsLowerThan( TimeSpan? value1, TimeSpan? value2 )
{
if( value1 == null || value2 == null )
return false;
return (value1.Value < value2.Value);
}
protected override bool IsGreaterThan( TimeSpan? value1, TimeSpan? value2 )
{
if( value1 == null || value2 == null )
return false;
return (value1.Value > value2.Value);
}
#endregion
#region Methods
private string ParseValueIntoTimeSpanInfo( TimeSpan? value )
{
string text = string.Empty;
_dateTimeInfoList.ForEach( info =>
{
if( info.Format == null )
{
info.StartPosition = text.Length;
info.Length = info.Content.Length;
text += info.Content;
}
else
{
TimeSpan span = TimeSpan.Parse( value.ToString() );
info.StartPosition = text.Length;
#if VS2008
switch (info.Format)
{
case "hh":
info.Content = span.Hours.ToString("00");
break;
case "mm":
info.Content = span.Minutes.ToString("00");
break;
case "ss":
info.Content = span.Seconds.ToString("00");
break;
case "dd":
info.Content = span.Days.ToString();
break;
case "%f":
info.Content = (span.Milliseconds / 100).ToString();
break;
case "ff":
info.Content = (span.Milliseconds / 10).ToString();
break;
case "fff":
info.Content = span.Milliseconds.ToString();
break;
default:
throw new InvalidOperationException("Wrong TimeSpan format");
}
#else
info.Content = span.ToString( info.Format, this.CultureInfo.DateTimeFormat );
#endif
if( info.Format == "dd" )
{
info.Content = Convert.ToInt32( info.Content ).ToString();
}
info.Length = info.Content.Length;
text += info.Content;
}
} );
return text;
}
private TimeSpan? UpdateTimeSpan( TimeSpan? currentValue, int value )
{
DateTimeInfo info = _selectedDateTimeInfo;
//this only occurs when the user manually type in a value for the Value Property
if( info == null )
{
info = (this.CurrentDateTimePart != DateTimePart.Other)
? this.GetDateTimeInfo( this.CurrentDateTimePart )
: (_dateTimeInfoList[ 0 ].Content != "-") ? _dateTimeInfoList[ 0 ] : _dateTimeInfoList[ 1 ]; //Skip negative sign
}
TimeSpan? result = null;
try
{
switch( info.Type )
{
case DateTimePart.Day:
result = ( ( TimeSpan )currentValue).Add( new TimeSpan( value, 0, 0, 0, 0 ) );
break;
case DateTimePart.Hour24:
result = ( ( TimeSpan )currentValue).Add( new TimeSpan( 0, value, 0, 0, 0 ) );
break;
case DateTimePart.Minute:
result = ( ( TimeSpan )currentValue).Add( new TimeSpan( 0, 0, value, 0, 0 ) );
break;
case DateTimePart.Second:
result = ( ( TimeSpan )currentValue).Add( new TimeSpan( 0, 0, 0, value, 0 ) );
break;
case DateTimePart.Millisecond:
switch( this.FractionalSecondsDigitsCount )
{
case 1:
value = value * 100;
break;
case 2:
value = value * 10;
break;
default:
value = value * 1;
break;
}
result = ( ( TimeSpan )currentValue).Add( new TimeSpan( 0, 0, 0, 0, value ) );
break;
default:
break;
}
}
catch
{
//this can occur if the date/time = 1/1/0001 12:00:00 AM which is the smallest date allowed.
//I could write code that would validate the date each and everytime but I think that it would be more
//efficient if I just handle the edge case and allow an exeption to occur and swallow it instead.
}
result = ( ( result != null ) && result.HasValue )
? result.Value
: result;
result = this.CoerceValueMinMax( result );
return result;
}
private void Increment( int step )
{
// if UpdateValueOnEnterKey is true,
// Sync Value on Text only when Enter Key is pressed.
if( this.UpdateValueOnEnterKey )
{
var currentValue = this.ConvertTextToValue( this.TextBox.Text );
var newValue = currentValue.HasValue
? this.UpdateTimeSpan( currentValue, step )
: this.DefaultValue ?? TimeSpan.Zero;
if( newValue != null )
{
this.InitializeDateTimeInfoList( newValue );
var selectionStart = this.TextBox.SelectionStart;
var selectionLength = this.TextBox.SelectionLength;
#if VS2008
this.TextBox.Text = newValue.Value.ToString();
#else
this.TextBox.Text = newValue.Value.ToString( this.GetTimeSpanFormat() );
#endif
this.TextBox.Select( selectionStart, selectionLength );
}
}
else
{
if( this.Value.HasValue )
{
var newValue = this.UpdateTimeSpan( this.Value, step );
if( newValue != null )
{
this.InitializeDateTimeInfoList( newValue );
var selectionStart = this.TextBox.SelectionStart;
var selectionLength = this.TextBox.SelectionLength;
this.Value = newValue;
this.TextBox.Select( selectionStart, selectionLength );
}
}
else
{
this.Value = this.DefaultValue ?? TimeSpan.Zero;
}
}
}
#if !VS2008
private string GetTimeSpanFormat()
{
//use this format : "d.hh\:mm\:ss\.fff"
var formatParts = _dateTimeInfoList.Select( part =>
{
if( part.Format == null )
return '\\' + part.Content;
if( part.Format.Contains( 'd' ) )
{
var dayInfo = _dateTimeInfoList.FirstOrDefault( info => info.Type == DateTimePart.Day );
part.Format = string.Join( "", Enumerable.Repeat( "d", dayInfo.Length ) );
}
return part.Format;
} ).ToList();
return string.Join( "", formatParts );
}
#endif
private bool IsNumber( string str )
{
foreach( char c in str )
{
if( !char.IsNumber( c ) )
return false;
}
return true;
}
#endregion
#region Event Handlers
private void OnPasting( object sender, DataObjectPastingEventArgs e )
{
if( e.DataObject.GetDataPresent( typeof( string ) ) )
{
// Allow pasting only TimeSpan values
var pasteText = e.DataObject.GetData( typeof( string ) ) as string;
TimeSpan result;
var success = TimeSpan.TryParse( pasteText, out result );
if( !success )
{
e.CancelCommand();
}
}
}
#endregion
}
}