/************************************************************************************* 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.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Controls.Primitives; using Xceed.Wpf.Toolkit.Core.Utilities; using Xceed.Wpf.Toolkit.Primitives; #if VS2008 using Microsoft.Windows.Controls; using Microsoft.Windows.Controls.Primitives; #endif namespace Xceed.Wpf.Toolkit { [TemplatePart( Name = PART_Calendar, Type = typeof( Calendar ) )] [TemplatePart( Name = PART_TimeUpDown, Type = typeof( TimePicker ) )] public class DateTimePicker : DateTimePickerBase { private const string PART_Calendar = "PART_Calendar"; private const string PART_TimeUpDown = "PART_TimeUpDown"; #region Members private Calendar _calendar; private TimePicker _timePicker; private DateTime? _calendarTemporaryDateTime; private DateTime? _calendarIntendedDateTime; #endregion //Members #region Properties #region AutoCloseCalendar public static readonly DependencyProperty AutoCloseCalendarProperty = DependencyProperty.Register( "AutoCloseCalendar", typeof( bool ), typeof( DateTimePicker ), new UIPropertyMetadata( false ) ); public bool AutoCloseCalendar { get { return ( bool )GetValue( AutoCloseCalendarProperty ); } set { SetValue( AutoCloseCalendarProperty, value ); } } #endregion //AutoCloseCalendar #region CalendarDisplayMode public static readonly DependencyProperty CalendarDisplayModeProperty = DependencyProperty.Register( "CalendarDisplayMode", typeof( CalendarMode ) , typeof( DateTimePicker ), new UIPropertyMetadata( CalendarMode.Month ) ); public CalendarMode CalendarDisplayMode { get { return (CalendarMode)GetValue( CalendarDisplayModeProperty ); } set { SetValue( CalendarDisplayModeProperty, value ); } } #endregion //CalendarDisplayMode #region TimeFormat public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register( "TimeFormat", typeof( DateTimeFormat ), typeof( DateTimePicker ), new UIPropertyMetadata( DateTimeFormat.ShortTime ) ); public DateTimeFormat TimeFormat { get { return ( DateTimeFormat )GetValue( TimeFormatProperty ); } set { SetValue( TimeFormatProperty, value ); } } #endregion //TimeFormat #region TimeFormatString public static readonly DependencyProperty TimeFormatStringProperty = DependencyProperty.Register( "TimeFormatString", typeof( string ), typeof( DateTimePicker ), new UIPropertyMetadata( default( String ) ), IsTimeFormatStringValid ); public string TimeFormatString { get { return ( string )GetValue( TimeFormatStringProperty ); } set { SetValue( TimeFormatStringProperty, value ); } } private static bool IsTimeFormatStringValid(object value) { return DateTimeUpDown.IsFormatStringValid( value ); } #endregion //TimeFormatString #region TimePickerAllowSpin public static readonly DependencyProperty TimePickerAllowSpinProperty = DependencyProperty.Register( "TimePickerAllowSpin", typeof( bool ), typeof( DateTimePicker ), new UIPropertyMetadata( true ) ); public bool TimePickerAllowSpin { get { return (bool)GetValue( TimePickerAllowSpinProperty ); } set { SetValue( TimePickerAllowSpinProperty, value ); } } #endregion //TimePickerAllowSpin #region TimePickerShowButtonSpinner public static readonly DependencyProperty TimePickerShowButtonSpinnerProperty = DependencyProperty.Register( "TimePickerShowButtonSpinner", typeof( bool ), typeof( DateTimePicker ), new UIPropertyMetadata( true ) ); public bool TimePickerShowButtonSpinner { get { return (bool)GetValue( TimePickerShowButtonSpinnerProperty ); } set { SetValue( TimePickerShowButtonSpinnerProperty, value ); } } #endregion //TimePickerShowButtonSpinner #region TimePickerVisibility public static readonly DependencyProperty TimePickerVisibilityProperty = DependencyProperty.Register( "TimePickerVisibility", typeof( Visibility ), typeof( DateTimePicker ), new UIPropertyMetadata( Visibility.Visible ) ); public Visibility TimePickerVisibility { get { return ( Visibility )GetValue( TimePickerVisibilityProperty ); } set { SetValue( TimePickerVisibilityProperty, value ); } } #endregion //TimePickerVisibility #region TimeWatermark public static readonly DependencyProperty TimeWatermarkProperty = DependencyProperty.Register( "TimeWatermark", typeof( object ), typeof( DateTimePicker ), new UIPropertyMetadata( null ) ); public object TimeWatermark { get { return ( object )GetValue( TimeWatermarkProperty ); } set { SetValue( TimeWatermarkProperty, value ); } } #endregion //TimeWatermark #region TimeWatermarkTemplate public static readonly DependencyProperty TimeWatermarkTemplateProperty = DependencyProperty.Register( "TimeWatermarkTemplate", typeof( DataTemplate ), typeof( DateTimePicker ), new UIPropertyMetadata( null ) ); public DataTemplate TimeWatermarkTemplate { get { return ( DataTemplate )GetValue( TimeWatermarkTemplateProperty ); } set { SetValue( TimeWatermarkTemplateProperty, value ); } } #endregion //TimeWatermarkTemplate #endregion //Properties #region Constructors static DateTimePicker() { DefaultStyleKeyProperty.OverrideMetadata( typeof( DateTimePicker ), new FrameworkPropertyMetadata( typeof( DateTimePicker ) ) ); } public DateTimePicker() { } #endregion //Constructors #region Base Class Overrides public override void OnApplyTemplate() { base.OnApplyTemplate(); if( _calendar != null ) _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged; _calendar = GetTemplateChild( PART_Calendar ) as Calendar; if( _calendar != null ) { _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged; _calendar.SelectedDate = Value ?? null; _calendar.DisplayDate = Value ?? this.ContextNow; this.SetBlackOutDates(); } _timePicker = GetTemplateChild( PART_TimeUpDown ) as TimePicker; _timePicker.ValueChanged += _timePicker_ValueChanged; } private void _timePicker_ValueChanged( object sender, RoutedPropertyChangedEventArgs e ) { e.Handled = true; // if UpdateValueOnEnterKey is true, // Sync Value on Text only when Enter Key is pressed. if( this.UpdateValueOnEnterKey ) { var newTime = e.NewValue as DateTime?; if( newTime != null ) { _fireSelectionChangedEvent = false; var currentDate = this.ConvertTextToValue( this.TextBox.Text ); var date = currentDate ?? this.ContextNow; var newValue = new DateTime( date.Year, date.Month, date.Day, newTime.Value.Hour, newTime.Value.Minute, newTime.Value.Second, newTime.Value.Millisecond, date.Kind ); this.TextBox.Text = newValue.ToString( this.GetFormatString( this.Format ), this.CultureInfo ); _fireSelectionChangedEvent = true; } } } protected override void OnPreviewMouseUp( MouseButtonEventArgs e ) { if( Mouse.Captured is CalendarItem) { Mouse.Capture( null ); if( AutoCloseCalendar && ( _calendar.DisplayMode == CalendarMode.Month ) ) { ClosePopup( true ); } } base.OnPreviewMouseUp( e ); } protected override void OnValueChanged( DateTime? oldValue, DateTime? newValue ) { //The calendar only select the Date part, not the time part. DateTime? newValueDate = (newValue != null) ? newValue.Value.Date : (DateTime?)null; if( _calendar != null && _calendar.SelectedDate != newValueDate) { _calendar.SelectedDate = newValueDate; _calendar.DisplayDate = newValue.GetValueOrDefault( this.ContextNow ); } //If we change any part of the datetime without //using the calendar when the actual date is temporary, //clear the temporary value. if( _calendarTemporaryDateTime != null && newValue != _calendarTemporaryDateTime ) { _calendarTemporaryDateTime = null; _calendarIntendedDateTime = null; } base.OnValueChanged( oldValue, newValue ); } protected override void OnIsOpenChanged( bool oldValue, bool newValue ) { base.OnIsOpenChanged( oldValue, newValue ); if( !newValue ) { _calendarTemporaryDateTime = null; _calendarIntendedDateTime = null; } } protected override void OnPreviewKeyDown( KeyEventArgs e ) { //if the calendar is open then we don't want to modify the behavior of navigating the calendar control with the Up/Down keys. if( !IsOpen ) base.OnPreviewKeyDown( e ); } protected override void OnMaximumChanged( DateTime? oldValue, DateTime? newValue ) { base.OnMaximumChanged( oldValue, newValue ); this.SetBlackOutDates(); } protected override void OnMinimumChanged( DateTime? oldValue, DateTime? newValue ) { base.OnMinimumChanged( oldValue, newValue ); this.SetBlackOutDates(); } #endregion //Base Class Overrides #region Event Handlers protected override void HandleKeyDown( object sender, KeyEventArgs e ) { // The base call will handle the Ctrl+Down, Enter and Esc keys // in order to open o r close the popup. //Do not close the Calendar if the call is handled //by the TimePicker inside the DateTimePicker template if( IsOpen && _timePicker.IsKeyboardFocusWithin && ( _timePicker.IsOpen || e.Handled ) ) return; base.HandleKeyDown( sender, e ); } private void Calendar_SelectedDatesChanged( object sender, SelectionChangedEventArgs e ) { if( e.AddedItems.Count > 0 ) { var newDate = ( DateTime? )e.AddedItems[ 0 ]; if( newDate != null ) { //The Calendar will always return a date with an "Unspecified" Kind. //Force the expected kind to the value. newDate = DateTime.SpecifyKind( newDate.Value, this.Kind ); // Only change the year, month, and day part of the value. Keep everything to the last "tick." // "Milliseconds" aren't precise enough. Use a mathematical scheme instead. if( _calendarIntendedDateTime != null ) { newDate = newDate.Value.Date + _calendarIntendedDateTime.Value.TimeOfDay; _calendarTemporaryDateTime = null; _calendarIntendedDateTime = null; } else if( Value != null ) { newDate = newDate.Value.Date + Value.Value.TimeOfDay; } // Always be sure that the time part of the selected value is always // within the bound of the min max. The time part could be altered // if the calendar's selected date match the Minimum or Maximum date. // Keep in memory the intended time of day, in case that the selected // calendar date is only transitory (browsing the calendar with the keyboard) var limitedDateTime = this.GetClippedMinMaxValue( newDate ); if( limitedDateTime.Value != newDate.Value ) { _calendarTemporaryDateTime = limitedDateTime; _calendarIntendedDateTime = newDate; newDate = limitedDateTime; } } if( this.UpdateValueOnEnterKey ) { _fireSelectionChangedEvent = false; this.TextBox.Text = newDate.Value.ToString( this.GetFormatString( this.Format ), this.CultureInfo ); _fireSelectionChangedEvent = true; } else { if( !object.Equals( newDate, Value ) ) { this.Value = newDate; } } } } protected override void Popup_Opened( object sender, EventArgs e ) { base.Popup_Opened( sender, e ); if( _calendar != null ) _calendar.Focus(); } #endregion //Event Handlers #region Methods private void SetBlackOutDates() { if( _calendar != null ) { _calendar.BlackoutDates.Clear(); if( ( this.Minimum != null ) && this.Minimum.HasValue && ( this.Minimum.Value != DateTime.MinValue ) ) { DateTime minDate = this.Minimum.Value; _calendar.BlackoutDates.Add( new CalendarDateRange( DateTime.MinValue, minDate.AddDays( -1 ) ) ); } if( ( this.Maximum != null ) && this.Maximum.HasValue && ( this.Maximum.Value != DateTime.MaxValue ) ) { DateTime maxDate = this.Maximum.Value; _calendar.BlackoutDates.Add( new CalendarDateRange( maxDate.AddDays( 1 ), DateTime.MaxValue ) ); } } } #endregion //Methods } }