diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeParser.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeParser.cs new file mode 100644 index 00000000..14539718 --- /dev/null +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeParser.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Microsoft.Windows.Controls +{ + internal class DateTimeParser + { + #region Properties + + private DateTimeFormatInfo DateTimeFormatInfo { get; set; } + + public string Format { get; set; } + + private IEnumerable MonthNames { get { return DateTimeFormatInfo.AbbreviatedMonthNames.Union(DateTimeFormatInfo.MonthNames); } } + + #endregion //Properties + + #region Constructors + + public DateTimeParser(DateTimeFormatInfo dateTimeFormatInfo) + { + DateTimeFormatInfo = dateTimeFormatInfo; + } + + public DateTimeParser(DateTimeFormatInfo dateTimeFormatInfo, string format) + { + DateTimeFormatInfo = dateTimeFormatInfo; + Format = format; + } + + #endregion //Constructors + + #region Methods + + public bool TryParse(string value, out DateTime result, DateTime currentDate) + { + bool success = false; + result = currentDate; + + if (string.IsNullOrEmpty(value)) + return false; + + //parse date + DateTime date; + success = TryParseDate(value, out date, currentDate); + + //parse time + DateTime time; + success = TryParseTime(value, out time, currentDate); + + //merge the two + result = MergeDateAndTime(date, time); + + return success; + } + + #region Parse Date + + public bool TryParseDate(string value, out DateTime result, DateTime currentDate) + { + bool success = false; + result = currentDate; + + if (string.IsNullOrEmpty(value)) + return false; + + var dateParts = GetDateParts(ResolveDateString(value)).ToArray(); + + if (dateParts.Length > 0) + { + var dateFormatParts = DateTimeFormatInfo.ShortDatePattern.Split(new string[] { DateTimeFormatInfo.DateSeparator }, StringSplitOptions.RemoveEmptyEntries).ToList(); + int yearIndex = dateFormatParts.IndexOf(dateFormatParts.FirstOrDefault(e => e.Contains("y") || e.Contains("Y"))); + if (yearIndex >= 0 && yearIndex < dateParts.Length && dateParts[yearIndex].Length <= 2 && !dateParts.Any(dp => dp.Length > 2 && dp != dateParts[yearIndex])) + { + if (dateParts[yearIndex].Length == 0) + { + dateParts[yearIndex] = "00"; + } + else if (dateParts[yearIndex].Length == 1) + { + dateParts[yearIndex] = "0" + dateParts[yearIndex]; + } + + dateParts[yearIndex] = string.Format("{0}{1}", currentDate.Year / 100, dateParts[yearIndex]); + } + + success = DateTime.TryParse(string.Join(DateTimeFormatInfo.DateSeparator, dateParts), DateTimeFormatInfo, DateTimeStyles.None, out result); + if (!success) + result = currentDate; + } + + return success; + } + + private string ResolveDateString(string date) + { + string[] dateParts = new string[3]; // Month/Day/Year + + string[] dateSeparators = new string[] { ",", " ", "/", "-", "T" }; + + var dates = date.Split(dateSeparators, StringSplitOptions.RemoveEmptyEntries).ToList(); + var formats = Format.Split(dateSeparators, StringSplitOptions.RemoveEmptyEntries).ToList(); + + //strip out the date pieces + for (int i = 0; i < formats.Count; i++) + { + var format = formats[i]; + if (!format.Equals("dddd") && !format.Contains(DateTimeFormatInfo.AMDesignator) && !format.Contains(DateTimeFormatInfo.PMDesignator)) + { + if (format.Contains("M")) + dateParts[0] = dates[i]; + else if (format.Contains("d")) + dateParts[1] = dates[i]; + else if (format.Contains("y")) + dateParts[2] = dates[i]; + } + } + + return string.Join(DateTimeFormatInfo.DateSeparator, dateParts); + } + + protected List GetDateParts(string date) + { + var months = new Regex(GetDatePattern(), RegexOptions.IgnoreCase); + var dateParts = months.Matches(date) + .OfType() + .Select(match => match.Value) + .Where(s => !string.IsNullOrEmpty(s)) + .ToList(); + return dateParts; + } + + protected string GetDatePattern() + { + var pattern = new StringBuilder(@"[0-9]+"); + + foreach (var m in MonthNames.Where(m => !string.IsNullOrEmpty(m))) + { + pattern.AppendFormat(@"|(?<=\b|\W|[0-9_]){0}(?=\b|\W|[0-9_])", m); + } + + return pattern.ToString(); + } + + #endregion //Parse Date + + #region Parse Time + + public bool TryParseTime(string value, out DateTime result, DateTime fallback) + { + bool success = false; + result = fallback; + + if (string.IsNullOrEmpty(value)) + return false; + + return success; + } + + private string ResolveTimeString(string time) + { + return string.Empty; + } + + #endregion //Parse Time + + public static DateTime MergeDateAndTime(DateTime date, DateTime time) + { + return new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second, time.Millisecond); + } + + #endregion // Methods + } +} diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeUpDown.cs b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeUpDown.cs index f2857cf6..5bc07308 100644 --- a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeUpDown.cs +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Implementation/DateTimeUpDown.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Windows; using System.Windows.Input; using Microsoft.Windows.Controls.Primitives; +using System.Windows.Data; namespace Microsoft.Windows.Controls { @@ -16,6 +17,8 @@ namespace Microsoft.Windows.Controls private bool _fireSelectionChangedEvent = true; private bool _isSyncingTextAndValueProperties; + private DateTimeParser _dateParser; + #endregion //Members #region Properties @@ -83,7 +86,7 @@ namespace Microsoft.Windows.Controls get { return (DateTime)GetValue(NullValueProperty); } set { SetValue(NullValueProperty, value); } } - + #endregion //NullValue @@ -162,6 +165,24 @@ namespace Microsoft.Windows.Controls { base.OnApplyTemplate(); TextBox.SelectionChanged += TextBox_SelectionChanged; + + _dateParser = new DateTimeParser(DateTimeFormatInfo, GetFormatString(Format)); + } + + protected override void OnIncrement() + { + if (Value.HasValue) + UpdateDateTime(1); + else + Value = NullValue; + } + + protected override void OnDecrement() + { + if (Value.HasValue) + UpdateDateTime(-1); + else + Value = NullValue; } protected override void OnPreviewKeyDown(KeyEventArgs e) @@ -170,25 +191,18 @@ namespace Microsoft.Windows.Controls { case Key.Enter: { + if (IsEditable) + { + _fireSelectionChangedEvent = false; + BindingExpression binding = BindingOperations.GetBindingExpression(TextBox, System.Windows.Controls.TextBox.TextProperty); + binding.UpdateSource(); + _fireSelectionChangedEvent = true; + } return; } - case Key.Delete: - { - Value = null; - break; - } - case Key.Left: - { - PerformKeyboardSelection(-1); - break; - } - case Key.Right: - { - PerformKeyboardSelection(1); - break; - } default: { + _fireSelectionChangedEvent = false; break; } } @@ -196,13 +210,29 @@ namespace Microsoft.Windows.Controls base.OnPreviewKeyDown(e); } + protected override void OnTextChanged(string previousValue, string currentValue) + { + if (String.IsNullOrEmpty(currentValue)) + { + Value = null; + return; + } + + //TODO: clean this up and make sure it doesn't fire recursively + DateTime current = Value.HasValue ? Value.Value : DateTime.Now; + DateTime result; + var success = _dateParser.TryParse(currentValue, out result, current); + + SyncTextAndValueProperties(InputBase.TextProperty, result); + } + #endregion //Base Class Overrides #region Event Hanlders void TextBox_SelectionChanged(object sender, RoutedEventArgs e) { - if (_fireSelectionChangedEvent) + if (_fireSelectionChangedEvent) PerformMouseSelection(); else _fireSelectionChangedEvent = true; @@ -223,28 +253,6 @@ namespace Microsoft.Windows.Controls #region Methods - #region Abstract - - protected override void OnIncrement() - { - if (Value.HasValue) - UpdateDateTime(1); - else - Value = NullValue; - } - - protected override void OnDecrement() - { - if (Value.HasValue) - UpdateDateTime(-1); - else - Value = NullValue; - } - - #endregion //Abstract - - #region Private - private void InitializeDateTimeInfoListAndParseValue() { InitializeDateTimeInfoList(); @@ -268,7 +276,7 @@ namespace Microsoft.Windows.Controls 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)).ToString() }; + 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; } @@ -451,34 +459,6 @@ namespace Microsoft.Windows.Controls }); } - /// - /// Performs the keyboard selection. - /// - /// The direction. - /// -1 = Left, 1 = Right - private void PerformKeyboardSelection(int direction) - { - DateTimeInfo info; - int index = _dateTimeInfoList.IndexOf(_selectedDateTimeInfo); - - //make sure we stay within the selection ranges - if ((index == 0 && direction == -1) || (index == _dateTimeInfoList.Count - 1 && direction == 1)) - return; - - //get the DateTimeInfo at the next position - index += direction; - info = _dateTimeInfoList[index]; - - //we don't care about spaces and commas, only select valid DateTimeInfos - while (info.Type == DateTimePart.Other) - { - info = _dateTimeInfoList[index += direction]; - } - - //perform selection - Select(info); - } - private void Select(DateTimeInfo info) { _fireSelectionChangedEvent = false; @@ -527,7 +507,6 @@ namespace Microsoft.Windows.Controls if (info == null) info = _dateTimeInfoList[0]; - switch (info.Type) { case DateTimePart.Year: @@ -584,14 +563,15 @@ namespace Microsoft.Windows.Controls _fireSelectionChangedEvent = true; } - #endregion //Private - - protected object ConvertTextToValue(string text) + private DateTime? ConvertTextToValue(string text) { - throw new NotImplementedException("ConvertTextToValue"); + if (string.IsNullOrEmpty(text)) + return null; + + return DateTime.Parse(text, CultureInfo.CurrentCulture); } - protected string ConvertValueToText(object value) + private string ConvertValueToText(object value) { if (value == null) return string.Empty; @@ -599,7 +579,7 @@ namespace Microsoft.Windows.Controls return dt.ToString(GetFormatString(Format), CultureInfo.CurrentCulture); } - protected void SyncTextAndValueProperties(DependencyProperty p, object newValue) + private void SyncTextAndValueProperties(DependencyProperty p, object newValue) { //prevents recursive syncing properties if (_isSyncingTextAndValueProperties) diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Themes/Generic.xaml b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Themes/Generic.xaml index 9008d57a..d825f9f4 100644 --- a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Themes/Generic.xaml +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/DateTimeUpDown/Themes/Generic.xaml @@ -1,6 +1,9 @@  + xmlns:local="clr-namespace:Microsoft.Windows.Controls" + xmlns:coreConverters="clr-namespace:Microsoft.Windows.Controls.Core.Converters"> + + @@ -30,7 +33,7 @@ FontWeight="{TemplateBinding FontWeight}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" - IsReadOnly="True" + IsReadOnly="{Binding IsEditable, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource InverseBoolConverter}}" MinWidth="20" AcceptsReturn="False" TextWrapping="NoWrap" TabIndex="{TemplateBinding TabIndex}" diff --git a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj index 8f45e45d..1e5f0139 100644 --- a/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj +++ b/ExtendedWPFToolkitSolution/Src/WPFToolkit.Extended/WPFToolkit.Extended.csproj @@ -176,6 +176,7 @@ +