"公司要求项目升级到WPF,但是我完全摸不着头脑。连个简单的日期控件都不知道怎么用了!"这话让我想起了自己当年的转型经历。据统计,超过60%的.NET桌面开发者仍在使用WinForm,但随着现代化UI需求的增长,WPF转型已经成为必然趋势。
今天我们就从最常用的DatePicker控件入手,让你彻底掌握WPF开发的核心思路,告别转型焦虑!
很多WinForm开发者在转型时都会遇到这些问题:
其实,核心问题就是没有掌握WPF的设计哲学。
WinForm中的做法:
C#// 传统WinForm方式
dateTimePicker1.Value = DateTime.Now;
var selectedDate = dateTimePicker1.Value;
WPF中的新思路:
XML<Window x:Class="AppDateTimePicker.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:AppDateTimePicker"
mc:Ignorable="d"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<sys:DateTime x:Key="now">
</sys:DateTime>
</Window.Resources>
<StackPanel Margin="20">
<!-- 实时显示选中日期 -->
<TextBlock Text="{Binding ElementName=myDatePicker,
Path=SelectedDate,
StringFormat='选中日期:{0:yyyy-MM-dd}'}"
FontSize="16"
Margin="0,0,0,10" />
<!-- DatePicker控件 -->
<DatePicker x:Name="myDatePicker"
SelectedDate="{x:Static sys:DateTime.Now}"
Width="150" />
</StackPanel>
</Window>

⚠️ 关键坑点提醒:
SelectedDate是DateTime?可空类型,记得判空!{Binding}语法,不是直接赋值C#// WPF后台代码处理
DateTime? selectedDate = myDatePicker.SelectedDate;
if (selectedDate.HasValue)
{
// 安全的日期处理
Console.WriteLine($"选中日期:{selectedDate.Value:yyyy-MM-dd}");
}
XAML界面层:
XML<Window x:Class="AppDateTimePicker.Window1"
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:AppDateTimePicker"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<Window.DataContext>
<!-- 直接实例化ViewModel -->
<local:DateViewModel />
</Window.DataContext>
<StackPanel Margin="20">
<TextBlock Text="{Binding SelectedDateModel,
StringFormat='模型日期:{0:d}'}"
FontSize="16"
Margin="0,0,0,10" />
<!-- 双向绑定到ViewModel -->
<DatePicker SelectedDate="{Binding SelectedDateModel, Mode=TwoWay}"
Width="150" />
</StackPanel>
</Window>
ViewModel业务层:
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppDateTimePicker
{
public class DateViewModel : INotifyPropertyChanged
{
private DateTime? _selectedDateModel = DateTime.Now;
public DateTime? SelectedDateModel
{
get => _selectedDateModel;
set
{
if (_selectedDateModel != value)
{
_selectedDateModel = value;
OnPropertyChanged(nameof(SelectedDateModel));
// 业务逻辑处理
ValidateDate(value);
}
}
}
private void ValidateDate(DateTime? date)
{
// 实际业务验证逻辑
if (date.HasValue && date.Value > DateTime.Now.AddYears(1))
{
// 处理超出范围的日期
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
💎 最佳实践:
INotifyPropertyChanged实现属性变更通知Mode=TwoWay确保界面和数据双向同步自定义基础样式:
XML<Window x:Class="AppDateTimePicker.Window2"
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:AppDateTimePicker"
mc:Ignorable="d"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Window2" Height="450" Width="800">
<Window.Resources>
<sys:DateTime x:Key="now">
</sys:DateTime>
<!-- 可复用的DatePicker样式 -->
<Style TargetType="DatePicker" x:Key="CustomDatePickerStyle">
<Setter Property="Margin" Value="5" />
<Setter Property="BorderBrush" Value="#3498db" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="Foreground" Value="#2c3e50" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="35" />
<!-- 鼠标悬停效果 -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#e74c3c" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- 应用自定义样式 -->
<DatePicker SelectedDate="{x:Static sys:DateTime.Now}"
SelectedDateFormat="Short"
Style="{StaticResource CustomDatePickerStyle}" />
</Window>

XML<Window x:Class="AppDateTimePicker.Window3"
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:AppDateTimePicker"
mc:Ignorable="d"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Window3" Height="450" Width="800">
<Window.Resources>
<!-- 完全自定义DatePicker外观 -->
<Style TargetType="DatePicker" x:Key="ModernDatePickerStyle">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#ddd"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="#333"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Height" Value="35"/>
<Setter Property="Padding" Value="5,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<!-- 文本框部分 -->
<DatePickerTextBox x:Name="PART_TextBox"
Grid.Column="0"
Margin="0"
Background="Transparent"
BorderThickness="0"
Foreground="{TemplateBinding Foreground}"
Padding="{TemplateBinding Padding}"
FontSize="{TemplateBinding FontSize}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"/>
<!-- 自定义下拉按钮 -->
<Button x:Name="PART_Button"
Grid.Column="1"
Content="📅"
Width="35"
Height="{TemplateBinding Height}"
Background="#3498db"
BorderBrush="{x:Null}"
BorderThickness="0"
Foreground="White"
FontSize="14"
Cursor="Hand"
VerticalAlignment="Stretch"
Focusable="False">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#3498db"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2980b9"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="#1f618d"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
<!-- 弹出日历 -->
<Popup x:Name="PART_Popup"
AllowsTransparency="True"
Placement="Bottom"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
StaysOpen="False">
<Border Background="White"
BorderBrush="#ccc"
BorderThickness="1"
CornerRadius="3"
Padding="1">
<Border.Effect>
<DropShadowEffect Color="Black"
Opacity="0.3"
ShadowDepth="3"
BlurRadius="5"/>
</Border.Effect>
<Calendar x:Name="PART_Calendar"
SelectionMode="SingleDate"/>
</Border>
</Popup>
</Grid>
</Border>
<!-- 控件状态触发器 -->
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#3498db"/>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" Value="#2980b9"/>
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#f8f9fa"/>
<Setter Property="Foreground" Value="#6c757d"/>
<Setter Property="BorderBrush" Value="#dee2e6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 扁平化设计样式 -->
<Style TargetType="DatePicker" x:Key="FlatDatePickerStyle">
<Setter Property="Background" Value="#ecf0f1"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="#2c3e50"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Padding" Value="0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<!-- 文本框 -->
<DatePickerTextBox x:Name="PART_TextBox"
Grid.Column="0"
Background="Transparent"
BorderThickness="0"
Foreground="{TemplateBinding Foreground}"
Padding="{TemplateBinding Padding}"
FontSize="{TemplateBinding FontSize}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"/>
<!-- 图标按钮 -->
<Button x:Name="PART_Button"
Grid.Column="1"
Content="📅"
Width="40"
Background="Transparent"
BorderThickness="0"
Foreground="#7f8c8d"
FontSize="16"
Cursor="Hand"
Focusable="False">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#d5dbdb"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
<!-- 弹出日历 -->
<Popup x:Name="PART_Popup"
AllowsTransparency="True"
Placement="Bottom"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
StaysOpen="False">
<Border Background="White"
BorderBrush="#bdc3c7"
BorderThickness="1"
CornerRadius="5"
Padding="1">
<Border.Effect>
<DropShadowEffect Color="Black"
Opacity="0.2"
ShadowDepth="2"
BlurRadius="8"/>
</Border.Effect>
<Calendar x:Name="PART_Calendar"
SelectionMode="SingleDate"/>
</Border>
</Popup>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#d5dbdb"/>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="Background" Value="#bdc3c7"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 简约样式 -->
<Style TargetType="DatePicker" x:Key="MinimalDatePickerStyle">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#e0e0e0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="#424242"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Height" Value="38"/>
<Setter Property="Padding" Value="0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DatePicker">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<!-- 文本框 -->
<DatePickerTextBox x:Name="PART_TextBox"
Grid.Column="0"
Background="Transparent"
BorderThickness="0"
Foreground="{TemplateBinding Foreground}"
Padding="{TemplateBinding Padding}"
FontSize="{TemplateBinding FontSize}"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"/>
<!-- 下拉按钮 -->
<Button x:Name="PART_Button"
Grid.Column="1"
Content="▼"
Width="30"
Background="Transparent"
BorderThickness="0"
Foreground="#9e9e9e"
FontSize="10"
Cursor="Hand"
Focusable="False"/>
<!-- 弹出日历 -->
<Popup x:Name="PART_Popup"
AllowsTransparency="True"
Placement="Bottom"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
StaysOpen="False">
<Border Background="White"
BorderBrush="#e0e0e0"
BorderThickness="1"
CornerRadius="2"
Padding="1">
<Border.Effect>
<DropShadowEffect Color="Black"
Opacity="0.15"
ShadowDepth="1"
BlurRadius="6"/>
</Border.Effect>
<Calendar x:Name="PART_Calendar"
SelectionMode="SingleDate"/>
</Border>
</Popup>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="#2196f3"/>
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderBrush" Value="#1976d2"/>
<Setter Property="BorderThickness" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="30"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 标题 -->
<TextBlock Grid.Row="0"
Text="自定义DatePicker样式示例"
FontSize="20"
FontWeight="Bold"
Foreground="#2c3e50"
HorizontalAlignment="Center"/>
<!-- 默认样式 -->
<StackPanel Grid.Row="2" Orientation="Horizontal">
<TextBlock Text="默认样式: "
VerticalAlignment="Center"
Width="120"
FontSize="14"/>
<DatePicker x:Name="DefaultDatePicker"
Width="250"
SelectedDate="{x:Static sys:DateTime.Now}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"/>
</StackPanel>
<!-- 现代化样式 -->
<StackPanel Grid.Row="4" Orientation="Horizontal">
<TextBlock Text="现代化样式: "
VerticalAlignment="Center"
Width="120"
FontSize="14"/>
<DatePicker x:Name="ModernDatePicker"
Style="{StaticResource ModernDatePickerStyle}"
Width="250"
SelectedDate="{x:Static sys:DateTime.Now}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"/>
</StackPanel>
<!-- 扁平化样式 -->
<StackPanel Grid.Row="6" Orientation="Horizontal">
<TextBlock Text="扁平化样式: "
VerticalAlignment="Center"
Width="120"
FontSize="14"/>
<DatePicker x:Name="FlatDatePicker"
Style="{StaticResource FlatDatePickerStyle}"
Width="250"
SelectedDate="{x:Static sys:DateTime.Now}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"/>
</StackPanel>
<!-- 简约样式 -->
<StackPanel Grid.Row="8" Orientation="Horizontal">
<TextBlock Text="简约样式: "
VerticalAlignment="Center"
Width="120"
FontSize="14"/>
<DatePicker x:Name="MinimalDatePicker"
Style="{StaticResource MinimalDatePickerStyle}"
Width="250"
SelectedDate="{x:Static sys:DateTime.Now}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"/>
</StackPanel>
<!-- 禁用状态示例 -->
<StackPanel Grid.Row="10" Orientation="Horizontal">
<TextBlock Text="禁用状态: "
VerticalAlignment="Center"
Width="120"
FontSize="14"/>
<DatePicker x:Name="DisabledDatePicker"
Style="{StaticResource ModernDatePickerStyle}"
Width="250"
IsEnabled="False"
SelectedDate="{x:Static sys:DateTime.Now}"
xmlns:sys="clr-namespace:System;assembly=mscorlib"/>
</StackPanel>
</Grid>
</Window>

🚨 重要提醒:
PART_TextBox和PART_Button这些名称XML<Window x:Class="AppDateTimePicker.Window4"
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:AppDateTimePicker"
mc:Ignorable="d"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Window4" Height="450" Width="800">
<Window.Resources>
<sys:DateTime x:Key="now">
</sys:DateTime>
</Window.Resources>
<StackPanel>
<!-- 限制日期选择范围 -->
<DatePicker x:Name="startDatePicker"
DisplayDateStart="{x:Static sys:DateTime.Now}"
DisplayDateEnd="{Binding ElementName=endDatePicker, Path=SelectedDate}"
SelectedDateFormat="Short" />
<DatePicker x:Name="endDatePicker"
DisplayDateStart="{Binding ElementName=startDatePicker, Path=SelectedDate}"
SelectedDateFormat="Short" />
</StackPanel>
</Window>
带验证的ViewModel:
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppDateTimePicker
{
public class DateRangeViewModel : INotifyPropertyChanged
{
private DateTime? _startDate = DateTime.Now;
private DateTime? _endDate = DateTime.Now.AddDays(30);
public DateTime? StartDate
{
get => _startDate;
set
{
if (_startDate != value)
{
_startDate = value;
OnPropertyChanged(nameof(StartDate));
// 自动调整结束日期
if (_endDate.HasValue && value.HasValue && _endDate < value)
{
EndDate = value.Value.AddDays(1);
}
}
}
}
public DateTime? EndDate
{
get => _endDate;
set
{
if (_endDate != value)
{
_endDate = value;
OnPropertyChanged(nameof(EndDate));
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

WPF的强大不仅仅体现在DatePicker上,整个框架都遵循这套设计哲学。掌握了这些核心概念,你会发现WPF开发比WinForm更加优雅和高效。
💬 互动话题:
你在WinForm转WPF过程中遇到过哪些坑?或者有什么独特的解决方案?欢迎在评论区分享你的经验!
如果觉得这篇文章对你有帮助,请转发给更多正在转型路上的同行,让我们一起在WPF的道路上走得更远!
🔖 关注我,获取更多C#开发实战技巧和最佳实践!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!