如何实现日期范围选择器
控件名:DateRangePicker 作 者:WPFDevelopersOrg - 驚鏵 原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers 码云链接[2]:https://gitee.com/WPFDevelopersOrg/WPFDevelopers
.NET4 至 .NET8
;Visual Studio 2022
;日期范围选择器在界面中允许选择开始日期和结束日期,并提供高亮显示选择的日期范围。
SetSelectedDates
:设置选择的开始日期和结束日期,并在 Calendar
中高亮显示日期。SetIsHighlightFalse
:取消日期高亮。IsYearMonthBetween
:日期是否在指定的开始日期和结束日期的年份和月份之间。GetCalendarDayButtons
:递归查找日历中的每一个日历按钮,用于进行操作如高亮或取消。SetSelectedDates
方法的实现,它确保日期范围的正确,并在 Calendar
上标记日期。private void SetSelectedDates(DateTime? endDate = null)
{
if (_startDate.HasValue && _endDate.HasValue)
{
if (DateTime.Compare(_startDate.Value, _endDate.Value) > 0)
{
var temp = _startDate.Value;
_startDate = _endDate.Value;
_endDate = temp;
}
_startCalendar.SelectedDates.Clear();
_endCalendar.SelectedDates.Clear();
var eDate = _endDate;
if (endDate.HasValue)
eDate = endDate.Value;
for (var date = _startDate.Value; date < eDate.Value.AddDays(1); date = date.AddDays(1))
{
if (date.Date <= eDate.Value.Date
&&
!_startCalendar.SelectedDates.Contains(date.Date)
&&
date.Date <= _startCalendar.DisplayDateEnd.Value.Date)
{
_startCalendar.SelectedDates.Add(date);
if (date.Date == _startDate.Value.Date || date.Date >= eDate.Value.Date)
continue;
if (_startCalendarDayButtons != null)
{
var day = _startCalendarDayButtons.FirstOrDefault(x =>
x.DataContext is DateTime buttonDate && buttonDate.Date == date.Date);
if (day != null)
DatePickerHelper.SetIsHighlight(day, true);
}
}
if (date.Date >= _endCalendar.DisplayDateStart.Value.Date &&
!_endCalendar.SelectedDates.Contains(date.Date))
if (date.Date >= _startDate.Value.Date
&&
!_endCalendar.SelectedDates.Contains(date)
&&
date.Date >= _endCalendar.DisplayDateStart.Value.Date)
{
_endCalendar.SelectedDates.Add(date);
if (date.Date == eDate.Value.Date || date.Date <= _startDate.Value.Date)
continue;
if (_endCalendarDayButtons != null)
{
var day = _endCalendarDayButtons.FirstOrDefault(x =>
x.DataContext is DateTime buttonDate && buttonDate.Date == date.Date);
if (day != null)
DatePickerHelper.SetIsHighlight(day, true);
}
}
}
if (_clickCount == 2)
{
_popup.IsOpen = false;
StartDate = _startDate;
EndDate = _endDate;
_textBoxStart.Text = _startDate.Value.ToString(DateFormat);
_textBoxEnd.Text = _endDate.Value.ToString(DateFormat);
}
}
}
SetIsHighlightFalse
方法用于取消日期高亮显示的,遍历所有日历按钮并清除当前的高亮状态。private void SetIsHighlightFalse(IEnumerable<CalendarDayButton> calendarDayButtons)
{
if (calendarDayButtons == null)
return;
var days = calendarDayButtons.Where(x => DatePickerHelper.GetIsHighlight(x));
foreach (var day in days)
DatePickerHelper.SetIsHighlight(day, false);
}
IsYearMonthBetween
方法用来判断某个日期是否在特定的年月范围内。private bool IsYearMonthBetween(DateTime dateToCheck, DateTime startDate, DateTime endDate)
{
return dateToCheck.Year == startDate.Year && dateToCheck.Month >= startDate.Month &&
dateToCheck.Year == endDate.Year && dateToCheck.Month <= endDate.Month;
}
GetCalendarDayButtons
方法使用递归查找日历中的所有 CalendarDayButton
控件。private IEnumerable<CalendarDayButton> GetCalendarDayButtons(DependencyObject parent)
{
if (parent == null) yield break;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is CalendarDayButton dayButton)
yield return dayButton;
foreach (var result in GetCalendarDayButtons(child))
yield return result;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using WPFDevelopers.Helpers;
namespaceWPFDevelopers.Controls
{
[TemplatePart(Name = PopupTemplateName, Type = typeof(Popup))]
[TemplatePart(Name = StartCalendarTemplateName, Type = typeof(Calendar))]
[TemplatePart(Name = EndCalendarTemplateName, Type = typeof(Calendar))]
[TemplatePart(Name = TextBoxStartTemplateName, Type = typeof(DatePickerTextBox))]
[TemplatePart(Name = TextBoxEndTemplateName, Type = typeof(DatePickerTextBox))]
publicclassDateRangePicker : Control
{
privateconststring PopupTemplateName = "PART_Popup";
privateconststring StartCalendarTemplateName = "PART_StartCalendar";
privateconststring EndCalendarTemplateName = "PART_EndCalendar";
privateconststring TextBoxStartTemplateName = "PART_TextBoxStart";
privateconststring TextBoxEndTemplateName = "PART_TextBoxEnd";
publicstaticreadonly DependencyProperty StartWatermarkProperty =
DependencyProperty.Register("StartWatermark",
typeof(string),
typeof(DateRangePicker),
new PropertyMetadata(string.Empty));
publicstaticreadonly DependencyProperty EndWatermarkProperty =
DependencyProperty.Register("EndWatermark",
typeof(string),
typeof(DateRangePicker),
new PropertyMetadata(string.Empty));
publicstaticreadonly DependencyProperty StartDateProperty =
DependencyProperty.Register("StartDate", typeof(DateTime?), typeof(DateRangePicker),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
OnStartDateChanged));
publicstaticreadonly DependencyProperty EndDateProperty =
DependencyProperty.Register("EndDate", typeof(DateTime?), typeof(DateRangePicker),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal,
OnEndDateChanged));
publicstaticreadonly DependencyProperty DateFormatFormatProperty =
DependencyProperty.Register("DateFormat", typeof(string), typeof(DateRangePicker),
new PropertyMetadata("yyy-MM-dd"));
publicstaticreadonly DependencyProperty MaxDropDownHeightProperty =
DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(DateRangePicker),
new UIPropertyMetadata(SystemParameters.PrimaryScreenHeight / 2.5, OnMaxDropDownHeightChanged));
privateint _clickCount;
privatebool _isHandlingSelectionChange;
private Popup _popup;
private Calendar _startCalendar, _endCalendar;
private IEnumerable<CalendarDayButton> _startCalendarDayButtons, _endCalendarDayButtons;
private DateTime? _startDate, _endDate;
private DatePickerTextBox _textBoxStart, _textBoxEnd;
static DateRangePicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DateRangePicker),
new FrameworkPropertyMetadata(typeof(DateRangePicker)));
}
publicstring StartWatermark
{
get => (string)GetValue(StartWatermarkProperty);
set => SetValue(StartWatermarkProperty, value);
}
publicstring EndWatermark
{
get => (string)GetValue(EndWatermarkProperty);
set => SetValue(EndWatermarkProperty, value);
}
public DateTime? StartDate
{
get => (DateTime?)GetValue(StartDateProperty);
set => SetValue(StartDateProperty, value);
}
public DateTime? EndDate
{
get => (DateTime?)GetValue(EndDateProperty);
set => SetValue(EndDateProperty, value);
}
publicstring DateFormat
{
get => (string)GetValue(DateFormatFormatProperty);
set => SetValue(DateFormatFormatProperty, value);
}
private static void OnStartDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
private static void OnEndDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
private static void OnMaxDropDownHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctrl = d as DateRangePicker;
if (ctrl != null)
ctrl.OnMaxDropDownHeightChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual void OnMaxDropDownHeightChanged(double oldValue, double newValue)
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_popup = (Popup)GetTemplateChild(PopupTemplateName);
if (_popup != null)
{
_popup.Focusable = true;
_popup.PlacementTarget = this;
_popup.Opened -= Popup_Opened;
_popup.Opened += Popup_Opened;
}
AddHandler(PreviewMouseUpEvent, new MouseButtonEventHandler(OnPreviewMouseUp), true);
_startCalendar = (Calendar)GetTemplateChild(StartCalendarTemplateName);
if (_startCalendar != null)
{
_startCalendar.PreviewMouseUp += OnCalendar_PreviewMouseUp;
_startCalendar.DisplayDateChanged += OnStartCalendar_DisplayDateChanged;
_startCalendar.SelectedDatesChanged += OnStartCalendar_SelectedDatesChanged;
}
_endCalendar = (Calendar)GetTemplateChild(EndCalendarTemplateName);
if (_endCalendar != null)
{
_endCalendar.PreviewMouseUp += OnCalendar_PreviewMouseUp;
_endCalendar.DisplayDateChanged += OnEndCalendar_DisplayDateChanged;
_endCalendar.SelectedDatesChanged += OnEndCalendar_SelectedDatesChanged;
}
var now = DateTime.Now;
var firstDayOfNextMonth = new DateTime(now.Year, now.Month, 1).AddMonths(1);
_startCalendar.DisplayDateEnd = firstDayOfNextMonth.AddDays(-1);
_endCalendar.DisplayDate = firstDayOfNextMonth;
_endCalendar.DisplayDateStart = firstDayOfNextMonth;
var window = Window.GetWindow(this);
if (window != null)
window.MouseDown += OnWindow_MouseDown;
_startCalendarDayButtons = GetCalendarDayButtons(_startCalendar);
_endCalendarDayButtons = GetCalendarDayButtons(_endCalendar);
_textBoxStart = (DatePickerTextBox)GetTemplateChild(TextBoxStartTemplateName);
if (_textBoxStart != null)
_textBoxStart.TextChanged += TextBoxStart_TextChanged;
_textBoxEnd = (DatePickerTextBox)GetTemplateChild(TextBoxEndTemplateName);
if (_textBoxEnd != null)
_textBoxEnd.TextChanged += TextBoxEnd_TextChanged;
Loaded += DateRangePicker_Loaded;
}
private void TextBoxEnd_TextChanged(object sender, TextChangedEventArgs e)
{
if (_textBoxEnd != null)
{
if (DateTime.TryParse(_textBoxEnd.Text, outvar dateTime))
{
if (EndDate.HasValue && dateTime.ToString(DateFormat) == EndDate.Value.ToString(DateFormat))
return;
if (StartDate.HasValue && dateTime < StartDate.Value.Date)
{
EndDate = _endDate;
_textBoxEnd.Text = _endDate.Value.ToString(DateFormat);
return;
}
SetIsHighlightFalse(_endCalendarDayButtons);
EndDate = dateTime;
PopupOpened();
}
else
{
EndDate = _endDate;
_textBoxEnd.Text = _endDate.Value.ToString(DateFormat);
}
}
}
private void TextBoxStart_TextChanged(object sender, TextChangedEventArgs e)
{
if (_textBoxStart != null)
{
if (DateTime.TryParse(_textBoxStart.Text, outvar dateTime))
{
if (StartDate.HasValue && dateTime.ToString(DateFormat) == StartDate.Value.ToString(DateFormat))
return;
if (EndDate.HasValue && dateTime < EndDate.Value.Date)
{
StartDate = _startDate;
_textBoxStart.Text = _startDate.Value.ToString(DateFormat);
return;
}
SetIsHighlightFalse(_startCalendarDayButtons);
StartDate = dateTime;
PopupOpened();
}
else
{
StartDate = _startDate;
_textBoxStart.Text = _startDate.Value.ToString(DateFormat);
}
}
}
private void ClearSelectedDates()
{
_startCalendar.SelectedDatesChanged -= OnStartCalendar_SelectedDatesChanged;
_endCalendar.SelectedDatesChanged -= OnEndCalendar_SelectedDatesChanged;
_startCalendar.SelectedDates.Clear();
SetIsHighlightFalse(_startCalendarDayButtons);
_endCalendar.SelectedDates.Clear();
SetIsHighlightFalse(_endCalendarDayButtons);
_startCalendar.SelectedDatesChanged += OnStartCalendar_SelectedDatesChanged;
_endCalendar.SelectedDatesChanged += OnEndCalendar_SelectedDatesChanged;
}
private void DateRangePicker_Loaded(object sender, RoutedEventArgs e)
{
if (_textBoxStart != null && StartDate.HasValue)
_textBoxStart.Text = StartDate.Value.ToString(DateFormat);
if (_textBoxEnd != null && EndDate.HasValue)
_textBoxEnd.Text = EndDate.Value.ToString(DateFormat);
}
private void Popup_Opened(object sender, EventArgs e)
{
PopupOpened();
}
private void PopupOpened()
{
_startCalendar.SelectedDatesChanged -= OnStartCalendar_SelectedDatesChanged;
_endCalendar.SelectedDatesChanged -= OnEndCalendar_SelectedDatesChanged;
if (StartDate.HasValue)
_startDate = StartDate.Value;
if (EndDate.HasValue)
_endDate = EndDate.Value;
_clickCount = 0;
SetSelectedDates(EndDate);
_startCalendar.SelectedDatesChanged += OnStartCalendar_SelectedDatesChanged;
_endCalendar.SelectedDatesChanged += OnEndCalendar_SelectedDatesChanged;
}
private void OnEndCalendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
{
if (!_startDate.HasValue || !_endDate.HasValue)
return;
var isYearMonthBetween = IsYearMonthBetween(e.AddedDate.Value, _startDate.Value, _endDate.Value);
if (!isYearMonthBetween)
{
SetIsHighlightFalse(_endCalendarDayButtons);
}
else
{
SetIsHighlightFalse(_endCalendarDayButtons);
SetSelectedDates();
}
}
private void OnStartCalendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
{
if (!_startDate.HasValue || !_endDate.HasValue)
return;
var isYearMonthBetween = IsYearMonthBetween(e.AddedDate.Value, _startDate.Value, _endDate.Value);
if (!isYearMonthBetween)
{
SetIsHighlightFalse(_startCalendarDayButtons);
}
else
{
SetIsHighlightFalse(_startCalendarDayButtons);
SetSelectedDates();
}
}
private void OnStartCalendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
if (_isHandlingSelectionChange)
return;
_isHandlingSelectionChange = true;
try
{
if (e.AddedItems.Count > 0)
{
var dateTime = Convert.ToDateTime(e.AddedItems[0]);
_endCalendar.SelectedDates.Clear();
ResetDate(dateTime);
}
SetSelectedDates();
}
finally
{
_isHandlingSelectionChange = false;
}
}
private void OnEndCalendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
if (_isHandlingSelectionChange)
return;
_isHandlingSelectionChange = true;
try
{
if (e.AddedItems.Count > 0)
{
var dateTime = Convert.ToDateTime(e.AddedItems[0]);
_startCalendar.SelectedDates.Clear();
ResetDate(dateTime);
}
SetSelectedDates();
}
finally
{
_isHandlingSelectionChange = false;
}
}
private void OnCalendar_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (Mouse.Captured is CalendarItem)
{
_clickCount++;
Mouse.Capture(null);
}
}
private void OnWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
if (_popup != null && _popup.IsOpen)
_popup.IsOpen = false;
}
private void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (_popup != null && !_popup.IsOpen)
_popup.IsOpen = true;
}
private void ResetDate(DateTime? dateTime)
{
if (_startDate.HasValue && _endDate.HasValue)
{
_startDate = Convert.ToDateTime(dateTime);
_endDate = null;
if (_startCalendarDayButtons != null)
{
var startDays = _startCalendarDayButtons.Where(x => DatePickerHelper.GetIsHighlight(x));
foreach (var day in startDays) DatePickerHelper.SetIsHighlight(day, false);
}
if (_endCalendarDayButtons != null)
{
var endDays = _endCalendarDayButtons.Where(x => DatePickerHelper.GetIsHighlight(x));
foreach (var day in endDays) DatePickerHelper.SetIsHighlight(day, false);
}
}
else
{
if (!_startDate.HasValue)
_startDate = Convert.ToDateTime(dateTime);
else
_endDate = Convert.ToDateTime(dateTime);
}
}
private void SetSelectedDates(DateTime? endDate = null)
{
if (_startDate.HasValue && _endDate.HasValue)
{
if (DateTime.Compare(_startDate.Value, _endDate.Value) > 0)
{
var temp = _startDate.Value;
_startDate = _endDate.Value;
_endDate = temp;
}
_startCalendar.SelectedDates.Clear();
_endCalendar.SelectedDates.Clear();
var eDate = _endDate;
if (endDate.HasValue)
eDate = endDate.Value;
for (var date = _startDate.Value; date < eDate.Value.AddDays(1); date = date.AddDays(1))
{
if (date.Date <= eDate.Value.Date
&&
!_startCalendar.SelectedDates.Contains(date.Date)
&&
date.Date <= _startCalendar.DisplayDateEnd.Value.Date)
{
_startCalendar.SelectedDates.Add(date);
if (date.Date == _startDate.Value.Date || date.Date >= eDate.Value.Date)
continue;
if (_startCalendarDayButtons != null)
{
var day = _startCalendarDayButtons.FirstOrDefault(x =>
x.DataContext is DateTime buttonDate && buttonDate.Date == date.Date);
if (day != null)
DatePickerHelper.SetIsHighlight(day, true);
}
}
if (date.Date >= _endCalendar.DisplayDateStart.Value.Date &&
!_endCalendar.SelectedDates.Contains(date.Date))
if (date.Date >= _startDate.Value.Date
&&
!_endCalendar.SelectedDates.Contains(date)
&&
date.Date >= _endCalendar.DisplayDateStart.Value.Date)
{
_endCalendar.SelectedDates.Add(date);
if (date.Date == eDate.Value.Date || date.Date <= _startDate.Value.Date)
continue;
if (_endCalendarDayButtons != null)
{
var day = _endCalendarDayButtons.FirstOrDefault(x =>
x.DataContext is DateTime buttonDate && buttonDate.Date == date.Date);
if (day != null)
DatePickerHelper.SetIsHighlight(day, true);
}
}
}
if (_clickCount == 2)
{
_popup.IsOpen = false;
StartDate = _startDate;
EndDate = _endDate;
_textBoxStart.Text = _startDate.Value.ToString(DateFormat);
_textBoxEnd.Text = _endDate.Value.ToString(DateFormat);
}
}
}
private void SetIsHighlightFalse(IEnumerable<CalendarDayButton> calendarDayButtons)
{
if (calendarDayButtons == null)
return;
var days = calendarDayButtons.Where(x => DatePickerHelper.GetIsHighlight(x));
foreach (var day in days)
DatePickerHelper.SetIsHighlight(day, false);
}
private bool IsYearMonthBetween(DateTime dateToCheck, DateTime startDate, DateTime endDate)
{
return dateToCheck.Year == startDate.Year && dateToCheck.Month >= startDate.Month &&
dateToCheck.Year == endDate.Year && dateToCheck.Month <= endDate.Month;
}
private IEnumerable<CalendarDayButton> GetCalendarDayButtons(DependencyObject parent)
{
if (parent == null) yieldbreak;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is CalendarDayButton dayButton)
yieldreturn dayButton;
foreach (var result in GetCalendarDayButtons(child))
yield return result;
}
}
}
}
Popup
包含了一个自定义的 Panel
控件,里面放置了两个 Calendar
控件,用于选择日期区间。<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Controls"
xmlns:helpers="clr-namespace:WPFDevelopers.Helpers">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Basic/ControlBasic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style
x:Key="WD.NoShadowCalendarItemStyle"
BasedOn="{StaticResource WD.ControlBasicStyle}"
TargetType="{x:Type CalendarItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CalendarItem}">
<ControlTemplate.Resources>
<DataTemplate x:Key="{x:Static CalendarItem.DayTitleTemplateResourceKey}">
<StackPanel>
<TextBlock
Margin="0,6"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource WD.PrimaryTextSolidColorBrush}"
Text="{Binding}" />
<Rectangle
Height="1"
VerticalAlignment="Bottom"
Fill="{DynamicResource WD.BaseSolidColorBrush}" />
</StackPanel>
</DataTemplate>
</ControlTemplate.Resources>
<controls:SmallPanel x:Name="PART_Root" Margin="{TemplateBinding Margin}">
<Border
Background="{DynamicResource WD.BackgroundSolidColorBrush}"
BorderBrush="{DynamicResource WD.BaseSolidColorBrush}"
BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=Calendar}}"
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource AncestorType=Calendar}}"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Grid Margin="0,20,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
x:Name="PART_PreviousButton"
Grid.Row="0"
Grid.Column="0"
Width="28"
Height="20"
HorizontalAlignment="Left"
Focusable="False"
Template="{StaticResource WD.PreviousButtonTemplate}" />
<Button
x:Name="PART_HeaderButton"
Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Focusable="False"
FontSize="14"
Template="{StaticResource WD.HeaderButtonTemplate}" />
<Button
x:Name="PART_NextButton"
Grid.Row="0"
Grid.Column="2"
Width="28"
Height="20"
HorizontalAlignment="Right"
Focusable="False"
Template="{StaticResource WD.NextButtonTemplate}" />
<Grid
x:Name="PART_MonthView"
Grid.Row="1"
Grid.ColumnSpan="3"
Margin="6,10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="Visible">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
<Grid
x:Name="PART_YearView"
Grid.Row="1"
Grid.ColumnSpan="3"
Margin="6,-3,7,6"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="Hidden">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</controls:SmallPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding DisplayMode, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Calendar}}}" Value="Year">
<Setter TargetName="PART_MonthView" Property="Visibility" Value="Collapsed" />
<Setter TargetName="PART_YearView" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DisplayMode, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Calendar}}}" Value="Decade">
<Setter TargetName="PART_MonthView" Property="Visibility" Value="Collapsed" />
<Setter TargetName="PART_YearView" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="WD.DateRangePicker" TargetType="{x:Type controls:DateRangePicker}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
<Setter Property="Padding" Value="{StaticResource WD.DefaultPadding}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:DateRangePicker}">
<ControlTemplate.Resources>
<Storyboard x:Key="OpenStoryboard">
<DoubleAnimation
EasingFunction="{StaticResource WD.ExponentialEaseOut}"
Storyboard.TargetName="PART_DropDown"
Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
To="1"
Duration="00:00:.2" />
</Storyboard>
<Storyboard x:Key="CloseStoryboard">
<DoubleAnimation
EasingFunction="{StaticResource WD.ExponentialEaseOut}"
Storyboard.TargetName="PART_DropDown"
Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
To="0"
Duration="00:00:.2" />
</Storyboard>
</ControlTemplate.Resources>
<controls:SmallPanel SnapsToDevicePixels="True">
<Border
Name="PART_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
SnapsToDevicePixels="True" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<controls:PathIcon
Margin="15,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Kind="Date" />
<DatePickerTextBox
x:Name="PART_TextBoxStart"
Grid.Column="1"
Width="Auto"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
helpers:DatePickerHelper.Watermark="{Binding StartWatermark, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Background="Transparent"
Focusable="True"
Foreground="{TemplateBinding Foreground}"
SelectionBrush="{DynamicResource WD.WindowBorderBrushSolidColorBrush}" />
<controls:PathIcon
Grid.Column="2"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Kind="DateRangeRight" />
<DatePickerTextBox
x:Name="PART_TextBoxEnd"
Grid.Column="3"
Width="Auto"
Margin="10,0,0,0"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
helpers:DatePickerHelper.Watermark="{Binding EndWatermark, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Background="Transparent"
Focusable="True"
Foreground="{TemplateBinding Foreground}"
SelectionBrush="{DynamicResource WD.WindowBorderBrushSolidColorBrush}" />
</Grid>
<Popup
x:Name="PART_Popup"
MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
AllowsTransparency="True"
Focusable="False"
Placement="Bottom"
StaysOpen="False"
VerticalOffset="2">
<controls:SmallPanel
x:Name="PART_DropDown"
MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
Margin="24,2,24,24"
RenderTransformOrigin=".5,0"
SnapsToDevicePixels="True">
<controls:SmallPanel.RenderTransform>
<ScaleTransform ScaleY="0" />
</controls:SmallPanel.RenderTransform>
<Border
Name="PART_DropDownBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
Effect="{StaticResource WD.PopupShadowDepth}"
SnapsToDevicePixels="True"
UseLayoutRounding="True" />
<StackPanel Orientation="Horizontal">
<Calendar
x:Name="PART_StartCalendar"
Margin="2,0,0,0"
BorderThickness="0"
CalendarItemStyle="{StaticResource WD.NoShadowCalendarItemStyle}"
SelectionMode="MultipleRange" />
<Calendar
x:Name="PART_EndCalendar"
Margin="0,0,2,0"
BorderThickness="0"
CalendarItemStyle="{StaticResource WD.NoShadowCalendarItemStyle}"
SelectionMode="MultipleRange" />
</StackPanel>
</controls:SmallPanel>
</Popup>
</controls:SmallPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="PART_Popup" Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="BeginStoryboardOpenStoryboard" Storyboard="{StaticResource OpenStoryboard}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginStoryboardOpenStoryboard" />
</Trigger.ExitActions>
</Trigger>
<Trigger SourceName="PART_Popup" Property="IsOpen" Value="False">
<Trigger.EnterActions>
<BeginStoryboard x:Name="BeginStoryboardCloseStoryboard" Storyboard="{StaticResource CloseStoryboard}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="BeginStoryboardCloseStoryboard" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource WD.DateRangePicker}" TargetType="{x:Type controls:DateRangePicker}" />
</ResourceDictionary>
示例引入 WPFDevelopers
的 Nuget
正式包
<UserControl
x:Class="WPFDevelopers.Samples.ExampleViews.DateRangePickerExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Samples.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UniformGrid wd:PanelHelper.Spacing="4" Columns="2">
<wd:DateRangePicker
Width="300"
Height="38"
VerticalAlignment="Top"
EndWatermark="结束日期"
StartWatermark="开始日期" />
<wd:DateRangePicker
Width="300"
Height="38"
VerticalAlignment="Top"
wd:ElementHelper.CornerRadius="3"
EndWatermark="EndDate"
StartWatermark="StartDate" />
<WrapPanel wd:PanelHelper.Spacing="3">
<wd:DateRangePicker
x:Name="MyDateRangePicker"
Width="240"
Height="38"
VerticalAlignment="Top"
EndDate="{Binding EndDate, RelativeSource={RelativeSource AncestorType=local:DateRangePickerExample}}"
EndWatermark="结束日期"
StartDate="{Binding StartDate, RelativeSource={RelativeSource AncestorType=local:DateRangePickerExample}}"
StartWatermark="开始日期" />
<Button
Width="60"
HorizontalAlignment="Center"
Click="Button_Click"
Content="获取"
Style="{StaticResource WD.PrimaryButton}" />
</WrapPanel>
</UniformGrid>
</UserControl>
using System.Windows;
using System;
using System.Windows.Controls;
namespaceWPFDevelopers.Samples.ExampleViews
{
/// <summary>
/// DateRangePickerExample.xaml 的交互逻辑
/// </summary>
publicpartialclassDateRangePickerExample : UserControl
{
public DateTime? StartDate
{
get { return (DateTime)GetValue(StartDateProperty); }
set { SetValue(StartDateProperty, value); }
}
publicstaticreadonly DependencyProperty StartDateProperty =
DependencyProperty.Register("StartDate", typeof(DateTime?), typeof(DateRangePickerExample), new PropertyMetadata(null));
public DateTime? EndDate
{
get { return (DateTime)GetValue(EndDateProperty); }
set { SetValue(EndDateProperty, value); }
}
publicstaticreadonly DependencyProperty EndDateProperty =
DependencyProperty.Register("EndDate", typeof(DateTime?), typeof(DateRangePickerExample), new PropertyMetadata(null));
public DateRangePickerExample()
{
InitializeComponent();
StartDate = DateTime.Now.AddDays(1);
EndDate = StartDate.Value.AddDays(10);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
WPFDevelopers.Controls.MessageBox.Show($"开始时间:{MyDateRangePicker.StartDate} \r结束时间:{MyDateRangePicker.EndDate}", "获取日期");
}
}
}
GitHub 源码地址[3]
Gitee 源码地址[4]
参考资料
[1]
原文链接: https://github.com/WPFDevelopersOrg/WPFDevelopers
[2]
码云链接: https://gitee.com/WPFDevelopersOrg/WPFDevelopers
[3]
GitHub 源码地址: https://github.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Shared/Controls/DateRangePicker/DateRangePicker.cs
[4]
Gitee 源码地址: https://gitee.com/WPFDevelopersOrg/WPFDevelopers/tree/dev/src/WPFDevelopers.Shared/Controls/DateRangePicker/DateRangePicker.cs