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.

730 lines
22 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.Primitives;
namespace Xceed.Wpf.Toolkit
{
public class DateTimeUpDown : DateTimeUpDownBase<DateTime?>
{
#region Members
private DateTime? _lastValidDate;
#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
#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 ?? DateTime.Now;
}
}
protected override void OnDecrement()
{
if( this.IsCurrentValueValid() )
{
if( Value.HasValue )
UpdateDateTime( -1 );
else
Value = DefaultValue ?? DateTime.Now;
}
}
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 );
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 )
validDirections = validDirections | ValidSpinDirections.Increase;
if( this.IsGreaterThan( this.Value, this.Minimum ) || !this.Value.HasValue )
validDirections = validDirections | ValidSpinDirections.Decrease;
}
if( this.Spinner != null )
this.Spinner.ValidSpinDirection = validDirections;
}
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 );
}
#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 = DateTime.Parse( Value.ToString() );
info.StartPosition = text.Length;
info.Content = date.ToString( info.Format, CultureInfo.DateTimeFormat );
info.Length = info.Content.Length;
text += info.Content;
}
} );
}
private 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 = DateTime.Now;
DateTime current = this.Value.HasValue ? this.Value.Value : DateTime.Parse( DateTime.Now.ToString(), this.CultureInfo.DateTimeFormat );
isValid = DateTimeParser.TryParse( text, this.GetFormatString( Format ), current, this.CultureInfo, out result );
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;
}
#endregion //Methods
}
}