如何实现日期范围选择器
框架支持.net4 至 .net8;
visual studio 2022;
日期范围选择器的逻辑实现
日期范围选择器在界面中允许选择开始日期和结束日期,并提供高亮显示选择的日期范围。
daterangepicker 逻辑如下
setselecteddates:设置选择的开始日期和结束日期,并在calendar中高亮显示日期。setishighlightfalse:取消日期高亮。isyearmonthbetween:日期是否在指定的开始日期和结束日期的年份和月份之间。getcalendardaybuttons:递归查找日历中的每一个日历按钮,用于进行操作如高亮或取消。
1. 设置选定的日期范围
日期范围选择器允许选择一个开始日期和一个结束日期。确保选择范围有效。如果开始日期晚于结束日期,需交换它们。以下是 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);
}
}
}
2. 取消日期高亮
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);
}
3.日期范围检查
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;
}
4.获取日历按钮
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;
}
}
daterangepicker.cs 全部代码
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;
namespace wpfdevelopers.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))]
public class daterangepicker : control
{
private const string popuptemplatename = "part_popup";
private const string startcalendartemplatename = "part_startcalendar";
private const string endcalendartemplatename = "part_endcalendar";
private const string textboxstarttemplatename = "part_textboxstart";
private const string textboxendtemplatename = "part_textboxend";
public static readonly dependencyproperty startwatermarkproperty =
dependencyproperty.register("startwatermark",
typeof(string),
typeof(daterangepicker),
new propertymetadata(string.empty));
public static readonly dependencyproperty endwatermarkproperty =
dependencyproperty.register("endwatermark",
typeof(string),
typeof(daterangepicker),
new propertymetadata(string.empty));
public static readonly dependencyproperty startdateproperty =
dependencyproperty.register("startdate", typeof(datetime?), typeof(daterangepicker),
new frameworkpropertymetadata(null,
frameworkpropertymetadataoptions.bindstwowaybydefault | frameworkpropertymetadataoptions.journal,
onstartdatechanged));
public static readonly dependencyproperty enddateproperty =
dependencyproperty.register("enddate", typeof(datetime?), typeof(daterangepicker),
new frameworkpropertymetadata(null,
frameworkpropertymetadataoptions.bindstwowaybydefault | frameworkpropertymetadataoptions.journal,
onenddatechanged));
public static readonly dependencyproperty dateformatformatproperty =
dependencyproperty.register("dateformat", typeof(string), typeof(daterangepicker),
new propertymetadata("yyy-mm-dd"));
public static readonly dependencyproperty maxdropdownheightproperty =
dependencyproperty.register("maxdropdownheight", typeof(double), typeof(daterangepicker),
new uipropertymetadata(systemparameters.primaryscreenheight / 2.5, onmaxdropdownheightchanged));
private int _clickcount;
private bool _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)));
}
public string startwatermark
{
get => (string)getvalue(startwatermarkproperty);
set => setvalue(startwatermarkproperty, value);
}
public string 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);
}
public string 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, out var 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, out var 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) 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;
}
}
}
}
日期范围选择器的样式实现
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;
namespace wpfdevelopers.samples.exampleviews
{
/// <summary>
/// daterangepickerexample.xaml 的交互逻辑
/// </summary>
public partial class daterangepickerexample : usercontrol
{
public datetime? startdate
{
get { return (datetime)getvalue(startdateproperty); }
set { setvalue(startdateproperty, value); }
}
public static readonly dependencyproperty startdateproperty =
dependencyproperty.register("startdate", typeof(datetime?), typeof(daterangepickerexample), new propertymetadata(null));
public datetime? enddate
{
get { return (datetime)getvalue(enddateproperty); }
set { setvalue(enddateproperty, value); }
}
public static readonly 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}", "获取日期");
}
}
}
效果图



到此这篇关于wpf如何实现日期范围选择器的文章就介绍到这了,更多相关wpf日期范围选择器内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论