1、自定义的DatePicker和Calendar
<!-- Calendar.xaml -->
<UserControl
x:Class="Base.View.usercontrol.Calendar"
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:local="clr-namespace:Base.View.usercontrol"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="root"
Width="290"
Height="auto"
FontFamily="Segoe UI"
mc:Ignorable="d">
<UserControl.Resources>
<!-- Header Button Style -->
<Style x:Key="HeaderButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="8" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#555" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}" CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#F0F0F0" />
</Trigger>
</Style.Triggers>
</Style>
<!-- Day Button Style -->
<Style x:Key="CalendarDayButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="8" />
<Setter Property="FontSize" Value="13" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border
Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="15">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Month Button Style -->
<Style x:Key="CalendarMonthButtonStyle" TargetType="Button" BasedOn="{StaticResource HeaderButtonStyle}">
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Padding" Value="10"/>
</Style>
</UserControl.Resources>
<Border Padding="10" Background="White">
<Grid>
<!-- Day View Grid -->
<Grid x:Name="DayViewGrid">
<StackPanel>
<!-- Header -->
<DockPanel Margin="0,0,0,12">
<Button
Click="PrevMonth_Click"
Content="<"
DockPanel.Dock="Left"
Style="{StaticResource HeaderButtonStyle}" />
<Button
Click="NextMonth_Click"
Content=">"
DockPanel.Dock="Right"
Style="{StaticResource HeaderButtonStyle}" />
<Button x:Name="MonthYearHeader"
Style="{StaticResource HeaderButtonStyle}"
HorizontalContentAlignment="Center"
Click="MonthYearHeader_Click">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
FontWeight="SemiBold"
Foreground="#333"
Text="{Binding CurrentMonthText, ElementName=root}" />
</Button>
</DockPanel>
<!-- Week headers -->
<UniformGrid Margin="0,0,0,8" Columns="7">
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="日" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="一" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="二" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="三" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="四" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="五" TextAlignment="Center" />
<TextBlock FontWeight="SemiBold" Foreground="#888" Text="六" TextAlignment="Center" />
</UniformGrid>
<!-- Dates -->
<UniformGrid x:Name="DayGrid" Columns="7" Rows="6"/>
</StackPanel>
</Grid>
<!-- Month View Grid -->
<Grid x:Name="MonthViewGrid" Visibility="Collapsed">
<StackPanel>
<!-- Header -->
<DockPanel Margin="0,0,0,12">
<Button
Click="PrevYear_Click"
Content="<"
DockPanel.Dock="Left"
Style="{StaticResource HeaderButtonStyle}" />
<Button
Click="NextYear_Click"
Content=">"
DockPanel.Dock="Right"
Style="{StaticResource HeaderButtonStyle}" />
<TextBlock x:Name="YearTextBlock"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"
FontWeight="SemiBold"
Foreground="#333" />
</DockPanel>
<!-- Months -->
<UniformGrid x:Name="MonthsGrid" Columns="3" Rows="4"/>
</StackPanel>
</Grid>
</Grid>
</Border>
</UserControl>
//Calendar.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 Base.View.usercontrol
{
/// <summary>
/// Calendar.xaml 的交互逻辑
/// </summary>
public partial class Calendar : UserControl, System.ComponentModel.INotifyPropertyChanged
{
public static readonly RoutedEvent DateSelectedEvent = EventManager.RegisterRoutedEvent(
name: "DateSelected",
routingStrategy: RoutingStrategy.Bubble,
handlerType: typeof(RoutedEventHandler),
ownerType: typeof(Calendar));
public event RoutedEventHandler DateSelected
{
add { AddHandler(DateSelectedEvent, value); }
remove { RemoveHandler(DateSelectedEvent, value); }
}
public Calendar()
{
InitializeComponent();
DisplayMonth(DateTime.Today);
}
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(Calendar),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public DateTime? SelectedDate
{
get => (DateTime?)GetValue(SelectedDateProperty);
set
{
SetValue(SelectedDateProperty, value);
DisplayMonth(value ?? DateTime.Today);
}
}
private DateTime currentMonth;
private DateTime _currentViewDate;
public string CurrentMonthText => currentMonth.ToString("yyyy 年 MM 月");
private void DisplayMonth(DateTime date)
{
currentMonth = new DateTime(date.Year, date.Month, 1);
DayGrid.Children.Clear();
OnPropertyChanged(nameof(CurrentMonthText));
int firstDayOfWeek = (int)currentMonth.DayOfWeek;
DateTime startDate = currentMonth.AddDays(-firstDayOfWeek);
for (int i = 0; i < 42; i++)
{
DateTime displayDate = startDate.AddDays(i);
bool isCurrentMonth = displayDate.Month == currentMonth.Month;
var btn = new Button
{
Content = displayDate.Day.ToString(),
Margin = new Thickness(2),
Tag = displayDate,
Style = (Style)FindResource("CalendarDayButtonStyle")
};
if (!isCurrentMonth)
{
btn.Foreground = Brushes.LightGray;
}
if (displayDate.Date == DateTime.Today.Date && isCurrentMonth)
{
btn.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#0078D7"));
btn.BorderThickness = new Thickness(1);
}
if (SelectedDate.HasValue && SelectedDate.Value.Date == displayDate.Date)
{
btn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#0078D7"));
btn.Foreground = Brushes.White;
}
btn.MouseEnter += (s, e) =>
{
if (SelectedDate.HasValue && SelectedDate.Value.Date == displayDate.Date) return;
((Button)s).Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F0F0F0"));
};
btn.MouseLeave += (s, e) =>
{
if (SelectedDate.HasValue && SelectedDate.Value.Date == displayDate.Date) return;
((Button)s).Background = Brushes.Transparent;
};
btn.Click += (s, e) =>
{
var clickedDate = (DateTime)((Button)s).Tag;
if (clickedDate.Month == currentMonth.Month)
{
SelectedDate = clickedDate;
RaiseEvent(new RoutedEventArgs(DateSelectedEvent, this));
}
else
{
DisplayMonth(clickedDate);
}
};
DayGrid.Children.Add(btn);
}
}
private void DisplayMonthView()
{
DayViewGrid.Visibility = Visibility.Collapsed;
MonthViewGrid.Visibility = Visibility.Visible;
YearTextBlock.Text = _currentViewDate.Year.ToString();
MonthsGrid.Children.Clear();
for (int i = 1; i <= 12; i++)
{
var btn = new Button
{
Content = $"{i}月",
Tag = i,
Style = (Style)FindResource("CalendarMonthButtonStyle")
};
if (i == currentMonth.Month && _currentViewDate.Year == currentMonth.Year)
{
btn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#0078D7"));
btn.Foreground = Brushes.White;
}
btn.Click += (s, e) =>
{
var selectedMonth = (int)((Button)s).Tag;
DisplayMonth(new DateTime(_currentViewDate.Year, selectedMonth, 1));
MonthViewGrid.Visibility = Visibility.Collapsed;
DayViewGrid.Visibility = Visibility.Visible;
};
MonthsGrid.Children.Add(btn);
}
}
private void PrevMonth_Click(object sender, RoutedEventArgs e)
{
DisplayMonth(currentMonth.AddMonths(-1));
}
private void NextMonth_Click(object sender, RoutedEventArgs e)
{
DisplayMonth(currentMonth.AddMonths(1));
}
private void MonthYearHeader_Click(object sender, RoutedEventArgs e)
{
_currentViewDate = new DateTime(currentMonth.Year, currentMonth.Month, 1);
DisplayMonthView();
}
private void PrevYear_Click(object sender, RoutedEventArgs e)
{
_currentViewDate = _currentViewDate.AddYears(-1);
DisplayMonthView();
}
private void NextYear_Click(object sender, RoutedEventArgs e)
{
_currentViewDate = _currentViewDate.AddYears(1);
DisplayMonthView();
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
}
}
}
<!-- DatePicker.xaml -->
<UserControl
x:Class="Base.View.usercontrol.DatePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="clr-namespace:Base.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Base.View.usercontrol"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="root"
Width="{Binding ControlWidth, RelativeSource={RelativeSource Self}}"
Height="{Binding ControlHeight, RelativeSource={RelativeSource Self}}"
d:DesignHeight="200"
d:DesignWidth="200"
mc:Ignorable="d">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converter:BoolToCollapsedVisibilityConverter x:Key="BoolToCollapsedVisibilityConverter" />
<converter:ClearButtonVisibilityConverter x:Key="ClearButtonVisibilityConverter" />
<Style x:Key="TransparentButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<!-- 可选显示的文本框 -->
<TextBox
x:Name="DateTextBox"
Grid.Column="0"
Margin="0"
VerticalContentAlignment="Center"
IsReadOnly="True"
Text="{Binding SelectedDate, ElementName=root, StringFormat='yyyy-MM-dd'}"
Visibility="{Binding ShowTextBox, ElementName=root, Converter={StaticResource BoolToCollapsedVisibilityConverter}}" />
<!-- Clear Button -->
<Button
Grid.Column="1"
Click="ClearDate_Click"
Style="{StaticResource TransparentButtonStyle}"
VerticalAlignment="Center" Margin="2,0">
<Button.Visibility>
<MultiBinding Converter="{StaticResource ClearButtonVisibilityConverter}">
<Binding Path="ShowClearButton" ElementName="root" />
<Binding Path="SelectedDate" ElementName="root" />
</MultiBinding>
</Button.Visibility>
<Path
Width="10"
Height="10"
Data="M0,0 L1,1 M0,1 L1,0"
Stroke="Gray"
StrokeThickness="1.5" />
</Button>
<!-- 图标按钮 -->
<Button
Grid.Column="2"
Click="ToggleCalendar_Click"
Style="{StaticResource TransparentButtonStyle}">
<Image
Width="{Binding IconWidth, ElementName=root}"
Height="{Binding IconHeight, ElementName=root}"
RenderOptions.BitmapScalingMode="HighQuality"
SnapsToDevicePixels="True"
Source="{Binding IconSource, ElementName=root}" />
</Button>
<!-- 弹出的Calendar -->
<Popup
x:Name="CalendarPopup"
AllowsTransparency="True"
Placement="Bottom"
PlacementTarget="{Binding ElementName=root}"
StaysOpen="False">
<Border
Padding="8"
Background="White"
BorderBrush="#E0E0E0"
BorderThickness="1"
CornerRadius="5">
<Border.Effect>
<DropShadowEffect
BlurRadius="15"
Direction="270"
Opacity="0.1"
ShadowDepth="2"
Color="Black" />
</Border.Effect>
<local:Calendar DateSelected="Calendar_DateSelected" SelectedDate="{Binding SelectedDate, ElementName=root, Mode=TwoWay}" />
</Border>
</Popup>
</Grid>
</UserControl>
//DatePicker.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 Base.View.usercontrol
{
/// <summary>
/// DatePicker.xaml 的交互逻辑
/// </summary>
public partial class DatePicker : UserControl
{
public DatePicker()
{
InitializeComponent();
}
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate", typeof(DateTime?), typeof(DatePicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public DateTime? SelectedDate
{
get => (DateTime?)GetValue(SelectedDateProperty);
set => SetValue(SelectedDateProperty, value);
}
public static readonly DependencyProperty ShowTextBoxProperty =
DependencyProperty.Register("ShowTextBox", typeof(bool), typeof(DatePicker), new PropertyMetadata(true));
public bool ShowTextBox
{
get => (bool)GetValue(ShowTextBoxProperty);
set => SetValue(ShowTextBoxProperty, value);
}
public static readonly DependencyProperty IconSourceProperty =
DependencyProperty.Register("IconSource", typeof(ImageSource), typeof(DatePicker), new PropertyMetadata(new BitmapImage(new Uri("pack://application:,,,/Base;component/resources/calendar.png"))));
public ImageSource IconSource
{
get => (ImageSource)GetValue(IconSourceProperty);
set => SetValue(IconSourceProperty, value);
}
// 控件默认宽度
public static readonly DependencyProperty ControlWidthProperty =
DependencyProperty.Register("ControlWidth", typeof(double), typeof(DatePicker), new PropertyMetadata(200.0));
public double ControlWidth
{
get => (double)GetValue(ControlWidthProperty);
set => SetValue(ControlWidthProperty, value);
}
// 控件默认高度
public static readonly DependencyProperty ControlHeightProperty =
DependencyProperty.Register("ControlHeight", typeof(double), typeof(DatePicker), new PropertyMetadata(30.0));
// 图标宽度
public static readonly DependencyProperty IconWidthProperty =
DependencyProperty.Register("IconWidth", typeof(double), typeof(DatePicker), new PropertyMetadata(16.0));
public double IconWidth
{
get => (double)GetValue(IconWidthProperty);
set => SetValue(IconWidthProperty, value);
}
// 图标高度
public static readonly DependencyProperty IconHeightProperty =
DependencyProperty.Register("IconHeight", typeof(double), typeof(DatePicker), new PropertyMetadata(16.0));
public double IconHeight
{
get => (double)GetValue(IconHeightProperty);
set => SetValue(IconHeightProperty, value);
}
public static readonly DependencyProperty CloseOnSelectProperty =
DependencyProperty.Register("CloseOnSelect", typeof(bool), typeof(DatePicker), new PropertyMetadata(true));
public bool CloseOnSelect
{
get { return (bool)GetValue(CloseOnSelectProperty); }
set { SetValue(CloseOnSelectProperty, value); }
}
public static readonly DependencyProperty ShowClearButtonProperty =
DependencyProperty.Register("ShowClearButton", typeof(bool), typeof(DatePicker), new PropertyMetadata(false));
public bool ShowClearButton
{
get { return (bool)GetValue(ShowClearButtonProperty); }
set { SetValue(ShowClearButtonProperty, value); }
}
private void ToggleCalendar_Click(object sender, RoutedEventArgs e)
{
CalendarPopup.IsOpen = !CalendarPopup.IsOpen;
}
private void Calendar_DateSelected(object sender, RoutedEventArgs e)
{
if (CloseOnSelect)
{
CalendarPopup.IsOpen = false;
}
}
private void ClearDate_Click(object sender, RoutedEventArgs e)
{
SelectedDate = null;
}
}
}
//ClearButtonVisibilityConverter.cs
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace Base.Converter
{
public class ClearButtonVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
return Visibility.Collapsed;
var showClearButton = values[0] as bool?;
var selectedDate = values[1];
if (showClearButton == true && selectedDate != null)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
//BoolToCollapsedVisibilityConverter.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Base.Converter
{
public class BoolToCollapsedVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value is bool flag && flag) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value is Visibility v && v == Visibility.Visible);
}
}
}
使用方法
xmlns:base="clr-namespace:Base.View.usercontrol;assembly=Base"
...
<base:DatePicker
CloseOnSelect="True" //选中后关闭窗口
ControlWidth="180" //整个控件的宽度
IconHeight="20" //图标高度
IconSource="/Resources/Calendar.png" //图标源URI
IconWidth="20" //图标宽度
SelectedDate="{Binding Date}" //选择的日期
ShowClearButton="True" //清空按钮是否显示(逻辑没做)
ShowTextBox="True" /> //显示文本框
显示效果如图