我正在尝试自定义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 :(长度超出允许的范围)