欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF 简易日期控件 魔改ListBox

程序员文章站 2022-05-01 20:43:59
先上截图 修正: 应该将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 ......

先上截图

WPF 简易日期控件 魔改ListBox

 

修正:

应该将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>