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.

887 lines
26 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.Windows;
using System.Windows.Data;
using System.Windows.Input;
using Xceed.Wpf.Toolkit.Primitives;
using System.Windows.Controls;
using System.Linq;
using Xceed.Wpf.Toolkit.Core.Utilities;
namespace Xceed.Wpf.Toolkit
{
public class DateTimeUpDown : DateTimeUpDownBase<DateTime?>
{
#region Members
private DateTime? _lastValidDate; //null
private bool _setKindInternal = false;
#endregion
#region Properties
#region Format
public static readonly DependencyProperty FormatProperty = DependencyProperty.Register( "Format", typeof( DateTimeFormat ), typeof( DateTimeUpDown ), new UIPropertyMetadata( DateTimeFormat.FullDateTime, OnFormatChanged ) );
public DateTimeFormat Format
{
get
{
return ( DateTimeFormat )GetValue( FormatProperty );
}
set
{
SetValue( FormatProperty, value );
}
}
private static void OnFormatChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
DateTimeUpDown dateTimeUpDown = o as DateTimeUpDown;
if( dateTimeUpDown != null )
dateTimeUpDown.OnFormatChanged( ( DateTimeFormat )e.OldValue, ( DateTimeFormat )e.NewValue );
}
protected virtual void OnFormatChanged( DateTimeFormat oldValue, DateTimeFormat newValue )
{
FormatUpdated();
}
#endregion //Format
#region FormatString
public static readonly DependencyProperty FormatStringProperty = DependencyProperty.Register( "FormatString", typeof( string ), typeof( DateTimeUpDown ), new UIPropertyMetadata( default( String ), OnFormatStringChanged ), IsFormatStringValid );
public string FormatString
{
get
{
return ( string )GetValue( FormatStringProperty );
}
set
{
SetValue( FormatStringProperty, value );
}
}
internal static bool IsFormatStringValid( object value )
{
try
{
// Test the format string if it is used.
DateTime.MinValue.ToString( ( string )value, CultureInfo.CurrentCulture );
}
catch
{
return false;
}
return true;
}
private static void OnFormatStringChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
DateTimeUpDown dateTimeUpDown = o as DateTimeUpDown;
if( dateTimeUpDown != null )
dateTimeUpDown.OnFormatStringChanged( ( string )e.OldValue, ( string )e.NewValue );
}
protected virtual void OnFormatStringChanged( string oldValue, string newValue )
{
FormatUpdated();
}
#endregion //FormatString
#region Kind
public static readonly DependencyProperty KindProperty = DependencyProperty.Register( "Kind", typeof( DateTimeKind ), typeof( DateTimeUpDown ),
new FrameworkPropertyMetadata( DateTimeKind.Unspecified, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnKindChanged ) );
public DateTimeKind Kind
{
get
{
return ( DateTimeKind )GetValue( KindProperty );
}
set
{
SetValue( KindProperty, value );
}
}
private static void OnKindChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
{
DateTimeUpDown dateTimeUpDown = o as DateTimeUpDown;
if( dateTimeUpDown != null )
dateTimeUpDown.OnKindChanged( ( DateTimeKind )e.OldValue, ( DateTimeKind )e.NewValue );
}
protected virtual void OnKindChanged( DateTimeKind oldValue, DateTimeKind newValue )
{
//Upate the value based on kind. (Postpone to EndInit if not yet initialized)
if( !_setKindInternal
&& this.Value != null
&& this.IsInitialized )
{
this.Value = this.ConvertToKind( this.Value.Value, newValue );
}
}
private void SetKindInternal( DateTimeKind kind )
{
_setKindInternal = true;
try
{
#if VS2008
// Warning : Binding could be lost
this.Kind = kind;
#else
//We use SetCurrentValue to not erase the possible underlying
//OneWay Binding. (This will also update correctly any
//possible TwoWay bindings).
this.SetCurrentValue( DateTimeUpDown.KindProperty, kind );
#endif
}
finally
{
_setKindInternal = false;
}
}
#endregion //Kind
#region ContextNow (Private)
internal DateTime ContextNow
{
get
{
return DateTimeUtilities.GetContextNow( this.Kind );
}
}
#endregion
#endregion //Properties
#region Constructors
static DateTimeUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata( typeof( DateTimeUpDown ), new FrameworkPropertyMetadata( typeof( DateTimeUpDown ) ) );
MaximumProperty.OverrideMetadata( typeof( DateTimeUpDown ), new FrameworkPropertyMetadata( DateTime.MaxValue ) );
MinimumProperty.OverrideMetadata( typeof( DateTimeUpDown ), new FrameworkPropertyMetadata( DateTime.MinValue ) );
}
public DateTimeUpDown()
{
}
#endregion //Constructors
#region Base Class Overrides
public override bool CommitInput()
{
bool isSyncValid = this.SyncTextAndValueProperties( true, Text );
_lastValidDate = this.Value;
return isSyncValid;
}
protected override void OnCultureInfoChanged( CultureInfo oldValue, CultureInfo newValue )
{
FormatUpdated();
}
protected override void OnIncrement()
{
if( this.IsCurrentValueValid() )
{
if( Value.HasValue )
UpdateDateTime( 1 );
else
Value = DefaultValue ?? this.ContextNow;
}
}
protected override void OnDecrement()
{
if( this.IsCurrentValueValid() )
{
if( Value.HasValue )
UpdateDateTime( -1 );
else
Value = DefaultValue ?? this.ContextNow;
}
}
protected override void OnTextChanged( string previousValue, string currentValue )
{
if( !_processTextChanged )
return;
base.OnTextChanged( previousValue, currentValue );
}
protected override DateTime? ConvertTextToValue( string text )
{
if( string.IsNullOrEmpty( text ) )
return null;
DateTime result;
this.TryParseDateTime( text, out result );
//Do not force "unspecified" to a time-zone specific
//parsed text value. This would result in a lost of precision and
//corrupt data. Let the value impose the Kind to the
//DateTimePicker.
if( this.Kind != DateTimeKind.Unspecified )
{
//Keep the current kind (Local or Utc)
//by imposing it to the parsed text value.
//
//Note: A parsed UTC text value may be
// adjusted with a Local kind and time.
result = this.ConvertToKind( result, this.Kind );
}
if( this.ClipValueToMinMax )
{
return this.GetClippedMinMaxValue( result );
}
this.ValidateDefaultMinMax( result );
return result;
}
protected override string ConvertValueToText()
{
if( Value == null )
return string.Empty;
return Value.Value.ToString( GetFormatString( Format ), CultureInfo );
}
protected override void SetValidSpinDirection()
{
ValidSpinDirections validDirections = ValidSpinDirections.None;
if( !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 object OnCoerceValue( object newValue )
{
//Since only changing the "kind" of a date
//Ex. "2001-01-01 12:00 AM, Kind=Utc" to "2001-01-01 12:00 AM Kind=Local"
//by setting the "Value" property won't trigger a property changed,
//but will call this callback (coerce), we update the Kind here.
DateTime? value = ( DateTime? )base.OnCoerceValue( newValue );
//Let the initialized determine the final "kind" value.
if(value != null && this.IsInitialized)
{
//Update kind based on value kind
this.SetKindInternal( value.Value.Kind );
}
return value;
}
protected override void OnValueChanged( DateTime? oldValue, DateTime? 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 )
ParseValueIntoDateTimeInfo();
base.OnValueChanged( oldValue, newValue );
if( !_isTextChangedFromUI )
{
_lastValidDate = newValue;
}
}
protected override void RaiseValueChangedEvent( DateTime? oldValue, DateTime? newValue )
{
if( ( this.TemplatedParent is TimePicker )
&& ( ( TimePicker )this.TemplatedParent ).TemplatedParent is DateTimePicker )
return;
base.RaiseValueChangedEvent( oldValue, newValue );
}
protected override bool IsCurrentValueValid()
{
DateTime result;
if( string.IsNullOrEmpty( this.TextBox.Text ) )
return true;
return this.TryParseDateTime( this.TextBox.Text, out result );
}
protected override void OnInitialized( EventArgs e )
{
base.OnInitialized( e );
if( this.Value != null )
{
DateTimeKind valueKind = this.Value.Value.Kind;
if( valueKind != this.Kind )
{
//Conflit between "Kind" property and the "Value.Kind" value.
//Priority to the one that is not "Unspecified".
if( this.Kind == DateTimeKind.Unspecified )
{
this.SetKindInternal( valueKind );
}
else
{
this.Value = this.ConvertToKind( this.Value.Value, this.Kind );
}
}
}
}
#endregion //Base Class Overrides
#region Methods
public void SelectAll()
{
_fireSelectionChangedEvent = false;
TextBox.SelectAll();
_fireSelectionChangedEvent = true;
}
private void FormatUpdated()
{
InitializeDateTimeInfoList();
if( Value != null )
ParseValueIntoDateTimeInfo();
// Update the Text representation of the value.
_processTextChanged = false;
this.SyncTextAndValueProperties( false, null );
_processTextChanged = true;
}
protected override void InitializeDateTimeInfoList()
{
_dateTimeInfoList.Clear();
_selectedDateTimeInfo = null;
string format = GetFormatString( Format );
if( string.IsNullOrEmpty( format ) )
return;
while( format.Length > 0 )
{
int elementLength = GetElementLengthByFormat( format );
DateTimeInfo info = null;
switch( format[ 0 ] )
{
case '"':
case '\'':
{
int closingQuotePosition = format.IndexOf( format[ 0 ], 1 );
info = new DateTimeInfo
{
IsReadOnly = true,
Type = DateTimePart.Other,
Length = 1,
Content = format.Substring( 1, Math.Max( 1, closingQuotePosition - 1 ) )
};
elementLength = Math.Max( 1, closingQuotePosition + 1 );
break;
}
case 'D':
case 'd':
{
string d = format.Substring( 0, elementLength );
if( elementLength == 1 )
d = "%" + d;
if( elementLength > 2 )
info = new DateTimeInfo
{
IsReadOnly = true,
Type = DateTimePart.DayName,
Format = d
};
else
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Day,
Format = d
};
break;
}
case 'F':
case 'f':
{
string f = format.Substring( 0, elementLength );
if( elementLength == 1 )
f = "%" + f;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Millisecond,
Format = f
};
break;
}
case 'h':
{
string h = format.Substring( 0, elementLength );
if( elementLength == 1 )
h = "%" + h;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Hour12,
Format = h
};
break;
}
case 'H':
{
string H = format.Substring( 0, elementLength );
if( elementLength == 1 )
H = "%" + H;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Hour24,
Format = H
};
break;
}
case 'M':
{
string M = format.Substring( 0, elementLength );
if( elementLength == 1 )
M = "%" + M;
if( elementLength >= 3 )
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.MonthName,
Format = M
};
else
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Month,
Format = M
};
break;
}
case 'S':
case 's':
{
string s = format.Substring( 0, elementLength );
if( elementLength == 1 )
s = "%" + s;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Second,
Format = s
};
break;
}
case 'T':
case 't':
{
string t = format.Substring( 0, elementLength );
if( elementLength == 1 )
t = "%" + t;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.AmPmDesignator,
Format = t
};
break;
}
case 'Y':
case 'y':
{
string y = format.Substring( 0, elementLength );
if( elementLength == 1 )
y = "%" + y;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Year,
Format = y
};
break;
}
case '\\':
{
if( format.Length >= 2 )
{
info = new DateTimeInfo
{
IsReadOnly = true,
Content = format.Substring( 1, 1 ),
Length = 1,
Type = DateTimePart.Other
};
elementLength = 2;
}
break;
}
case 'g':
{
string g = format.Substring( 0, elementLength );
if( elementLength == 1 )
g = "%" + g;
info = new DateTimeInfo
{
IsReadOnly = true,
Type = DateTimePart.Period,
Format = format.Substring( 0, elementLength )
};
break;
}
case 'm':
{
string m = format.Substring( 0, elementLength );
if( elementLength == 1 )
m = "%" + m;
info = new DateTimeInfo
{
IsReadOnly = false,
Type = DateTimePart.Minute,
Format = m
};
break;
}
case 'z':
{
string z = format.Substring( 0, elementLength );
if( elementLength == 1 )
z = "%" + z;
info = new DateTimeInfo
{
IsReadOnly = true,
Type = DateTimePart.TimeZone,
Format = z
};
break;
}
default:
{
elementLength = 1;
info = new DateTimeInfo
{
IsReadOnly = true,
Length = 1,
Content = format[ 0 ].ToString(),
Type = DateTimePart.Other
};
break;
}
}
_dateTimeInfoList.Add( info );
format = format.Substring( elementLength );
}
}
protected override bool IsLowerThan( DateTime? value1, DateTime? value2 )
{
if( value1 == null || value2 == null )
return false;
return ( value1.Value < value2.Value );
}
protected override bool IsGreaterThan( DateTime? value1, DateTime? value2 )
{
if( value1 == null || value2 == null )
return false;
return ( value1.Value > value2.Value );
}
private static int GetElementLengthByFormat( string format )
{
for( int i = 1; i < format.Length; i++ )
{
if( String.Compare( format[ i ].ToString(), format[ 0 ].ToString(), false ) != 0 )
{
return i;
}
}
return format.Length;
}
private void ParseValueIntoDateTimeInfo()
{
string text = string.Empty;
_dateTimeInfoList.ForEach( info =>
{
if( info.Format == null )
{
info.StartPosition = text.Length;
info.Length = info.Content.Length;
text += info.Content;
}
else
{
DateTime date = Value.Value;
info.StartPosition = text.Length;
info.Content = date.ToString( info.Format, CultureInfo.DateTimeFormat );
info.Length = info.Content.Length;
text += info.Content;
}
} );
}
internal string GetFormatString( DateTimeFormat dateTimeFormat )
{
switch( dateTimeFormat )
{
case DateTimeFormat.ShortDate:
return CultureInfo.DateTimeFormat.ShortDatePattern;
case DateTimeFormat.LongDate:
return CultureInfo.DateTimeFormat.LongDatePattern;
case DateTimeFormat.ShortTime:
return CultureInfo.DateTimeFormat.ShortTimePattern;
case DateTimeFormat.LongTime:
return CultureInfo.DateTimeFormat.LongTimePattern;
case DateTimeFormat.FullDateTime:
return CultureInfo.DateTimeFormat.FullDateTimePattern;
case DateTimeFormat.MonthDay:
return CultureInfo.DateTimeFormat.MonthDayPattern;
case DateTimeFormat.RFC1123:
return CultureInfo.DateTimeFormat.RFC1123Pattern;
case DateTimeFormat.SortableDateTime:
return CultureInfo.DateTimeFormat.SortableDateTimePattern;
case DateTimeFormat.UniversalSortableDateTime:
return CultureInfo.DateTimeFormat.UniversalSortableDateTimePattern;
case DateTimeFormat.YearMonth:
return CultureInfo.DateTimeFormat.YearMonthPattern;
case DateTimeFormat.Custom:
{
switch( this.FormatString )
{
case "d":
return CultureInfo.DateTimeFormat.ShortDatePattern;
case "t":
return CultureInfo.DateTimeFormat.ShortTimePattern;
case "T":
return CultureInfo.DateTimeFormat.LongTimePattern;
case "D":
return CultureInfo.DateTimeFormat.LongDatePattern;
case "f":
return CultureInfo.DateTimeFormat.LongDatePattern + " " + CultureInfo.DateTimeFormat.ShortTimePattern;
case "F":
return CultureInfo.DateTimeFormat.FullDateTimePattern;
case "g":
return CultureInfo.DateTimeFormat.ShortDatePattern + " " + CultureInfo.DateTimeFormat.ShortTimePattern;
case "G":
return CultureInfo.DateTimeFormat.ShortDatePattern + " " + CultureInfo.DateTimeFormat.LongTimePattern;
case "m":
return CultureInfo.DateTimeFormat.MonthDayPattern;
case "y":
return CultureInfo.DateTimeFormat.YearMonthPattern;
case "r":
return CultureInfo.DateTimeFormat.RFC1123Pattern;
case "s":
return CultureInfo.DateTimeFormat.SortableDateTimePattern;
case "u":
return CultureInfo.DateTimeFormat.UniversalSortableDateTimePattern;
default:
return FormatString;
}
}
default:
throw new ArgumentException( "Not a supported format" );
}
}
private void UpdateDateTime( int value )
{
_fireSelectionChangedEvent = false;
DateTimeInfo info = _selectedDateTimeInfo;
//this only occurs when the user manually type in a value for the Value Property
if( info == null )
info = _dateTimeInfoList[ 0 ];
DateTime? result = null;
try
{
switch( info.Type )
{
case DateTimePart.Year:
{
result = ( ( DateTime )Value ).AddYears( value );
break;
}
case DateTimePart.Month:
case DateTimePart.MonthName:
{
result = ( ( DateTime )Value ).AddMonths( value );
break;
}
case DateTimePart.Day:
case DateTimePart.DayName:
{
result = ( ( DateTime )Value ).AddDays( value );
break;
}
case DateTimePart.Hour12:
case DateTimePart.Hour24:
{
result = ( ( DateTime )Value ).AddHours( value );
break;
}
case DateTimePart.Minute:
{
result = ( ( DateTime )Value ).AddMinutes( value );
break;
}
case DateTimePart.Second:
{
result = ( ( DateTime )Value ).AddSeconds( value );
break;
}
case DateTimePart.Millisecond:
{
result = ( ( DateTime )Value ).AddMilliseconds( value );
break;
}
case DateTimePart.AmPmDesignator:
{
result = ( ( DateTime )Value ).AddHours( value * 12 );
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.
}
this.Value = this.CoerceValueMinMax( result );
//we loose our selection when the Value is set so we need to reselect it without firing the selection changed event
TextBox.Select( info.StartPosition, info.Length );
_fireSelectionChangedEvent = true;
}
private bool TryParseDateTime( string text, out DateTime result )
{
bool isValid = false;
result = this.ContextNow;
DateTime current = this.ContextNow;
try
{
current = (this.Value.HasValue)
? this.Value.Value
: DateTime.Parse( this.ContextNow.ToString(), this.CultureInfo.DateTimeFormat );
isValid = DateTimeParser.TryParse( text, this.GetFormatString( Format ), current, this.CultureInfo, out result );
}
catch( FormatException )
{
isValid = false;
}
if( !isValid )
{
isValid = DateTime.TryParseExact( text, this.GetFormatString( this.Format ), this.CultureInfo, DateTimeStyles.None, out result );
}
if( !isValid )
{
result = ( _lastValidDate != null ) ? _lastValidDate.Value : current;
}
return isValid;
}
private DateTime ConvertToKind( DateTime dateTime, DateTimeKind kind )
{
//Same kind, just return same value.
if( kind == dateTime.Kind )
return dateTime;
//"ToLocalTime()" from an unspecified will assume
// That the time was originaly Utc and affect the datetime value.
// Just "Force" the "Kind" instead.
if( dateTime.Kind == DateTimeKind.Unspecified
|| kind == DateTimeKind.Unspecified )
return DateTime.SpecifyKind( dateTime, kind );
return ( kind == DateTimeKind.Local )
? dateTime.ToLocalTime()
: dateTime.ToUniversalTime();
}
#endregion //Methods
}
}