先上截图

 

修正:

应该将settime方法修改为,行号为207行开始修改

     var nk = day_of_week(year, month, 1);
            if (nk == 0)
                nk = 7;
            for (var i = 0; i < nk-1; i++)
            {
                time.add(new basetime());
            }

 

 

源代码

整体过程是魔改listbox,整体是放在用户控件中,关于日期的计算是来自这位博友的

抱歉没有过多注释,原本也只是一时兴起,多多见谅

 

1 修改listbox的模板

将listbox的模板设为一个三层结果的模板

1 左右日期调整

2 星期显示

3 当月显示

其中左右日期调整为按钮,并实现是命令。星期显示使用数据提供者进行枚举绑定,当月显示即为普通的lisbox面板

 

 

2修改listitem

大部分的改动都是这里的,一开始想用子项样式选择器或者子项模板选择器来进行动态改变,不过发现不太适合,后来改为数据触发器动态改变底层矩形颜色。

整体是一个checkbox,利用其属性来完成选择过程。

选择过程是使用栈,保证只有最多两个选择项

 

 

3添加依赖属性

保证某些数据能够被获取,不过没有实现路由事件。所以本控件依旧是一个玩具,不能被直接使用于真实项目

 

代码中命名不太规范,请凑乎看。

xaml代码

<usercontrol x:class="日期控件.uc_datetime"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
xmlns:local="clr-namespace:日期控件"
xmlns:sys="clr-namespace:system;assembly=mscorlib"
mc:ignorable="d" 
d:designheight="450" d:designwidth="800" x:name="uc_datetime_"    >
<usercontrol.resources>
<local:autowidth x:key="autowidth"/>
<objectdataprovider x:key="week" objecttype="{x:type sys:enum}"  methodname="getvalues"   >
<objectdataprovider.methodparameters>
<x:type  typename="local:basedatetime"/>
</objectdataprovider.methodparameters>
</objectdataprovider>
<local:basedate x:key="time"   />
<local:baseselectstyle x:key="select"/>
<local:datetimecombo x:key="datetime"/>
<local:selectdatetimelist x:key="sdt"/>
<style targettype="local:uc_datetime">
<setter property="selectdatetime"  value="{binding source={staticresource time},  path=selectdatetimelist}"/>
</style>
</usercontrol.resources>
<grid x:name="gridpanel" tag="{binding elementname=lb,path=tag}">
<listbox  margin="0,0,0,10" x:name="lb"    selectionmode="single"     borderbrush="silver" borderthickness="0,1,0,1"  >
<listbox.itemssource>
<multibinding  converter="{staticresource datetime}">
<binding elementname="uc_datetime_" path="setyear"/>
<binding elementname="uc_datetime_" path="setmonth"/>
<binding   source="{staticresource time}"/>
</multibinding>
</listbox.itemssource>
<listbox.itemspanel>
<itemspaneltemplate>
<wrappanel    width="{binding relativesource={relativesource mode=findancestor,ancestorlevel=1,ancestortype=scrollcontentpresenter}, path=actualwidth}"   itemwidth="{binding relativesource={relativesource mode=self}, path=width,converter={staticresource autowidth}}" />
</itemspaneltemplate>
</listbox.itemspanel>
<listbox.template>
<controltemplate targettype="listbox">
<grid>
<grid.rowdefinitions>
<rowdefinition height="auto"/>
<rowdefinition height="auto"/>
<rowdefinition height="*"/>
</grid.rowdefinitions>
<grid grid.row="0">
<grid.columndefinitions>
<columndefinition width="*"/>
<columndefinition width="*" />
<columndefinition width="*"/>
</grid.columndefinitions>
<button x:name="previousbutton"   grid.column="0" background="transparent" command="{binding path=previousclick,  source={staticresource time}}"   borderthickness="0" content="&lt;" horizontalcontentalignment="left" >
<button.commandparameter>
<multibinding converter="{staticresource datetime}" converterparameter="1">
<binding elementname="year" mode="twoway"/>
<binding elementname="month" mode="twoway"/>
</multibinding>
</button.commandparameter>
</button>
<textblock grid.column="1" horizontalalignment="center"  >
<run x:name="year" text="{binding elementname=uc_datetime_,path=setyear}"/>
<run text="年"/>
<run x:name="month" text="{binding elementname=uc_datetime_,path=setmonth}"/>
<run text="月"/>
</textblock>
<button x:name="nextbutton" grid.column="2"  command="{binding source={staticresource time}, path=nextclick}"  background="transparent" borderthickness="0" content="&gt;" horizontalcontentalignment="right">
<button.commandparameter>
<multibinding converter="{staticresource datetime}" converterparameter="1">
<binding elementname="year" mode="twoway"/>
<binding elementname="month" mode="twoway"/>
</multibinding>
</button.commandparameter>
</button>
</grid>
<itemscontrol   grid.row="1"    itemssource="{binding source={staticresource week}}" >
<itemscontrol.itemspanel>
<itemspaneltemplate>
<wrappanel   width="{binding relativesource={relativesource mode=findancestor,ancestorlevel=1,ancestortype=itemscontrol}, path=actualwidth}"   itemwidth="{binding relativesource={relativesource mode=self}, path=width,converter={staticresource autowidth}}" />
</itemspaneltemplate>
</itemscontrol.itemspanel>
<itemscontrol.itemtemplate>
<datatemplate>
<textblock text="{binding }" horizontalalignment="center"/>
</datatemplate>
</itemscontrol.itemtemplate>
</itemscontrol>
<border grid.row="2" margin="{templatebinding margin}"   borderthickness="{templatebinding borderthickness}"  borderbrush="{templatebinding borderbrush}">
<scrollviewer padding="3" verticalscrollbarvisibility="hidden">
<itemspresenter snapstodevicepixels="{templatebinding snapstodevicepixels}"/>
</scrollviewer>
</border>
</grid>
</controltemplate>
</listbox.template>
<listbox.itemcontainerstyle>
<style targettype="listboxitem">
<setter property="template">
<setter.value>
<controltemplate targettype="listboxitem">
<controltemplate.resources>
<solidcolorbrush color="{binding elementname=uc_datetime_, path=selectitemlistbackground}" x:key="listselectedcolor"/>
<solidcolorbrush color="{binding elementname=uc_datetime_, path=selectitembackground}" x:key="selectedcolor"/>
</controltemplate.resources>
<checkbox  x:name="cb"  ischecked="{binding itemisselected,mode=twoway}"   clickmode="release" command="{binding  click,source={staticresource time }}"   commandparameter="{ binding relativesource={relativesource mode=self}, path=datacontext}"  >
<checkbox.template>
<controltemplate>
<grid>
<grid>
<grid.columndefinitions>
<columndefinition/>
<columndefinition/>
</grid.columndefinitions>
<rectangle margin="0,2,0,2" grid.column="0" fill="{binding elementname=uc_datetime_, path=selectitemlistbackground }" x:name="leftbackground"  visibility="collapsed"/>
<rectangle margin="0,2,0,2" grid.column="1" fill="{binding elementname=uc_datetime_, path=selectitemlistbackground }" x:name="rightbackground"  visibility="collapsed"/>
<ellipse grid.columnspan="2" x:name="bd" width="{binding elementname=uc_datetime_, path=backgroundellipessize}" height="{binding elementname=uc_datetime_, path=backgroundellipessize}"/>
</grid>
<textblock   x:name="txt"    text="{binding time}" fontsize="{binding elementname=uc_datetime_, path=datetimefontsize}"  horizontalalignment="center" verticalalignment="center"/>
</grid>
<controltemplate.triggers>
<datatrigger binding="{binding time}" value="0">
<setter property="isenabled" value="false"/>
<setter property="text" targetname="txt" value=""/>
</datatrigger>
<multidatatrigger >
<multidatatrigger.conditions>
<condition binding="{binding itemisselected}" value="true"/>
</multidatatrigger.conditions>
<setter property="fill" targetname="bd" value="{binding elementname=uc_datetime_, path=selectitembackground }" />
</multidatatrigger>
<datatrigger binding="{binding itemisselected}" value="false">
<setter property="fill" value="white"   targetname="bd"/>
</datatrigger>
<datatrigger binding="{binding backgroundshowmode}" value="none">
<setter targetname="leftbackground" property="visibility" value="collapsed"/>
<setter targetname="rightbackground" property="visibility" value="collapsed"/>
</datatrigger>
<datatrigger binding="{binding backgroundshowmode}" value="right">
<setter targetname="leftbackground" property="visibility" value="collapsed"/>
<setter targetname="rightbackground" property="visibility" value="visible"/>
</datatrigger>
<datatrigger binding="{binding backgroundshowmode}" value="left">
<setter targetname="leftbackground" property="visibility" value="visible"/>
<setter targetname="rightbackground" property="visibility" value="collapsed"/>
</datatrigger>
<datatrigger binding="{binding backgroundshowmode}" value="both">
<setter targetname="leftbackground" property="visibility" value="visible"/>
<setter targetname="rightbackground" property="visibility" value="visible"/>
<setter property="visibility" targetname="bd"  value="hidden"/>
</datatrigger>
</controltemplate.triggers>
</controltemplate>
</checkbox.template>
</checkbox>
</controltemplate>
</setter.value>
</setter>
</style>
</listbox.itemcontainerstyle>
</listbox>
</grid>
</usercontrol>

 

xaml.cs页面

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using system.windows;
using system.windows.controls;
using system.windows.data;
using system.windows.documents;
using system.windows.input;
using system.windows.media;
using system.windows.media.imaging;
using system.windows.navigation;
using system.windows.shapes;
namespace 日期控件
{
/// <summary>
/// uc_datetime.xaml 的交互逻辑
/// </summary>
public partial class uc_datetime : usercontrol
{
public uc_datetime()
{
initializecomponent();
}
public static readonly dependencyproperty datetimefontsizeproperty = dependencyproperty.register("datetimefontsize", typeof(int), typeof(uc_datetime), new propertymetadata(27));
public int datetimefontsize
{
get => convert.toint32(getvalue(datacontextproperty));
set => setvalue(datacontextproperty, value);
}
public static readonly dependencyproperty backgroundellipesstretchproperty = dependencyproperty.register("backgroundellipesstretch", typeof(stretch), typeof(uc_datetime), new propertymetadata(stretch.uniformtofill));
public stretch backgroundellipesstretch
{
get => (stretch)getvalue(backgroundellipesstretchproperty);
set => setvalue(backgroundellipesstretchproperty, value);
}
public static readonly dependencyproperty selectitembackgroundproperty = dependencyproperty.register("selectitembackground", typeof(solidcolorbrush), typeof(uc_datetime), new propertymetadata(new solidcolorbrush(colors.red)));
public solidcolorbrush selectitembackground
{
get => (solidcolorbrush)getvalue(selectitembackgroundproperty);
set => setvalue(selectitembackgroundproperty, value);
}
public static readonly dependencyproperty backgroundellipessizeproperty = dependencyproperty.register("backgroundellipessize", typeof(double), typeof(uc_datetime), new propertymetadata(25.0));
public double backgroundellipessize
{
get => convert.todouble(getvalue(backgroundellipessizeproperty));
set => setvalue(backgroundellipessizeproperty, value);
}
public static readonly dependencyproperty selectitemlistbackgroundproperty = dependencyproperty.register("selectitemlistbackground", typeof(solidcolorbrush), typeof(uc_datetime), new propertymetadata(new solidcolorbrush(colors.red)));
public solidcolorbrush selectitemlistbackground
{
get => (solidcolorbrush)getvalue(selectitemlistbackgroundproperty);
set => setvalue(selectitemlistbackgroundproperty, value);
}
public static readonly dependencyproperty backgroundshowmodeproperty = dependencyproperty.register("backgroundshowmode", typeof(showmode), typeof(uc_datetime), new propertymetadata(showmode.none));
public showmode backgroundshowmode
{
get => (showmode)getvalue(backgroundshowmodeproperty);
set => setvalue(backgroundshowmodeproperty, value);
}
public static readonly dependencyproperty setyearproperty = dependencyproperty.register("setyear", typeof(int), typeof(uc_datetime), new propertymetadata(datetime.now.year));
public int setyear
{
get => convert.toint32(getvalue(setyearproperty));
set => setvalue(setyearproperty, value);
}
public static readonly dependencyproperty setmonthproperty = dependencyproperty.register("setmonth", typeof(int), typeof(uc_datetime), new propertymetadata(datetime.now.month));
public int setmonth
{
get => convert.toint32(getvalue(setmonthproperty));
set => setvalue(setmonthproperty, value);
}
//datetime.parse(datetime.now.tostring("yyyy-mm-dd"))
public static readonly dependencyproperty selectdatetimepropery = dependencyproperty.register("selectdatetime", typeof(list<datetime>), typeof(uc_datetime), new propertymetadata( new list<datetime>() { }));
public list<datetime> selectdatetime
{
get => (list<datetime>)getvalue(selectdatetimepropery);
set => setvalue(selectdatetimepropery, value);
}
}
}

 

 

最重要的视觉模型类

using system;
using system.collections.generic;
using system.collections.objectmodel;
using system.componentmodel;
using system.linq;
using system.runtime.compilerservices;
using system.text;
using system.threading.tasks;
using system.windows.controls;
using system.windows.documents;
using system.windows.input;
namespace 日期控件
{
public class clickcommand : icommand
{
public event eventhandler canexecutechanged;
public bool canexecute(object parameter)
{
return true;
}
public void execute(object parameter)
{
if (parameter != null)
action?.invoke(parameter);
else
noparameteraction?.invoke();
}
private action noparameteraction;
private action<object> action;
public clickcommand(action<object> acion)
{
this.action = acion;
}
public clickcommand(action action)
{
this.noparameteraction = action;
}
}
public enum basedatetime
{
周一,
周二,
周三,
周四,
周五,
周六,
周日,
}
public enum showmode
{
left,
right,
both,
none
}
public class basetime : inotifypropertychanged
{
private int _time;
public int time { get => _time; set { _time = value; onvaluechanged(); } }
private bool _itemisselected;
public bool itemisselected { get => _itemisselected; set { _itemisselected = value; onvaluechanged(); onselecchanged?.invoke(this); } }
private bool _ischangtemplate;
public bool ischangtemplate { get => _ischangtemplate; set { _ischangtemplate = value; onvaluechanged(); } }
public event propertychangedeventhandler propertychanged;
protected void onvaluechanged([callermembername]string name = "") => propertychanged?.invoke(this, new propertychangedeventargs(name));
public action<basetime> onselecchanged;
private showmode _showmode=showmode.none;
public showmode backgroundshowmode
{
get => _showmode;
set
{
_showmode = value;
onvaluechanged();
}
}
}
public class basedate : inotifypropertychanged
{
public event propertychangedeventhandler propertychanged;
protected void onvaluechanged([callermembername]string name = "") => propertychanged?.invoke(this, new propertychangedeventargs(name));
public observablecollection<basetime> time { get; set; }
public int year { get; set; }
public int month { get; set; }
//单选 public icommand click { get => new clickcommand(new action<object>(onclick)); }
//日期调整 public icommand nextclick { get => new clickcommand(new action<object>(onnextclick)); } int monthcount = 0; private void onnextclick(object n) { updatetime(n,1); month++; monthcount++; if (month== 13) { month = 1; this.year += 1; } settime(this.year, this.month); updatetime(n); } public timespan selecttime { get;set; } private object _selectdatetimelist; public object selectdatetimelist { get => _selectdatetimelist; set { _selectdatetimelist = value; onvaluechanged(); } } public list<datetime> setselecttime() { var ls = new list<datetime>(); if (stackbasetimelist.count == 2) { if (year == 0 && month == 0) { ls.add(new datetime(year2, month2, stackbasetimelist.last().time)); ls.add(new datetime(year2, month2, stackbasetimelist.first().time)); } else { ls.add(new datetime(year, month, stackbasetimelist.last().time)); ls.add(new datetime(year, month, stackbasetimelist.first().time)); } } return ls; } private void updatetime(object n,int i=0) { var arr = n as object[]; var r1 = arr[0] as run; var r2 = arr[1] as run; if (i == 1) { year = convert.toint32(r1.text); month = convert.toint32(r2.text); return; } r1.text = year.tostring(); r2.text = month.tostring(); } public icommand previousclick { get => new clickcommand(new action<object>(onpreviousclick)); } private void onpreviousclick(object n) { updatetime(n,1); month--; monthcount--; if (month == 0) { month = 12; this.year -= 1; } settime(this.year, this.month); updatetime(n); } private void onclick(object obj) { if(obj!=null) queueupdate(obj as basetime); } private int year2, month2; public void settime(int year,int month) { year2 = year; month2 = month; time.clear(); stackbasetimelist.clear(); setnorm(); clear(); var day = days_of_month(year, month); var nk = day_of_week(year, month, 1); for (var i = 0; i < nk; i++) { time.add(new basetime()); } for (var i=0;i<day;i++) { time.add(new basetime() { time = i + 1 }); } } ////返回这个月一共有多少天 int days_of_month(int year, int month) { //存储平年每月的天数 int[] month_days = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (2 == month && datetime.isleapyear(year)) return 29; // 如果是闰年2月,返回29天 else return month_days[month - 1]; //正常返回 } int days_of_year(int year, int month, int day) { int i; int days = 0; for (i = 1; i < month; i++) { days += days_of_month(year, i); } return days + day; } //返回这一天从公元元年算起是第几天 int get_days(int year, int month, int day) { int days = days_of_year(year, month, day); int temp = year - 1; return temp * 365 + temp / 4 - temp / 100 + temp / 400 + days; } int day_of_week(int year, int month, int day) { return get_days(year, month, day) % 7; } public basedate() { stackbasetimelist = new stack<basetime>(); time = new observablecollection<basetime>(); for (var i = 0; i < 31; i++) if (i == 5) time.add(new basetime() { time = i + 1, ischangtemplate = true }); else time.add(new basetime() { time = i + 1 }); } private void setbackground() { setnorm(); var f = stackbasetimelist.first(); var l = stackbasetimelist.last(); var start = time.indexof(l); var end = time.indexof(f); if(l.time<f.time) for(var i=start;i<end+1;i++) { var item = time[i]; item.backgroundshowmode = showmode.both; if (item.time == l.time) { item.backgroundshowmode = showmode.right; } if (item.time == f.time) { item.backgroundshowmode = showmode.left; } } selectdatetimelist = setselecttime(); } private void setnorm() { foreach(var item in time) { item.backgroundshowmode = showmode.none; } } private stack<basetime> stackbasetimelist; private void clear() { foreach (var item in time) item.itemisselected = false; stackbasetimelist.clear(); }
//原先是队列,不过不对,名字没改,现在是栈表 private void queueupdate(basetime bt) { if (bt.itemisselected) { if (stackbasetimelist.count < 2) { if (stackbasetimelist.count == 1) { var l = stackbasetimelist.last(); if (bt.time > l.time) stackbasetimelist.push(bt); else { clear(); l.itemisselected = true; stackbasetimelist.push(l); } } else stackbasetimelist.push(bt); } else { var f = stackbasetimelist.first(); var l = stackbasetimelist.last(); if (bt.time > l.time) { var pop = stackbasetimelist.pop(); pop.itemisselected = false; stackbasetimelist.push(bt); } else { clear(); setnorm(); l.itemisselected = true; stackbasetimelist.push(l); } } } else { var f = stackbasetimelist.first(); var l = stackbasetimelist.last(); if (l.time == bt.time) { clear(); setnorm(); } else if (f.time == bt.time) { stackbasetimelist.pop(); } else if(bt.time<=l.time) { clear(); setnorm(); } } if (stackbasetimelist.count == 2) setbackground(); } } }

 

各种转换器

using system;
using system.collections.generic;
using system.globalization;
using system.linq;
using system.text;
using system.threading.tasks;
using system.windows.data;
namespace 日期控件
{
public class autowidth : ivalueconverter
{
public object convert(object value, type targettype, object parameter, cultureinfo culture)
{
var width = system.convert.todouble(value) / 7;
return width;
}
public object convertback(object value, type targettype, object parameter, cultureinfo culture)
{
var width = system.convert.todouble(value) * 7;
return width;
}
}
public class datetimecombo : imultivalueconverter
{
public object convert(object[] values, type targettype, object parameter, cultureinfo culture)
{
if (parameter != null)
{
return new object[2] { values[0],values[1]};
}
var t = values[2] as basedate;
t.settime((int)values[0], (int)values[1]);
return t.time;
}
public object[] convertback(object value, type[] targettypes, object parameter, cultureinfo culture)
{
throw new notimplementedexception();
}
}
public class selectdatetimelist : ivalueconverter
{
public object convert(object value, type targettype, object parameter, cultureinfo culture)
{
var bt = value as basedate;
return value;
}
public object convertback(object value, type targettype, object parameter, cultureinfo culture)
{
throw new notimplementedexception();
}
}
}

 

显示页面xaml

<window x:class="日期控件.mainwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:日期控件"
mc:ignorable="d"
title="mainwindow" height="450" width="800">
<grid>
<grid.rowdefinitions>
<rowdefinition height="auto"/>
<rowdefinition height="auto"/>
<rowdefinition height="*"/>
</grid.rowdefinitions>
<textblock text="{binding elementname=qq,path=selectdatetime[0]}"/>
<textblock grid.row="1" text="{binding elementname=qq,path=selectdatetime[1]}"/>
<local:uc_datetime  grid.row="2" margin="0,30,0,0" backgroundellipessize="35" setmonth="4"  x:name="qq" datetimefontsize="20" selectitembackground="burlywood" selectitemlistbackground="aqua" backgroundellipesstretch="none"/>
</grid>
</window>