C# WPF 工具包 DatePicker 仅限月/年
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1798513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF Toolkit DatePicker Month/Year Only
提问by ChrisFletcher
I'm using the Toolkit's Datepicker as above but I'd like to restrict it to month and year selections only, as in this situation the users don't know or care about the exact date .Obviously with the data being stored in a Datetime format there will be day stored but that doesn't concern me. Is there an easy way to tie this down?
我正在使用 Toolkit 的 Datepicker 如上所述,但我想将其限制为仅选择月份和年份,因为在这种情况下,用户不知道或不关心确切日期。显然,数据存储在日期时间中格式会存储一天,但这与我无关。有没有简单的方法来解决这个问题?
Thanks
谢谢
采纳答案by kenwarner
If you can use the Calendar control instead you could do
如果您可以改用 Calendar 控件,则可以这样做
<toolkit:Calendar x:Name="_calendar" DisplayModeChanged="_calendar_DisplayModeChanged" DisplayMode="Year" />
with this codebehind
使用此代码隐藏
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
回答by Fernando García
Recently I had a specific requirement for calendar with the option to choose only month and year. So i have a previous post about custom format for the wpf datepicker, but in this case we need to do a little tricky answer. First you need to create a specific control like this:
最近我对日历有一个特定的要求,可以选择只选择月份和年份。所以我之前有一篇关于 wpf 日期选择器的自定义格式的帖子,但在这种情况下,我们需要做一些棘手的回答。首先,您需要创建一个特定的控件,如下所示:
public class DatePickerCo : DatePicker
Once you have already do this. Exists another post when someone else, I don't rememberer the url for that post, but anyway, the think is you need to overwrite the OnCalendarOpened method just like this:
一旦你已经这样做了。当其他人存在另一个帖子时,我不记得该帖子的网址,但无论如何,认为您需要像这样覆盖 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);
}
Notice that we add a last line with the handler for the event DisplayModeChanged. This is really important for the next steps. Ok so the next step is define the DisplayModeChanged
请注意,我们添加了最后一行,其中包含事件 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;
}
Here there's a couple things, first you need to pick a month, so thats the reason for create the function GetSelectedMonth. The second thing is that method use a class of the WPFToolkit called DateTimeHelper.cs. Ok Until now we just created the functionality for pick up the month. But once you have click on the month calendar, we proceed to show the selected date in month/year format. For accomplish that we just need to create a specific style to our custom control like this:
这里有一些事情,首先你需要选择一个月,这就是创建函数 GetSelectedMonth 的原因。第二件事是该方法使用名为 DateTimeHelper.cs 的 WPFToolkit 类。好的 到目前为止,我们刚刚创建了提取月份的功能。但是一旦您点击了月历,我们就会以月/年格式显示所选日期。为此,我们只需要为我们的自定义控件创建一个特定的样式,如下所示:
In my first answer How to change format (e.g. dd/MMM/yyyy) of DateTimePicker in WPF application
在我的第一个回答中 如何在 WPF 应用程序中更改 DateTimePicker 的格式(例如 dd/MMM/yyyy)
I explained all about with the custom format of the date picker. I hope this help you. please any comment or suggestion, you just let me know, regards!!.
我用日期选择器的自定义格式解释了一切。我希望这对你有帮助。请任何评论或建议,你只是让我知道,问候!!
回答by Simon
Thanks to @Fernando García for the basis of this.
感谢@Fernando García 为此提供的基础。
I have written DateFormat and IsMonthYear attached properties for DatePicker to enable Month/Year selection.
我已经为 DatePicker 编写了 DateFormat 和 IsMonthYear 附加属性以启用月/年选择。
The IsMonthYear attached property restricts DatePicker's Calendar.DisplayMode to Year and Decade to prevent selection from CalendarMode.Month. It also sets the day portion of DatePicker.SelectedDate to 1.
IsMonthYear 附加属性将 DatePicker 的 Calendar.DisplayMode 限制为 Year 和 Decade,以防止从 CalendarMode.Month 中进行选择。它还将 DatePicker.SelectedDate 的日期部分设置为 1。
The DateFormat attached property is not limited to this scenario, it can also be used separate from IsMonthYear to provide a format string for DatePicker's display and input.
DateFormat 附加属性不限于此场景,它还可以与 IsMonthYear 分开使用,为 DatePicker 的显示和输入提供格式字符串。
Example usage of the attached properties:
附加属性的示例用法:
<DatePicker my:DatePickerCalendar.IsMonthYear="True"
my:DatePickerDateFormat.DateFormat="MM/yyyy"/>
The IsMonthYear attached property is:
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);
}
}
And the DateFormat attached property is:
而 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;
}
}
}
回答by GameAlchemist
i translated Simon's answer into VB, i post here the results (please give credits to him )
我把西蒙的答案翻译成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 : https://stackoverflow.com/questions/1798513/wpf-toolkit-datepicker-month-year-only
''' 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
But for the format class, i couldn't get it to work.
I used and slighlty modified the (simpler) answer from petrycolfor the month / year display. Source :
Changing the string format of the WPF DatePicker
但是对于格式类,我无法让它工作。
我使用并稍微修改了petrycol中月/年显示的(更简单的)答案。来源:
更改 WPF DatePicker 的字符串格式
<DatePicker SelectedDate="{Binding FromDate}"
l:DatePickerCalendar.IsMonthYear="True"
x:Name="MonthCalendar" HorizontalAlignment="Center"
>
<DatePicker.Resources>
<!--Source : https://stackoverflow.com/questions/3819832/changing-the-string-format-of-the-wpf-datepicker
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>
回答by Dr. ABT
To add to Simon's brilliant answer, I've made some changes to DatePickerDateFormat.cs to prevent a bug where the Textbox momentarily flickers between dd/MM/yyyy (original string format) and MM/yyyy (overridden string format).
为了添加Simon 的精彩回答,我对 DatePickerDateFormat.cs 进行了一些更改,以防止出现文本框在 dd/MM/yyyy(原始字符串格式)和 MM/yyyy(覆盖字符串格式)之间暂时闪烁的错误。
This occurs due to the DatePicker setting the PART_Textbox text internally immediately before the dropdown is opened. It sets it to the string formatted text and there is ntohing you can do about it - it will override your binding.
这是因为 DatePicker 在打开下拉菜单之前立即在内部设置了 PART_Textbox 文本。它将它设置为字符串格式的文本,并且您无能为力 - 它会覆盖您的绑定。
To prevent this behaviour, I added this code to the DatePickerDateFormat class from above.
为了防止这种行为,我将此代码添加到上面的DatePickerDateFormat 类中。
in ApplyDateFormat, get the dropdown button and handle 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); }
在 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); }
When PreviewMouseUp fires, if there is a Selected date, override the On Dropdown Shown behaviour (we mimic everything that the Datepicker does, but we don't set the PART_TextBox Text)
当 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;
}
}
where the extension method TryFindAncestorOrSelf walks up the visual tree to find an object of type T. You can find an implementation of this here: http://www.hardcodet.net/2008/02/find-wpf-parent
其中扩展方法 TryFindAncestorOrSelf 沿着可视化树向上查找 T 类型的对象。您可以在此处找到它的实现:http: //www.hardcodet.net/2008/02/find-wpf-parent
Hope this helps someone!
希望这可以帮助某人!
回答by Charles Clayton
This is a combination of Simon'sand Dr. ABT'sanswers, including all extra required code and with the reference conflicts between Windows
and Microsoft
controls fixed.
这是一个组合西蒙和博士ABT的答案,包括所有额外所需的代码和与之间的相冲突Windows
,并Microsoft
控制固定。
It's pretty ridiculous that this requires a class of 345 lines to achieve, but it should work if you include DatePickerCalendar.cs
(with your namespace), build your project, and use the following XAML.
这需要一个 345 行的类来实现,这是非常荒谬的,但是如果您包含DatePickerCalendar.cs
(使用您的命名空间)、构建您的项目并使用以下 XAML ,它应该可以工作。
<DatePicker local:DatePickerCalendar.IsMonthYear="True"
local:DatePickerDateFormat.DateFormat="MMM-yyyy"
Text="MMM-yyyy"></DatePicker>
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);
}
}
}
Screenshot
截屏
回答by abaga129
I want to provide an alternative solution that doesn't require a lot of code.
You can bind to a DatePickers's CalendarOpened
event. In that event you can set the display mode of the Calendar
to Decade
and add an event handler for the DisplayModeChanged
event on the Calendar
. Then in the DisplayModeChanged event handler you can close the calendar if the mode is month, and also set the SelectedDate
to the current DisplayDate
. This has worked well for my purposes
我想提供一种不需要大量代码的替代解决方案。您可以绑定到 DatePickers 的CalendarOpened
事件。在该事件中,您可以将 的显示模式设置Calendar
为Decade
并为 上的DisplayModeChanged
事件添加事件处理程序Calendar
。然后在 DisplayModeChanged 事件处理程序中,如果模式为月,您可以关闭日历,并将 设置SelectedDate
为当前的DisplayDate
. 这对我的目的很有效
XAML
XAML
<DatePicker
Name="YearPicker"
Grid.Column="1"
SelectedDate="{Binding SelectedDate, Mode=TwoWay}"
CalendarOpened="DatePicker_Opened">
<DatePicker.Resources>
<Style TargetType="DatePickerTextBox">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<TextBox x:Name="PART_TextBox"
Text="{Binding Path=SelectedDate, StringFormat = {}{0:MM-yyyy},
RelativeSource={RelativeSource AncestorType={x:Type DatePicker}}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DatePicker.Resources>
Then for the code behind
然后对于后面的代码
private void DatePicker_Opened(object sender, RoutedEventArgs e)
{
DatePicker datepicker = (DatePicker)sender;
Popup popup = (Popup)datepicker.Template.FindName("PART_Popup", datepicker);
Calendar cal = (Calendar)popup.Child;
cal.DisplayModeChanged += Calender_DisplayModeChanged;
cal.DisplayMode = CalendarMode.Decade;
}
private void Calender_DisplayModeChanged(object sender, CalendarModeChangedEventArgs e)
{
Calendar calendar = (Calendar)sender;
if (calendar.DisplayMode == CalendarMode.Month)
{
calendar.SelectedDate = calendar.DisplayDate;
YearPicker.IsDropDownOpen = false;
}
}
I hope this helps!
我希望这有帮助!