WPF 简易日期控件 魔改ListBox
先上截图
修正2
修正:
应该将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="<" 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=">" 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>