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.

689 lines
20 KiB

/************************************************************************
Extended WPF Toolkit
Copyright (C) 2010-2012 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
This program can be provided to you by Xceed Software Inc. under a
proprietary commercial license agreement for use in non-Open Source
projects. The commercial version of Extended WPF Toolkit also includes
priority technical support, commercial updates, and many additional
useful WPF controls if you license Xceed Business Suite for WPF.
Visit http://xceed.com and follow @datagrid on Twitter.
**********************************************************************/
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;
namespace Xceed.Wpf.Toolkit
{
public class DateTimeUpDown : UpDownBase<DateTime?>
{
#region Members
private List<DateTimeInfo> _dateTimeInfoList = new List<DateTimeInfo>();
private DateTimeInfo _selectedDateTimeInfo;
private bool _fireSelectionChangedEvent = true;
private bool _processTextChanged = true;
#endregion //Members
#region Properties
#region DefaultValue
public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register( "DefaultValue", typeof( DateTime ), typeof( DateTimeUpDown ), new UIPropertyMetadata( DateTime.Now ) );
public DateTime DefaultValue
{
get
{
return ( DateTime )GetValue( DefaultValueProperty );
}
set
{
SetValue( DefaultValueProperty, value );
}
}
#endregion //DefaultValue
#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 )
{
InitializeDateTimeInfoListAndParseValue();
UpdateTextFormatting();
}
#endregion //Format
#region FormatString
public static readonly DependencyProperty FormatStringProperty = DependencyProperty.Register( "FormatString", typeof( string ), typeof( DateTimeUpDown ), new UIPropertyMetadata( default( String ), OnFormatStringChanged ) );
public string FormatString
{
get
{
return ( string )GetValue( FormatStringProperty );
}
set
{
SetValue( FormatStringProperty, value );
}
}
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 )
{
InitializeDateTimeInfoListAndParseValue();
UpdateTextFormatting();
}
#endregion //FormatString
#region Maximum
#endregion //Maximum
#region Minimum
#endregion //Minimum
#endregion //Properties
#region Constructors
static DateTimeUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata( typeof( DateTimeUpDown ), new FrameworkPropertyMetadata( typeof( DateTimeUpDown ) ) );
}
public DateTimeUpDown()
{
InitializeDateTimeInfoList();
}
#endregion //Constructors
#region Base Class Overrides
public override void OnApplyTemplate()
{
if( TextBox != null )
TextBox.SelectionChanged -= TextBox_SelectionChanged;
base.OnApplyTemplate();
if( TextBox != null )
TextBox.SelectionChanged += TextBox_SelectionChanged;
}
protected override void OnCultureInfoChanged( CultureInfo oldValue, CultureInfo newValue )
{
InitializeDateTimeInfoList();
}
protected override void OnIncrement()
{
if( Value.HasValue )
UpdateDateTime( 1 );
else
Value = DefaultValue;
}
protected override void OnDecrement()
{
if( Value.HasValue )
UpdateDateTime( -1 );
else
Value = DefaultValue;
}
protected override void OnPreviewKeyDown( KeyEventArgs e )
{
switch( e.Key )
{
case Key.Enter:
{
if( !IsReadOnly )
{
_fireSelectionChangedEvent = false;
BindingExpression binding = BindingOperations.GetBindingExpression( TextBox, System.Windows.Controls.TextBox.TextProperty );
binding.UpdateSource();
_fireSelectionChangedEvent = true;
e.Handled = true;
}
return;
}
default:
{
_fireSelectionChangedEvent = false;
break;
}
}
base.OnPreviewKeyDown( e );
}
protected override void OnTextChanged( string previousValue, string currentValue )
{
if( !_processTextChanged )
return;
if( !String.IsNullOrEmpty( currentValue ) )
{
DateTime current = Value.HasValue ? Value.Value : DateTime.Parse( DateTime.Now.ToString(), CultureInfo.DateTimeFormat );
DateTime result;
var success = DateTimeParser.TryParse( currentValue, GetFormatString( Format ), current, CultureInfo, out result );
currentValue = result.ToString();
}
SyncTextAndValueProperties( true, currentValue );
}
protected override DateTime? ConvertTextToValue( string text )
{
if( string.IsNullOrEmpty( text ) )
return null;
return DateTime.Parse( text, CultureInfo );
}
protected override string ConvertValueToText()
{
if( Value == null )
return string.Empty;
return Value.Value.ToString( GetFormatString( Format ), CultureInfo );
}
protected override void SetValidSpinDirection()
{
//TODO: implement Minimum and Maximum
}
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 );
}
#endregion //Base Class Overrides
#region Event Hanlders
void TextBox_SelectionChanged( object sender, RoutedEventArgs e )
{
if( _fireSelectionChangedEvent )
PerformMouseSelection();
else
_fireSelectionChangedEvent = true;
}
#endregion //Event Hanlders
#region Methods
private void InitializeDateTimeInfoListAndParseValue()
{
InitializeDateTimeInfoList();
if( Value != null )
ParseValueIntoDateTimeInfo();
}
private void InitializeDateTimeInfoList()
{
_dateTimeInfoList.Clear();
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 );
}
}
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 void PerformMouseSelection()
{
_dateTimeInfoList.ForEach( info =>
{
if( ( info.StartPosition <= TextBox.SelectionStart ) && ( TextBox.SelectionStart < ( info.StartPosition + info.Length ) ) )
{
Select( info );
return;
}
} );
}
private void Select( DateTimeInfo info )
{
_fireSelectionChangedEvent = false;
TextBox.Select( info.StartPosition, info.Length );
_fireSelectionChangedEvent = true;
_selectedDateTimeInfo = info;
}
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:
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 ];
try
{
switch( info.Type )
{
case DateTimePart.Year:
{
Value = ( ( DateTime )Value ).AddYears( value );
break;
}
case DateTimePart.Month:
case DateTimePart.MonthName:
{
Value = ( ( DateTime )Value ).AddMonths( value );
break;
}
case DateTimePart.Day:
case DateTimePart.DayName:
{
Value = ( ( DateTime )Value ).AddDays( value );
break;
}
case DateTimePart.Hour12:
case DateTimePart.Hour24:
{
Value = ( ( DateTime )Value ).AddHours( value );
break;
}
case DateTimePart.Minute:
{
Value = ( ( DateTime )Value ).AddMinutes( value );
break;
}
case DateTimePart.Second:
{
Value = ( ( DateTime )Value ).AddSeconds( value );
break;
}
case DateTimePart.Millisecond:
{
Value = ( ( DateTime )Value ).AddMilliseconds( value );
break;
}
case DateTimePart.AmPmDesignator:
{
Value = ( ( 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.
}
//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 void UpdateTextFormatting()
{
_processTextChanged = false;
if( Value.HasValue )
Text = ConvertValueToText();
_processTextChanged = true;
}
#endregion //Methods
}
}