从DatePicker选择日期后,保持WPF日历打开

时间:2018-08-01 15:55:28

标签: wpf datepicker calendar popup

我正在尝试自定义WPF DatePicker及其日历弹出窗口组件,以在用户选择日期后使弹出窗口保持打开状态。这是由于我对样式所做的扩展,允许用户选择时间。 仅当用户单击样式中的“完成”按钮后,日历才应关闭。

我已经做过多次尝试,每种尝试都取得了不同程度的成功,但是我不得不承认,一个完全有效的解决方案使我望而却步。

在IsDropDownOpen上使用覆盖的示例解决方案;

IsDropDownOpenProperty.OverrideMetadata(typeof(CustomDatePicker), new 
FrameworkPropertyMetadata(false, 
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
OnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));

private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
    {
        var instance = d as CustomDatePicker;
        if (instance != null)
        {
            if (instance.CalendarPopup.IsOpen == false)
            {
                return true;
            }

            var coalesed = (bool) baseValue;

            if (coalesed == false)
            {
                return instance.PopupStaysOpenAfterSelection;
            }
        }

        return baseValue;
    }

'PopupStaysOpenAfterSelection'是我创建的依赖项属性,以便可以自定义此行为。

例如,当用户单击“完成”时,弹出窗口保持打开和关闭状态,但是在与日期选择器按钮交互后,弹出窗口将不再重新打开。

通过这种方法我是否缺少明显的东西/是否有可行的解决方案?

编辑:

根控制

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

using Calendar = System.Windows.Controls.Calendar;

namespace CommonControls.Calendar
{
    [TemplatePart(Name = NavigationPreviousButtonPartName, Type = typeof(Button))]
    [TemplatePart(Name = NavigationNextButtonPartName, Type = typeof(Button))]
    [TemplatePart(Name = NavigationTodayButtonPartName, Type = typeof(Button))]
    [TemplatePart(Name = NavigationDoneButtonPartName, Type = typeof(Button))]
    [TemplatePart(Name = DatePickerButtonPartName, Type = typeof(Button))]
    [TemplatePart(Name = DatePickerTextBoxPartName, Type = typeof(DatePickerTextBox))]
    [TemplatePart(Name = CalendarPopupPartName, Type = typeof(Popup))]
    [TemplatePart(Name = DatePickerDisplayTextBoxPartName, Type = typeof(TextBlock))]
    public class ExtendedDatePicker : DatePicker
    {
        public const string DatePickerDisplayTextBoxPartName = "PART_DisplayDateTextBox";
        public const string DatePickerButtonPartName = "PART_Button";
        public const string DatePickerTextBoxPartName = "PART_TextBox";
        public const string CalendarPopupPartName = "PART_Popup";
        public const string NavigationPreviousButtonPartName = "PART_NavPreviousButton";
        public const string NavigationNextButtonPartName = "PART_NavNextButton";
        public const string NavigationTodayButtonPartName = "PART_TodayButton";
        public const string NavigationDoneButtonPartName = "PART_DoneButton";

        public static readonly DependencyProperty BlackoutDatesInPastProperty =
            DependencyProperty.Register("BlackoutDatesInPast", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(true, BlackoutDatesInPastPropertyChanged));

        public static readonly DependencyProperty ThrowExceptionOnBlackoutDateSelectionProperty =
            DependencyProperty.Register("ThrowExceptionOnBlackoutDateSelection", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false));

        public static readonly DependencyProperty DatePickerButtonTemplateProperty =
            DependencyProperty.Register("DatePickerButtonTemplate", typeof(ControlTemplate), typeof(ExtendedDatePicker), new PropertyMetadata(null));

        public static readonly DependencyProperty CustomDateFormatProperty =
            DependencyProperty.Register("CustomDateFormat", typeof(string), typeof(ExtendedDatePicker), new PropertyMetadata("d", CustomDateFormatPropertyChanged));

        public static readonly DependencyProperty UseCustomDateFormatProperty =
            DependencyProperty.Register("UseCustomDateFormat", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, UseCustomDateFormatPropertyChanged));

        public static readonly DependencyProperty ShowTodayButtonProperty =
            DependencyProperty.Register("ShowTodayButton", typeof(bool), typeof(ExtendedDatePicker), new PropertyMetadata(false, ShowTodayButtonPropertyChanged));

        public static readonly DependencyProperty DaysToJumpProperty =
            DependencyProperty.Register("DaysToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));

        public static readonly DependencyProperty MonthsToJumpProperty =
            DependencyProperty.Register("MonthsToJump", typeof(int), typeof(ExtendedDatePicker), new PropertyMetadata(1));

        public static readonly DependencyProperty PopupStaysOpenAfterSelectionProperty =
            DependencyProperty.Register("PopupStaysOpenAfterSelection", typeof(bool),
                typeof(ExtendedDatePicker), new PropertyMetadata(null));

        protected DatePickerTextBox DatePickerTextBox;

        protected Calendar InternalCalendar;

        protected Popup CalendarPopup;

        protected bool PendingClose = false;

        private Button _navigationPreviousButton;

        private Button _navigationNextButton;

        private Button _navigationTodayButton;

        private Button _navigationDoneButton;

        private Button _datePickerButton;

        private TextBlock _displayDateTextBlock;

        static ExtendedDatePicker()
        {
            IsDropDownOpenProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ExtendedOnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePicker), new FrameworkPropertyMetadata(typeof(ExtendedDatePicker)));
        }

        private static object OnCoerceIsDropDownOpen(DependencyObject d, object basevalue)
        {
            var instance = d as ExtendedDatePicker;
            if (!instance.IsEnabled)
            {
                return false;
            }

            if (!instance.PopupStaysOpenAfterSelection)
            {
                return basevalue;
            }

            if (instance.PendingClose)
            {
                instance.PendingClose = false;
                return false;
            }

            return true;
        }

        /**
         * OLD
        private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue)
        {
            var instance = d as ExtendedDatePicker;
            if (instance != null)
            {
                if (instance.IsDropDownOpen == false)
                {
                    return true;
                }

                var coalesed = (bool) baseValue;

                if (coalesed == false)
                {
                    return instance.PopupStaysOpenAfterSelection;
                }
            }

            return baseValue;
        }
    */

        private static void ExtendedOnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (ExtendedDatePicker) d;
            instance.CoerceValue(IsDropDownOpenProperty);
        }

        public bool BlackoutDatesInPast
        {
            get { return (bool)GetValue(BlackoutDatesInPastProperty); }
            set { SetValue(BlackoutDatesInPastProperty, value); }
        }

        public bool ThrowExceptionOnBlackoutDateSelection
        {
            get { return (bool)GetValue(ThrowExceptionOnBlackoutDateSelectionProperty); }
            set { SetValue(ThrowExceptionOnBlackoutDateSelectionProperty, value); }
        }

        public ControlTemplate DatePickerButtonTemplate
        {
            get { return (ControlTemplate)GetValue(DatePickerButtonTemplateProperty); }
            set { SetValue(DatePickerButtonTemplateProperty, value); }
        }

        public string CustomDateFormat
        {
            get { return (string)GetValue(CustomDateFormatProperty); }
            set { SetValue(CustomDateFormatProperty, value); }
        }

        public bool UseCustomDateFormat
        {
            get { return (bool)GetValue(UseCustomDateFormatProperty); }
            set { SetValue(UseCustomDateFormatProperty, value); }
        }

        public bool PopupStaysOpenAfterSelection
        {
            get { return (bool) GetValue(PopupStaysOpenAfterSelectionProperty); }
            set { SetValue(PopupStaysOpenAfterSelectionProperty, value);}
        }

        public bool ShowTodayButton
        {
            get { return (bool)GetValue(ShowTodayButtonProperty); }
            set { SetValue(ShowTodayButtonProperty, value); }
        }

        public int DaysToJump
        {
            get { return (int)GetValue(DaysToJumpProperty); }
            set { SetValue(DaysToJumpProperty, value); }
        }

        public int MonthsToJump
        {
            get { return (int)GetValue(MonthsToJumpProperty); }
            set { SetValue(MonthsToJumpProperty, value); }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _datePickerButton = GetTemplateChild(DatePickerButtonPartName) as Button;
            _datePickerButton.CreateBinding(TemplateProperty, this, nameof(DatePickerButtonTemplate));

            DatePickerTextBox = GetTemplateChild(DatePickerTextBoxPartName) as DatePickerTextBox;
            _displayDateTextBlock = GetTemplateChild(DatePickerDisplayTextBoxPartName) as TextBlock;
            CalendarPopup = GetTemplateChild(CalendarPopupPartName) as Popup;

            InternalCalendar = CalendarPopup.Child as Calendar;
            InternalCalendar.ApplyTemplate();

            CalendarPopup.Loaded += CalendarPopup_Loaded;

            _navigationPreviousButton = GetTemplateChild(NavigationPreviousButtonPartName) as Button;
            _navigationNextButton = GetTemplateChild(NavigationNextButtonPartName) as Button;

            _navigationTodayButton = InternalCalendar.Template.FindName(NavigationTodayButtonPartName, InternalCalendar) as Button;
            _navigationDoneButton = InternalCalendar.Template.FindName(NavigationDoneButtonPartName, InternalCalendar) as Button;

            if (_navigationTodayButton != null)
            {
                _navigationTodayButton.Click += TodayButton_Click;
            }

            if (_navigationPreviousButton != null)
            {
                _navigationPreviousButton.Click += NavPreviousButton_Click;
            }

            if (_navigationNextButton != null)
            {
                _navigationNextButton.Click += NavNextButton_Click;
            }

            if (_navigationDoneButton != null)
            {
                _navigationDoneButton.Click += NavDoneButton_Click;
            }

            RefreshBlackOutDates();
            RefreshDisplayDate();
            RefreshTextControlsVisibility();
            RefreshTodayButtonVisibility();
        }

        private void CalendarPopup_Loaded(object sender, RoutedEventArgs e)
        {
        }

        protected override void OnSelectedDateChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectedDateChanged(e);

            RefreshDisplayDate();
        }

        private static void BlackoutDatesInPastPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (ExtendedDatePicker)d;
            source.RefreshBlackOutDates();
        }

        private static void CustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (ExtendedDatePicker)d;
            source.RefreshDisplayDate();
        }

        private static void UseCustomDateFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (ExtendedDatePicker)d;
            source.RefreshTextControlsVisibility();
            source.RefreshDisplayDate();
        }

        private static void ShowTodayButtonPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var source = (ExtendedDatePicker)d;
            source.RefreshTodayButtonVisibility();
        }

        private void RefreshTextControlsVisibility()
        {
            if (DatePickerTextBox != null
                && _displayDateTextBlock != null)
            {
                DatePickerTextBox.Visibility = UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
                _displayDateTextBlock.Visibility = !UseCustomDateFormat ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        private void RefreshTodayButtonVisibility()
        {
            if (_navigationTodayButton != null)
            {
                _navigationTodayButton.Visibility = ShowTodayButton ? Visibility.Visible : Visibility.Collapsed;
            }
        }

        private void RefreshDisplayDate()
        {
            if (_displayDateTextBlock != null)
            {
                if (UseCustomDateFormat
                    && !string.IsNullOrEmpty(CustomDateFormat))
                {
                    if (SelectedDate.HasValue)
                    {
                        var d = SelectedDate.Value;

                        _displayDateTextBlock.Text = string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormat));
                    }
                    else
                    {
                        _displayDateTextBlock.Text = "Select a date";
                    }
                }
            }
        }

        private void RefreshBlackOutDates()
        {
            if (BlackoutDatesInPast)
            {
                BlackoutDates.AddDatesInPast();
            }
        }

        private void GoToToday()
        {
            var newDate = DateTime.Today;
            GoToDate(newDate);
        }

        private void GoToNextDate()
        {
            var selectedDate = SelectedDate;
            if (selectedDate.HasValue)
            {
                var newDate = selectedDate.Value;

                if (DaysToJump > 0)
                {
                    newDate = newDate.AddDays(DaysToJump);
                }

                if (MonthsToJump > 0)
                {
                    newDate = newDate.AddMonths(MonthsToJump);
                }

                GoToDate(newDate);
            }
        }

        private void GoToPreviousDate()
        {
            var selectedDate = SelectedDate;
            if (selectedDate.HasValue)
            {
                var newDate = selectedDate.Value;
                if (DaysToJump > 0)
                {
                    newDate = newDate.AddDays(DaysToJump * -1);
                }

                if (MonthsToJump > 0)
                {
                    newDate = newDate.AddMonths(MonthsToJump * -1);
                }

                GoToDate(newDate);
            }
        }

        private void GoToDate(DateTime date)
        {
            if (!BlackoutDates.Contains(date))
            {
                SelectedDate = date;
            }
        }

        private void NavNextButton_Click(object sender, RoutedEventArgs e)
        {
            GoToNextDate();
        }

        private void NavPreviousButton_Click(object sender, RoutedEventArgs e)
        {
            GoToPreviousDate();
        }

        private void TodayButton_Click(object sender, RoutedEventArgs e)
        {
            GoToToday();
        }

        private void NavDoneButton_Click(object sender, RoutedEventArgs e)
        {
            IsDropDownOpen = false;
        }
    }
}

与时间

public class CustomDatePickerWithTime : ExtendedDatePicker
    {
        public static readonly DependencyProperty MinuteIntervalProperty = DependencyProperty.Register(
            "MinuteInterval", typeof(int), typeof(ExtendedDatePickerWithTime), new FrameworkPropertyMetadata(30));

        public static readonly DependencyPropertyKey AvailableTimesPropertyKey = DependencyProperty.RegisterReadOnly(
            "AvailableTimes", typeof(List<TimeSpan>), typeof(ExtendedDatePickerWithTime),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

        public static readonly DependencyProperty AvailableTimesProperty = AvailableTimesPropertyKey.DependencyProperty;

        public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
            "SelectedTime", typeof(TimeSpan), typeof(ExtendedDatePickerWithTime), new PropertyMetadata(default(TimeSpan), OnSelectedTimePropertyChanged));

        static ExtendedDatePickerWithTime()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedDatePickerWithTime),
                new FrameworkPropertyMetadata(typeof(ExtendedDatePickerWithTime)));
        }

        public int MinuteInterval
        {
            get { return (int) GetValue(MinuteIntervalProperty); }
            set { SetValue(MinuteIntervalProperty, value);}
        }

        public List<TimeSpan> AvailableTimes
        {
            get { return (List<TimeSpan>) GetValue(AvailableTimesProperty); }
            protected set { SetValue(AvailableTimesPropertyKey, value);}
        }

        public TimeSpan SelectedTime
        {
            get { return (TimeSpan) GetValue(SelectedTimeProperty); }
            set { SetValue(SelectedTimeProperty, value);}
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            CheckIntervalConstraintsAndConstruct();
            OverrideTextBoxEntryStyle();
        }

        private void OverrideTextBoxEntryStyle()
        {
        }

        private static void OnSelectedTimePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (ExtendedDatePickerWithTime) d;

            instance.SelectedDate = instance.SelectedDate?.Date.Add(instance.SelectedTime) ?? DateTime.Today.Add(instance.SelectedTime);
        }

        private void CheckIntervalConstraintsAndConstruct()
        {
            if (60 % MinuteInterval != 0)
            {
                throw new ArgumentOutOfRangeException(nameof(MinuteInterval), @"The supplied interval in minutes must be divisible by 60");
            }

            var times = new List<TimeSpan>();

            var starting = DateTime.Today;
            var ending = DateTime.Today.AddDays(1);

            for (var ts = starting; ts <= ending.AddMinutes(MinuteInterval *-1); ts = ts.AddMinutes(MinuteInterval))
            {
                times.Add(ts.TimeOfDay);
            }

            AvailableTimes = times;
        }
    }

XAML :(长度超出允许的范围)

https://pastebin.com/W9KrtM8E

0 个答案:

没有答案