2017-01-17 91 views
2

我的要求是,只显示月份和年份datepicker我显示它,但是当我点击月份其去日期选择如何禁用日期选择和限制datepicker选择仅月和年WPF DatePicker显示和选择

谢谢

+1

其令人惊讶的难度比你所期望的,一个讨厌的一块代码/ XAML的工作要做......没有它在我的项目之一,要去寻找它。 – grek40

+0

您是在寻找日历部分还是日期格式在DatePickerTextBox中,其中显示选定的日期并且用户可以手动输入值? – grek40

+0

Hello @ grek40 |感谢您的考虑和答复。我只是在寻找日历部分。就像用户点击“月份”一样,所以它没有进一步去“日期”,但我选择了月份。感谢您的时间。 – fWd82

回答

1

相关提问/回答:https://stackoverflow.com/a/12993391/5265292

修改弹出

什么需要做的事情:

  • 标记DatePicker控制月/年的模式 - AttachedProperty将进入CalendarMode.Month
  • 手柄Calendar.SelectedDateCalendar.DisplayDate使用
  • 注册到DatePicker.CalendarOpenedDatePicker.CalendarClosed事件
  • 防止了Calendar - 由于不完整选择,SelectedDate本身不可靠。

以下代码可能不是100%固定在您的问题上,因为我有一些不同的要求需要考虑,当我写它。

public static class DatePickerCalendar 
{ 
    public static readonly DependencyProperty IsMonthYearProperty = 
     DependencyProperty.RegisterAttached("IsMonthYear", typeof(bool), typeof(DatePickerCalendar), 
              new FrameworkPropertyMetadata(false, OnIsMonthYearChanged)); 

    public static bool GetIsMonthYear(DependencyObject dobj) 
    { 
     return (bool)dobj.GetValue(IsMonthYearProperty); 
    } 

    public static void SetIsMonthYear(DependencyObject dobj, bool value) 
    { 
     dobj.SetValue(IsMonthYearProperty, value); 
    } 

    private static void OnIsMonthYearChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e) 
    { 
     var datePicker = (DatePicker)dobj; 

     Application.Current.Dispatcher 
      .BeginInvoke(DispatcherPriority.Loaded, 
         new Action<DatePicker, DependencyPropertyChangedEventArgs>(SetCalendarEventHandlers), 
         datePicker, e); 
    } 

    private static void SetCalendarEventHandlers(DatePicker datePicker, DependencyPropertyChangedEventArgs e) 
    { 
     if (e.NewValue == e.OldValue) 
      return; 

     if ((bool)e.NewValue) 
     { 
      datePicker.CalendarOpened += DatePickerOnCalendarOpened; 
      datePicker.CalendarClosed += DatePickerOnCalendarClosed; 
     } 
     else 
     { 
      datePicker.CalendarOpened -= DatePickerOnCalendarOpened; 
      datePicker.CalendarClosed -= DatePickerOnCalendarClosed; 
     } 
    } 

    private static void DatePickerOnCalendarOpened(object sender, RoutedEventArgs routedEventArgs) 
    { 
     var calendar = GetDatePickerCalendar(sender); 
     calendar.DisplayMode = CalendarMode.Year; 

     calendar.DisplayModeChanged += CalendarOnDisplayModeChanged; 

     calendar.KeyDown += Calendar_KeyDown; 
    } 

    private static void DatePickerOnCalendarClosed(object sender, RoutedEventArgs routedEventArgs) 
    { 
     var datePicker = (DatePicker)sender; 
     var calendar = GetDatePickerCalendar(sender); 
     if (calendar.SelectedDate.HasValue) 
     { 
      // warning, this might not be what you want, it's a pretty aggressive selection, where the selected date is changed even when keyboard navigating to a new date and then trying to cancel the selection 
      calendar.SelectedDate = GetSelectedCalendarDate(calendar.DisplayDate); 
     } 
     datePicker.SelectedDate = calendar.SelectedDate; 

     calendar.DisplayModeChanged -= CalendarOnDisplayModeChanged; 

     calendar.KeyDown -= Calendar_KeyDown; 
    } 

    private static void Calendar_KeyDown(object sender, KeyEventArgs e) 
    { 
     if (e.Key == Key.Tab) 
     { 
      var c = (Calendar)sender; 
      c.SelectedDate = GetSelectedCalendarDate(c.DisplayDate); 
     } 
    } 

    private static void CalendarOnDisplayModeChanged(object sender, CalendarModeChangedEventArgs e) 
    { 
     var calendar = (Calendar)sender; 
     if (calendar.DisplayMode != CalendarMode.Month) 
      return; 

     calendar.SelectedDate = GetSelectedCalendarDate(calendar.DisplayDate); 

     var datePicker = GetCalendarsDatePicker(calendar); 
     datePicker.IsDropDownOpen = false; 
    } 

    private static Calendar GetDatePickerCalendar(object sender) 
    { 
     var datePicker = (DatePicker)sender; 
     var popup = (Popup)datePicker.Template.FindName("PART_Popup", datePicker); 
     return ((Calendar)popup.Child); 
    } 

    private static DatePicker GetCalendarsDatePicker(FrameworkElement child) 
    { 
     var parent = (FrameworkElement)child.Parent; 
     if (parent.Name == "PART_Root") 
      return (DatePicker)parent.TemplatedParent; 
     return GetCalendarsDatePicker(parent); 
    } 

    private static DateTime? GetSelectedCalendarDate(DateTime? selectedDate) 
    { 
     if (!selectedDate.HasValue) 
      return null; 
     return new DateTime(selectedDate.Value.Year, selectedDate.Value.Month, 1); 
    } 
} 

用法:

<DatePicker local:DatePickerCalendar.IsMonthYear="True"/> 

随意问,如果事情是不明确的代码的任何部分,我可以在它上面详细说明即可。

1

我implent相同的代码@grek40

只需粘贴代码,如果有人正在寻找它。

XMAL代码

<DatePicker local:DatePickerCalendar.IsMonthYear="True" 
      local:DatePickerDateFormat.DateFormat="MMM-yyyy" 
      Text="MMM-yyyy"></DatePicker> 

CS代码

public class DatePickerCalendar 
    { 
     public static readonly DependencyProperty IsMonthYearProperty = 
      DependencyProperty.RegisterAttached("IsMonthYear", typeof(bool), typeof(DatePickerCalendar), 
               new PropertyMetadata(OnIsMonthYearChanged)); 

     public static bool GetIsMonthYear(DependencyObject dobj) 
     { 
      return (bool)dobj.GetValue(IsMonthYearProperty); 
     } 

     public static void SetIsMonthYear(DependencyObject dobj, bool value) 
     { 
      dobj.SetValue(IsMonthYearProperty, value); 
     } 

     private static void OnIsMonthYearChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e) 
     { 
      var datePicker = (DatePicker)dobj; 

      Application.Current.Dispatcher 
       .BeginInvoke(DispatcherPriority.Loaded, 
          new Action<DatePicker, DependencyPropertyChangedEventArgs>(SetCalendarEventHandlers), 
          datePicker, e); 
     } 

     private static void SetCalendarEventHandlers(DatePicker datePicker, DependencyPropertyChangedEventArgs e) 
     { 
      if (e.NewValue == e.OldValue) 
       return; 

      if ((bool)e.NewValue) 
      { 
       datePicker.CalendarOpened += DatePickerOnCalendarOpened; 
       datePicker.CalendarClosed += DatePickerOnCalendarClosed; 
      } 
      else 
      { 
       datePicker.CalendarOpened -= DatePickerOnCalendarOpened; 
       datePicker.CalendarClosed -= DatePickerOnCalendarClosed; 
      } 
     } 

     private static void DatePickerOnCalendarOpened(object sender, RoutedEventArgs routedEventArgs) 
     { 
      var calendar = GetDatePickerCalendar(sender); 
      calendar.DisplayMode = CalendarMode.Year; 

      calendar.DisplayModeChanged += CalendarOnDisplayModeChanged; 
     } 

     private static void DatePickerOnCalendarClosed(object sender, RoutedEventArgs routedEventArgs) 
     { 
      var datePicker = (DatePicker)sender; 
      var calendar = GetDatePickerCalendar(sender); 
      datePicker.SelectedDate = calendar.SelectedDate; 

      calendar.DisplayModeChanged -= CalendarOnDisplayModeChanged; 
     } 

     private static void CalendarOnDisplayModeChanged(object sender, CalendarModeChangedEventArgs e) 
     { 
      var calendar = (System.Windows.Controls.Calendar)sender; 
      if (calendar.DisplayMode != CalendarMode.Month) 
       return; 

      calendar.SelectedDate = GetSelectedCalendarDate(calendar.DisplayDate); 

      var datePicker = GetCalendarsDatePicker(calendar); 
      datePicker.IsDropDownOpen = false; 
     } 

     private static System.Windows.Controls.Calendar GetDatePickerCalendar(object sender) 
     { 
      var datePicker = (DatePicker)sender; 
      var popup = (Popup)datePicker.Template.FindName("PART_Popup", datePicker); 
      return ((System.Windows.Controls.Calendar)popup.Child); 
     } 

     private static DatePicker GetCalendarsDatePicker(FrameworkElement child) 
     { 
      var parent = (FrameworkElement)child.Parent; 
      if (parent.Name == "PART_Root") 
       return (DatePicker)parent.TemplatedParent; 
      return GetCalendarsDatePicker(parent); 
     } 

     private static DateTime? GetSelectedCalendarDate(DateTime? selectedDate) 
     { 
      if (!selectedDate.HasValue) 
       return null; 
      return new DateTime(selectedDate.Value.Year, selectedDate.Value.Month, 1); 
     } 
    } 

    public class DatePickerDateFormat 
    { 
     public static readonly DependencyProperty DateFormatProperty = 
      DependencyProperty.RegisterAttached("DateFormat", typeof(string), typeof(DatePickerDateFormat), 
               new PropertyMetadata(OnDateFormatChanged)); 

     public static string GetDateFormat(DependencyObject dobj) 
     { 
      return (string)dobj.GetValue(DateFormatProperty); 
     } 

     public static void SetDateFormat(DependencyObject dobj, string value) 
     { 
      dobj.SetValue(DateFormatProperty, value); 
     } 

     private static void OnDateFormatChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e) 
     { 
      var datePicker = (DatePicker)dobj; 

      Application.Current.Dispatcher.BeginInvoke(
       DispatcherPriority.Loaded, new Action<DatePicker>(ApplyDateFormat), datePicker); 
     } 
     private static void ApplyDateFormat(DatePicker datePicker) 
     { 
      var binding = new Binding("SelectedDate") 
      { 
       RelativeSource = new RelativeSource { AncestorType = typeof(DatePicker) }, 
       Converter = new DatePickerDateTimeConverter(), 
       ConverterParameter = new Tuple<DatePicker, string>(datePicker, GetDateFormat(datePicker)), 
       StringFormat = GetDateFormat(datePicker) // This is also new but didnt seem to help 
      }; 

      var textBox = GetTemplateTextBox(datePicker); 
      textBox.SetBinding(TextBox.TextProperty, binding); 

      textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown; 
      textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown; 

      var dropDownButton = GetTemplateButton(datePicker); 

      datePicker.CalendarOpened -= DatePickerOnCalendarOpened; 
      datePicker.CalendarOpened += DatePickerOnCalendarOpened; 

      // Handle Dropdownbutton PreviewMouseUp to prevent issue of flickering textboxes 
      dropDownButton.PreviewMouseUp -= DropDownButtonPreviewMouseUp; 
      dropDownButton.PreviewMouseUp += DropDownButtonPreviewMouseUp; 
     } 

     private static ButtonBase GetTemplateButton(DatePicker datePicker) 
     { 
      return (ButtonBase)datePicker.Template.FindName("PART_Button", datePicker); 
     } 


     /// <summary> 
     ///  Prevents a bug in the DatePicker, where clicking the Dropdown open button results in Text being set to default formatting regardless of StringFormat or binding overrides 
     /// </summary> 
     private static void DropDownButtonPreviewMouseUp(object sender, MouseButtonEventArgs e) 
     { 
      var fe = sender as FrameworkElement; 
      if (fe == null) return; 

      var datePicker = fe.TryFindParent<DatePicker>(); 
      if (datePicker == null || datePicker.SelectedDate == null) return; 

      var dropDownButton = GetTemplateButton(datePicker); 

      // Dropdown button was clicked 
      if (e.OriginalSource == dropDownButton && datePicker.IsDropDownOpen == false) 
      { 
       // Open dropdown 
       datePicker.SetCurrentValue(DatePicker.IsDropDownOpenProperty, true); 

       // Mimic everything else in the standard DatePicker dropdown opening *except* setting textbox value 
       datePicker.SetCurrentValue(DatePicker.DisplayDateProperty, datePicker.SelectedDate.Value); 

       // Important otherwise calendar does not work 
       dropDownButton.ReleaseMouseCapture(); 

       // Prevent datePicker.cs from handling this event 
       e.Handled = true; 
      } 
     } 



     private static TextBox GetTemplateTextBox(Control control) 
     { 
      control.ApplyTemplate(); 
      return (TextBox)control?.Template?.FindName("PART_TextBox", control); 
     } 

     private static void TextBoxOnPreviewKeyDown(object sender, KeyEventArgs e) 
     { 
      if (e.Key != Key.Return) 
       return; 

      /* DatePicker subscribes to its TextBox's KeyDown event to set its SelectedDate if Key.Return was 
      * pressed. When this happens its text will be the result of its internal date parsing until it 
      * loses focus or another date is selected. A workaround is to stop the KeyDown event bubbling up 
      * and handling setting the DatePicker.SelectedDate. */ 

      e.Handled = true; 

      var textBox = (TextBox)sender; 
      var datePicker = (DatePicker)textBox.TemplatedParent; 
      var dateStr = textBox.Text; 
      var formatStr = GetDateFormat(datePicker); 
      datePicker.SelectedDate = DatePickerDateTimeConverter.StringToDateTime(datePicker, formatStr, dateStr); 
     } 

     private static void DatePickerOnCalendarOpened(object sender, RoutedEventArgs e) 
     { 
      /* When DatePicker's TextBox is not focused and its Calendar is opened by clicking its calendar button 
      * its text will be the result of its internal date parsing until its TextBox is focused and another 
      * date is selected. A workaround is to set this string when it is opened. */ 

      var datePicker = (DatePicker)sender; 
      var textBox = GetTemplateTextBox(datePicker); 
      var formatStr = GetDateFormat(datePicker); 
      textBox.Text = DatePickerDateTimeConverter.DateTimeToString(formatStr, datePicker.SelectedDate); 
     } 

     private class DatePickerDateTimeConverter : IValueConverter 
     { 
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
      { 
       var formatStr = ((Tuple<DatePicker, string>)parameter).Item2; 
       var selectedDate = (DateTime?)value; 
       return DateTimeToString(formatStr, selectedDate); 
      } 

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
      { 
       var tupleParam = ((Tuple<DatePicker, string>)parameter); 
       var dateStr = (string)value; 
       return StringToDateTime(tupleParam.Item1, tupleParam.Item2, dateStr); 
      } 

      public static string DateTimeToString(string formatStr, DateTime? selectedDate) 
      { 
       return selectedDate.HasValue ? selectedDate.Value.ToString(formatStr) : null; 
      } 

      public static DateTime? StringToDateTime(DatePicker datePicker, string formatStr, string dateStr) 
      { 
       DateTime date; 
       var canParse = DateTime.TryParseExact(dateStr, formatStr, CultureInfo.CurrentCulture, 
                 DateTimeStyles.None, out date); 

       if (!canParse) 
        canParse = DateTime.TryParse(dateStr, CultureInfo.CurrentCulture, DateTimeStyles.None, out date); 

       return canParse ? date : datePicker.SelectedDate; 
      } 


     } 

    } 



public static class FEExten 
{ 
    /// <summary> 
    /// Finds a parent of a given item on the visual tree. 
    /// </summary> 
    /// <typeparam name="T">The type of the queried item.</typeparam> 
    /// <param name="child">A direct or indirect child of the 
    /// queried item.</param> 
    /// <returns>The first parent item that matches the submitted 
    /// type parameter. If not matching item can be found, a null 
    /// reference is being returned.</returns> 
    public static T TryFindParent<T>(this DependencyObject child) 
     where T : DependencyObject 
    { 
     //get parent item 
     DependencyObject parentObject = GetParentObject(child); 

     //we've reached the end of the tree 
     if (parentObject == null) return null; 

     //check if the parent matches the type we're looking for 
     T parent = parentObject as T; 
     if (parent != null) 
     { 
      return parent; 
     } 
     else 
     { 
      //use recursion to proceed with next level 
      return TryFindParent<T>(parentObject); 
     } 
    } 

    /// <summary> 
    /// This method is an alternative to WPF's 
    /// <see cref="VisualTreeHelper.GetParent"/> method, which also 
    /// supports content elements. Keep in mind that for content element, 
    /// this method falls back to the logical tree of the element! 
    /// </summary> 
    /// <param name="child">The item to be processed.</param> 
    /// <returns>The submitted item's parent, if available. Otherwise 
    /// null.</returns> 
    public static DependencyObject GetParentObject(this DependencyObject child) 
    { 
     if (child == null) return null; 

     //handle content elements separately 
     ContentElement contentElement = child as ContentElement; 
     if (contentElement != null) 
     { 
      DependencyObject parent = ContentOperations.GetParent(contentElement); 
      if (parent != null) return parent; 

      FrameworkContentElement fce = contentElement as FrameworkContentElement; 
      return fce != null ? fce.Parent : null; 
     } 

     //also try searching for parent in framework elements (such as DockPanel, etc) 
     FrameworkElement frameworkElement = child as FrameworkElement; 
     if (frameworkElement != null) 
     { 
      DependencyObject parent = frameworkElement.Parent; 
      if (parent != null) return parent; 
     } 

     //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper 
     return VisualTreeHelper.GetParent(child); 
    } 
} 

PS: 帮助从该GitHub Project拍摄。

幸得原作者:@crclayton