Skip to content

Commit

Permalink
Hotfix for DateTimePicker/TimePicker (#2710)
Browse files Browse the repository at this point in the history
* Add changes from @xxMUROxx
  - Binding should now work correctly in both directions
  - [Fix] Issue where clearing text does not immediately set value to ""
  - Add Unit-Test for DateTimePicker
  - [Fix] Issue where Hour 0 is not shown when using 24h clock
  - [Fix] Issue where Binding issues on DateTimePicker/TimePicker are shown on the output window
  - [Fix] Change DateTimePicker.SelectedDate to BindsTwoWayByDefault. The reason is that we do not have to use Mode=TwoWay explicit, and DatePicker.SelectedDate has BindsTwoWayByDefault too.

* [Fix] Failing Unit Test in PR #2710 (#2711)

* SetDatePartValues only if datetime is not null

* Add changes to 1.4.0.md
  • Loading branch information
punker76 authored Oct 24, 2016
1 parent 41726a6 commit 21dfea1
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 45 deletions.
5 changes: 5 additions & 0 deletions docs/release-notes/1.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ MahApps.Metro v1.4.0 bug fix and feature release.
* `MultiFrameImageMode.NoScaleSmallerFrame`
It takes the largest frame from the window which has equal or smaller size than the window's icon template. The frame is rendered centered if it's smaller.
![image](https://cloud.githubusercontent.com/assets/73690/11567646/3bb8e1fc-99e8-11e5-90f5-682d3d87527d.png)
- Changes for `DateTimePicker` and `TimePicker` [#2710](https://github.com/MahApps/MahApps.Metro/pull/2710)
+ Change `DateTimePicker.SelectedDate` to `BindsTwoWayByDefault`. The reason is that we do not have to use Mode=TwoWay explicit, and DatePicker.SelectedDate has BindsTwoWayByDefault too.
+ [Fix] Issue where Binding issues on DateTimePicker/TimePicker are shown on the output window
+ [Fix] Issue where Hour 0 is not shown when using 24h clock
+ Fix issue where clearing text does not immediately set value to `null` but `00:00:00` and `0001.01.01 00.00.00` respectively

# Closed Issues / PRs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

/// <summary>
/// Represents a control that allows the user to select a date and a time.
Expand All @@ -29,10 +30,11 @@ public class DateTimePicker : TimePickerBase
typeof(EventHandler<TimePickerBaseSelectionChangedEventArgs<DateTime?>>),
typeof(DateTimePicker));

public static readonly DependencyProperty SelectedDateProperty = DatePicker.SelectedDateProperty.AddOwner(typeof(DateTimePicker), new PropertyMetadata(OnSelectedDateChanged));
public static readonly DependencyProperty SelectedDateProperty = DatePicker.SelectedDateProperty.AddOwner(typeof(DateTimePicker), new FrameworkPropertyMetadata(default(DateTime?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedDateChanged));

private const string ElementCalendar = "PART_Calendar";
private Calendar _calendar;
private bool _deactivateWriteValueToTextBox;

static DateTimePicker()
{
Expand Down Expand Up @@ -134,7 +136,7 @@ public override void OnApplyTemplate()
{
_calendar = GetTemplateChild(ElementCalendar) as Calendar;
base.OnApplyTemplate();
_calendar = GetTemplateChild(ElementCalendar) as Calendar;
SetDatePartValues();
}

protected virtual void OnSelectedDateChanged(TimePickerBaseSelectionChangedEventArgs<DateTime?> e)
Expand All @@ -154,6 +156,7 @@ protected override void ApplyBindings()
_calendar.SetBinding(Calendar.FirstDayOfWeekProperty, GetBinding(FirstDayOfWeekProperty));
_calendar.SetBinding(Calendar.IsTodayHighlightedProperty, GetBinding(IsTodayHighlightedProperty));
_calendar.SetBinding(FlowDirectionProperty, GetBinding(FlowDirectionProperty));
_calendar.SetBinding(Calendar.SelectedDateProperty, GetBinding(SelectedDateProperty));
}
}

Expand All @@ -164,30 +167,46 @@ protected sealed override void ApplyCulture()
FirstDayOfWeek = SpecificCultureInfo.DateTimeFormat.FirstDayOfWeek;
}

protected override string GetValueForTextBox()
{
var formatInfo = SpecificCultureInfo.DateTimeFormat;
var dateTimeFormat = string.Intern($"{formatInfo.ShortDatePattern} {formatInfo.LongTimePattern}");

var selectedDateTimeFromGui = this.GetSelectedDateTimeFromGUI();
var valueForTextBox = selectedDateTimeFromGui?.ToString(dateTimeFormat, this.SpecificCultureInfo);
return valueForTextBox;
}

protected override void OnRangeBaseValueChanged(object sender, SelectionChangedEventArgs e)
{
base.OnRangeBaseValueChanged(sender, e);

SetDatePartValues();
}

protected override void SubscribeEvents()
protected override void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
{
base.SubscribeEvents();

if (_calendar != null)
DateTime ts;
if (DateTime.TryParse(((DatePickerTextBox)sender).Text, SpecificCultureInfo, System.Globalization.DateTimeStyles.None, out ts))
{
_calendar.SelectedDatesChanged += OnSelectedDatesChanged;
SelectedDate = ts;
}
else
{
if (SelectedDate == null)
{
// if already null, overwrite wrong data in textbox
WriteValueToTextBox();
}
SelectedDate = null;
}
}

protected override void UnSubscribeEvents()
protected override void WriteValueToTextBox()
{
base.UnSubscribeEvents();

if (_calendar != null)
if (!_deactivateWriteValueToTextBox)
{
_calendar.SelectedDatesChanged -= OnSelectedDatesChanged;
base.WriteValueToTextBox();
}
}

Expand All @@ -210,6 +229,12 @@ private static void OnSelectedDateChanged(DependencyObject d, DependencyProperty
{
var dateTimePicker = (DateTimePicker)d;

/* Without deactivating changing SelectedTime would callbase.OnSelectedTimeChanged.
* This would write too and this would result in duplicate writing.
* More problematic would be instead that a short amount of time SelectedTime would be as value in TextBox
*/
dateTimePicker._deactivateWriteValueToTextBox = true;

var dt = (DateTime?)e.NewValue;
if (dt.HasValue)
{
Expand All @@ -220,30 +245,35 @@ private static void OnSelectedDateChanged(DependencyObject d, DependencyProperty
{
dateTimePicker.SetDefaultTimeOfDayValues();
}

dateTimePicker._deactivateWriteValueToTextBox = false;

dateTimePicker.WriteValueToTextBox();
}

private void OnSelectedDatesChanged(object sender, SelectionChangedEventArgs e)
private DateTime? GetSelectedDateTimeFromGUI()
{
if (e.AddedItems != null)
// Because Calendar.SelectedDate is bound to this.SelectedDate return this.SelectedDate
var selectedDate = SelectedDate;

if (selectedDate != null)
{
var dt = (DateTime)e.AddedItems[0];
dt = dt.Add(SelectedTime.GetValueOrDefault());
SelectedDate = dt;
return selectedDate.Value.Date + GetSelectedTimeFromGUI().GetValueOrDefault();
}
}

private DateTime GetCorrectDateTime()
{
return SelectedDate.GetValueOrDefault(DateTime.Today).Date + SelectedTime.GetValueOrDefault();
return null;
}

private void SetDatePartValues()
{
var dateTime = GetCorrectDateTime();
DisplayDate = dateTime != DateTime.MinValue ? dateTime : DateTime.Today;
if (SelectedDate != DisplayDate || (Popup != null && Popup.IsOpen))
var dateTime = GetSelectedDateTimeFromGUI();
if (dateTime != null)
{
SelectedDate = DisplayDate;
DisplayDate = dateTime != DateTime.MinValue ? dateTime : DateTime.Today;
if (SelectedDate != DisplayDate || (Popup != null && Popup.IsOpen))
{
SelectedDate = DisplayDate;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,16 @@ public abstract class TimePickerBase : Control
private Selector _ampmSwitcher;
private Button _button;
private bool _deactivateRangeBaseEvent;
private bool _deactivateTextChangedEvent;
private bool _textInputChanged;
private UIElement _hourHand;
private Selector _hourInput;
private UIElement _minuteHand;
private Selector _minuteInput;
private Popup _popup;
private UIElement _secondHand;
private Selector _secondInput;
private DatePickerTextBox _textBox;
protected DatePickerTextBox _textBox;

static TimePickerBase()
{
Expand Down Expand Up @@ -375,17 +377,21 @@ public override void OnApplyTemplate()
SetHandVisibility(HandVisibility);
SetPickerVisibility(PickerVisibility);

SetHourPartValues(SelectedTime.GetValueOrDefault());
WriteValueToTextBox();

SetDefaultTimeOfDayValues();
SubscribeEvents();
ApplyCulture();
ApplyBindings();

}

protected virtual void ApplyBindings()
{
if (this.Popup != null)
if (Popup != null)
{
this.Popup.SetBinding(Popup.IsOpenProperty, GetBinding(IsDropDownOpenProperty));
Popup.SetBinding(Popup.IsOpenProperty, GetBinding(IsDropDownOpenProperty));
}
}

Expand Down Expand Up @@ -424,14 +430,33 @@ protected Binding GetBinding(DependencyProperty property)
return new Binding(property.Name) { Source = this };
}

protected virtual void OnRangeBaseValueChanged(object sender, SelectionChangedEventArgs e)
protected virtual string GetValueForTextBox()
{
var valueForTextBox = (DateTime.MinValue + SelectedTime)?.ToString(string.Intern(SpecificCultureInfo.DateTimeFormat.LongTimePattern), SpecificCultureInfo);
return valueForTextBox;
}

protected virtual void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (_deactivateRangeBaseEvent)
TimeSpan ts;
if (TimeSpan.TryParse(((DatePickerTextBox)sender).Text, SpecificCultureInfo, out ts))
{
return;
SelectedTime = ts;
}
else
{
if (SelectedTime == null)
{
// if already null, overwrite wrong data in textbox
WriteValueToTextBox();
}
SelectedTime = null;
}
}

SelectedTime = this.GetTimeOfDayFromClockHands();
protected virtual void OnRangeBaseValueChanged(object sender, SelectionChangedEventArgs e)
{
SelectedTime = this.GetSelectedTimeFromGUI();
}

protected virtual void OnSelectedTimeChanged(TimePickerBaseSelectionChangedEventArgs<TimeSpan?> e)
Expand All @@ -455,6 +480,11 @@ protected virtual void SubscribeEvents()
{
_button.Click += OnButtonClicked;
}
if (_textBox != null)
{
_textBox.TextChanged += OnTextChanged;
_textBox.LostFocus += InternalOnTextBoxLostFocus;
}
}

protected virtual void UnSubscribeEvents()
Expand All @@ -465,6 +495,21 @@ protected virtual void UnSubscribeEvents()
{
_button.Click -= OnButtonClicked;
}
if (_textBox != null)
{
_textBox.TextChanged -= OnTextChanged;
_textBox.LostFocus -= InternalOnTextBoxLostFocus;
}
}

protected virtual void WriteValueToTextBox()
{
if (_textBox != null)
{
_deactivateTextChangedEvent = true;
_textBox.Text = GetValueForTextBox();
_deactivateTextChangedEvent = false;
}
}

private static IList<int> CreateValueList(int interval)
Expand Down Expand Up @@ -511,11 +556,29 @@ private static object CoerceSourceHours(DependencyObject d, object basevalue)
{
return hourList.Where(i => i > 0 && i <= 12).OrderBy(i => i, new AmPmComparer());
}
return hourList.Where(i => i > 0 && i <= 24);
return hourList.Where(i => i >= 0 && i < 24);
}
return Enumerable.Empty<int>();
}

private void InternalOnTextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (_textInputChanged)
{
_textInputChanged = false;

OnTextBoxLostFocus(sender, e);
}
}

private void InternalOnRangeBaseValueChanged(object sender, SelectionChangedEventArgs e)
{
if (!_deactivateRangeBaseEvent)
{
OnRangeBaseValueChanged(sender, e);
}
}

private static void OnCultureChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var timePartPickerBase = (TimePickerBase)d;
Expand Down Expand Up @@ -567,9 +630,25 @@ private static void OnPickerVisibilityChanged(DependencyObject d, DependencyProp
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var timePartPickerBase = (TimePickerBase)d;

if (timePartPickerBase._deactivateRangeBaseEvent)
{
return;
}

timePartPickerBase.SetHourPartValues((e.NewValue as TimeSpan?).GetValueOrDefault(TimeSpan.Zero));

timePartPickerBase.OnSelectedTimeChanged(new TimePickerBaseSelectionChangedEventArgs<TimeSpan?>(SelectedTimeChangedEvent, (TimeSpan?)e.OldValue, (TimeSpan?)e.NewValue));

timePartPickerBase.WriteValueToTextBox();
}

private void OnTextChanged(object sender, TextChangedEventArgs e)
{
if (!_deactivateTextChangedEvent)
{
_textInputChanged = true;
}
}

private static void SetVisibility(UIElement partHours, UIElement partMinutes, UIElement partSeconds, TimePartVisibility visibility)
Expand Down Expand Up @@ -606,7 +685,7 @@ private static void SetDefaultTimeOfDayValue(Selector selector)
}
}

private TimeSpan? GetTimeOfDayFromClockHands()
protected TimeSpan? GetSelectedTimeFromGUI()
{
{
if (IsValueSelected(_hourInput) &&
Expand Down Expand Up @@ -703,6 +782,11 @@ private void SetHandVisibility(TimePartVisibility visibility)

private void SetHourPartValues(TimeSpan timeOfDay)
{
if (this._deactivateRangeBaseEvent)
{
return;
}

_deactivateRangeBaseEvent = true;
if (_hourInput != null)
{
Expand Down Expand Up @@ -747,15 +831,15 @@ private void SubscribeRangeBaseValueChanged(params Selector[] selectors)
{
foreach (var selector in selectors.Where(i => i != null))
{
selector.SelectionChanged += OnRangeBaseValueChanged;
selector.SelectionChanged += InternalOnRangeBaseValueChanged;
}
}

private void UnsubscribeRangeBaseValueChanged(params Selector[] selectors)
{
foreach (var selector in selectors.Where(i => i != null))
{
selector.SelectionChanged -= OnRangeBaseValueChanged;
selector.SelectionChanged -= InternalOnRangeBaseValueChanged;
}
}
}
Expand Down
Loading

0 comments on commit 21dfea1

Please sign in to comment.