WPF工具包DatePicker仅限月/年

Chr*_*her 20 c# datetime datepicker wpftoolkit

我正在使用上面的Toolkit的Datepicker,但我想将它限制为仅限月份和年份,因为在这种情况下用户不知道或不关心确切的日期.显然,数据存储在Datetime中格式将存储日,但这与我无关.是否有一种简单的方法来解决这个问题?

谢谢

Sim*_*mon 26

感谢@FernandoGarcía的基础.

我为DatePicker编写了DateFormat和IsMonthYear附加属性,以启用月/年选择.

IsMonthYear附加属性将DatePicker的Calendar.DisplayMode限制为Year和Decade,以防止从CalendarMode.Month中进行选择.它还将DatePicker.SelectedDate的日期部分设置为1.

DateFormat附加属性不限于此场景,它也可以与IsMonthYear分开使用,为DatePicker的显示和输入提供格式字符串.

附加属性的示例用法:

<DatePicker my:DatePickerCalendar.IsMonthYear="True"
            my:DatePickerDateFormat.DateFormat="MM/yyyy"/>
Run Code Online (Sandbox Code Playgroud)

IsMonthYear附加属性是:

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 = (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);
    }
}
Run Code Online (Sandbox Code Playgroud)

并且DateFormat附加属性是:

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))
            };
        var textBox = GetTemplateTextBox(datePicker);
        textBox.SetBinding(TextBox.TextProperty, binding);

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

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

    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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


ken*_*ner 14

如果您可以使用Calendar控件,则可以执行此操作

<toolkit:Calendar x:Name="_calendar" DisplayModeChanged="_calendar_DisplayModeChanged" DisplayMode="Year" />
Run Code Online (Sandbox Code Playgroud)

有了这个代码隐藏

Private Sub _calendar_DisplayModeChanged(ByVal sender As System.Object, ByVal e As Microsoft.Windows.Controls.CalendarModeChangedEventArgs)

    If _calendar.DisplayMode = Microsoft.Windows.Controls.CalendarMode.Month Then
        _calendar.DisplayMode = Microsoft.Windows.Controls.CalendarMode.Year
    End If

End Sub
Run Code Online (Sandbox Code Playgroud)

  • 这不适合我.它从未填充SelectedDate.DisplayDate不值得信任,因为它可能会随着用户移动鼠标而点击另一个按钮而改变. (7认同)

Cha*_*ton 12

这是SimonABT博士的答案的组合,包括所有额外需要的代码以及修复WindowsMicrosoft控件之间的参考冲突.

这非常荒谬,这需要一个345行的类来实现,但如果你包含DatePickerCalendar.cs(使用你的命名空间),构建你的项目,并使用下面的XAML ,它应该工作.

<DatePicker local:DatePickerCalendar.IsMonthYear="True" 
            local:DatePickerDateFormat.DateFormat="MMM-yyyy"
            Text="MMM-yyyy"></DatePicker>
Run Code Online (Sandbox Code Playgroud)

DatePickerCalendar.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Calendar = System.Windows.Controls.Calendar;
using CalendarMode = System.Windows.Controls.CalendarMode;
using CalendarModeChangedEventArgs = System.Windows.Controls.CalendarModeChangedEventArgs;
using DatePicker = System.Windows.Controls.DatePicker;

namespace <YourProject>
{
    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 = (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);
        }
    }

    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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

截图


小智 5

最近我对日历有一个特定的要求,可以选择只选择月份和年份.所以我有一篇关于wpf datepicker的自定义格式的帖子,但在这种情况下我们需要做一些棘手的回答.首先,您需要创建一个这样的特定控件:

public class DatePickerCo : DatePicker
Run Code Online (Sandbox Code Playgroud)

一旦你已经这样做了.当其他人存在另一个帖子时,我不记得该帖子的网址,但无论如何,我认为您需要覆盖OnCalendarOpened方法,如下所示:

 
 protected override void OnCalendarOpened(RoutedEventArgs e)
        {
            var popup = this.Template.FindName(
                "PART_Popup", this) as Popup;
            if (popup != null && popup.Child is System.Windows.Controls.Calendar)
            {
                ((System.Windows.Controls.Calendar)popup.Child).DisplayMode = CalendarMode.Year;
            }

            ((System.Windows.Controls.Calendar)popup.Child).DisplayModeChanged += new EventHandler(DatePickerCo_DisplayModeChanged);
        }
Run Code Online (Sandbox Code Playgroud)

请注意,我们使用事件DisplayModeChanged的处理程序添加最后一行.这对于后续步骤非常重要.好的,下一步是定义DisplayModeChanged


 private void DatePickerCo_DisplayModeChanged(object sender, CalendarModeChangedEventArgs e)
        {
            var popup = this.Template.FindName(
                "PART_Popup", this) as Popup;
            if (popup != null && popup.Child is System.Windows.Controls.Calendar)
            {
                var _calendar = popup.Child as System.Windows.Controls.Calendar;
                if (_calendar.DisplayMode == CalendarMode.Month)
                {
                    _calendar.DisplayMode = CalendarMode.Year;

                    if (IsDropDownOpen)
                    {
                        this.SelectedDate = GetSelectedMonth(_calendar.DisplayDate);
                        this.IsDropDownOpen = false;
                        ((System.Windows.Controls.Calendar)popup.Child).DisplayModeChanged -= new EventHandler(DatePickerCo_DisplayModeChanged);
                    }
                }

            }
        }

 private DateTime? GetSelectedMonth(DateTime? selectedDate)
        {
            if (selectedDate == null)
            {
                selectedDate = DateTime.Now;
            }

            int monthDifferenceStart = DateTimeHelper.CompareYearMonth(selectedDate.Value, DisplayDateRangeStart);
            int monthDifferenceEnd = DateTimeHelper.CompareYearMonth(selectedDate.Value, DisplayDateRangeEnd);

            if (monthDifferenceStart >= 0 && monthDifferenceEnd  0, "monthDifferenceEnd should be greater than 0!");
                    _selectedMonth = DateTimeHelper.DiscardDayTime(DisplayDateRangeEnd);
                }
            }

            return _selectedMonth;
        }
Run Code Online (Sandbox Code Playgroud)

这里有几件事,首先你需要选择一个月,这就是创建GetSelectedMonth函数的原因.第二件事是该方法使用一个名为DateTimeHelper.cs的WPFToolkit类.好的,直到现在我们刚刚创建了月份的功能.但是,一旦您点击月历,我们就会以月/年格式显示所选日期.为了完成,我们只需要为我们的自定义控件创建一个特定的样式,如下所示:

在我的第一个答案中 如何在WPF应用程序中更改DateTimePicker的格式(例如dd/MMM/yyyy)

我用日期选择器的自定义格式解释了所有内容.我希望这对你有帮助.请任何评论或建议,你只是让我知道,问候!!


Gam*_*ist 5

我将Simon的答案翻译成VB,我在这里发布结果(请注明出处)

Imports System.Windows.Threading
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls

''' <summary>     Allows the Date Picker Calendar to select month.
'''                      Use with the attached property IsMonthYear Property.
''' </summary>
''' <remarks> Source : /sf/ask/125895941/ 
'''           Author : </remarks>
Public Class DatePickerCalendar

Public Shared IsMonthYearProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsMonthYear", GetType(System.Boolean), GetType(DatePickerCalendar), New PropertyMetadata(AddressOf OnIsMonthYearChanged))

Public Shared Function GetIsMonthYear(ByVal dobj As DependencyObject) As Boolean
    Return CType(dobj.GetValue(IsMonthYearProperty), Boolean)
End Function

Public Shared Sub SetIsMonthYear(ByVal dobj As DependencyObject, ByVal value As Boolean)
    dobj.SetValue(IsMonthYearProperty, value)
End Sub

Private Shared Sub OnIsMonthYearChanged(ByVal dobj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    Dim MyDatePicker = CType(dobj, DatePicker)
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, _
                    New Action(Of DatePicker, DependencyPropertyChangedEventArgs)(AddressOf SetCalendarEventHandlers), MyDatePicker, e)
End Sub

Private Shared Sub SetCalendarEventHandlers(ByVal datePicker As DatePicker, ByVal e As DependencyPropertyChangedEventArgs)
    If (e.NewValue = e.OldValue) Then
        Return
    End If
    If CType(e.NewValue, Boolean) Then
        AddHandler datePicker.CalendarOpened, AddressOf DatePickerOnCalendarOpened
        AddHandler datePicker.CalendarClosed, AddressOf DatePickerOnCalendarClosed
    Else
        RemoveHandler datePicker.CalendarOpened, AddressOf DatePickerOnCalendarOpened
        RemoveHandler datePicker.CalendarClosed, AddressOf DatePickerOnCalendarClosed
    End If
End Sub

Private Shared Sub DatePickerOnCalendarOpened(ByVal sender As Object, ByVal routedEventArgs As RoutedEventArgs)
    Dim MyCalendar = GetDatePickerCalendar(sender)
    MyCalendar.DisplayMode = CalendarMode.Year
    AddHandler MyCalendar.DisplayModeChanged, AddressOf CalendarOnDisplayModeChanged
End Sub

Private Shared Sub DatePickerOnCalendarClosed(ByVal sender As Object, ByVal routedEventArgs As RoutedEventArgs)
    Dim MyDatePicker = CType(sender, DatePicker)
    Dim MyCalendar = GetDatePickerCalendar(sender)
    MyDatePicker.SelectedDate = MyCalendar.SelectedDate
    RemoveHandler MyCalendar.DisplayModeChanged, AddressOf CalendarOnDisplayModeChanged
End Sub

Private Shared Sub CalendarOnDisplayModeChanged(ByVal sender As Object, ByVal e As CalendarModeChangedEventArgs)
    Dim MyCalendar = CType(sender, Calendar)
    If (MyCalendar.DisplayMode <> CalendarMode.Month) Then
        Return
    End If
    MyCalendar.SelectedDate = GetSelectedCalendarDate(MyCalendar.DisplayDate)
    Dim MyDatePicker = GetCalendarsDatePicker(MyCalendar)
    MyDatePicker.IsDropDownOpen = False
End Sub

Private Shared Function GetDatePickerCalendar(ByVal sender As Object) As Calendar
    Dim MyDatePicker = CType(sender, DatePicker)
    Dim MyPopup = CType(MyDatePicker.Template.FindName("PART_Popup", MyDatePicker), Popup)
    Return CType(MyPopup.Child, Calendar)
End Function

Private Shared Function GetCalendarsDatePicker(ByVal child As FrameworkElement) As DatePicker
    Dim MyParent = CType(child.Parent, FrameworkElement)
    If (MyParent.Name = "PART_Root") Then
        Return CType(MyParent.TemplatedParent, DatePicker)
    End If
    Return GetCalendarsDatePicker(MyParent)
End Function

Private Shared Function GetSelectedCalendarDate(ByVal selectedDate As DateTime?) As DateTime?
    If Not selectedDate.HasValue Then
        Return Nothing
    End If
    Return New DateTime(selectedDate.Value.Year, selectedDate.Value.Month, 1)
End Function
End Class
Run Code Online (Sandbox Code Playgroud)

但对于格式类,我无法让它工作。我使用并稍微修改了petrycol对于月/年显示的
(更简单的)答案。来源:更改 WPF DatePicker 的字符串格式

    <DatePicker   SelectedDate="{Binding FromDate}"  
                  l:DatePickerCalendar.IsMonthYear="True"
                  x:Name="MonthCalendar" HorizontalAlignment="Center"                   
                 >
        <DatePicker.Resources>
            <!--Source : /sf/ask/267388271/
                Author : petrycol -->
            <Style TargetType="{x:Type DatePickerTextBox}">
                <Setter Property="Control.Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <TextBox Width="60"    TextAlignment="Center" x:Name="PART_TextBox"
                                     Text="{Binding Path=SelectedDate, StringFormat='MM yy', 
                                     RelativeSource={RelativeSource AncestorType={x:Type DatePicker}},FallbackValue='-- --'}" />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DatePicker.Resources>
        <!--CalendarOpened="DatePicker_CalendarOpened"-->
    </DatePicker>
Run Code Online (Sandbox Code Playgroud)


Dr.*_*ABT 5

为了添加Simon 的精彩回答,我对 DatePickerDateFormat.cs 进行了一些更改,以防止出现文本框在 dd/MM/yyyy(原始字符串格式)和 MM/yyyy(覆盖字符串格式)之间暂时闪烁的错误。

这是由于 DatePicker 在打开下拉菜单之前立即在内部设置了 PART_Textbox 文本。它将它设置为字符串格式的文本,并且您无能为力 - 它会覆盖您的绑定。

为了防止这种行为,我将此代码添加到上面DatePickerDateFormat 类中

  1. 在 ApplyDateFormat 中,获取下拉按钮并处理 PreviewMouseUp

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

当 PreviewMouseUp 触发时,如果有一个 Selected 日期,则覆盖 On Dropdown Shown 行为(我们模仿 Datepicker 所做的一切,但我们不设置 PART_TextBox 文本)

    /// <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.TryFindAncestorOrSelf<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;                
        }
    }
Run Code Online (Sandbox Code Playgroud)

其中扩展方法 TryFindAncestorOrSelf 沿着可视化树向上查找 T 类型的对象。您可以在此处找到它的实现:http : //www.hardcodet.net/2008/02/find-wpf-parent

希望这可以帮助某人!