WPF自定义控件实战:从零封装一个可复用的DateTimePicker(支持MVVM绑定)

张开发
2026/4/21 18:01:49 15 分钟阅读

分享文章

WPF自定义控件实战:从零封装一个可复用的DateTimePicker(支持MVVM绑定)
WPF企业级DateTimePicker控件开发全流程指南在WPF企业级应用开发中标准的DatePicker控件往往无法满足精确到秒级的时间选择需求。本文将带你从零开始构建一个支持完整日期时间选择年月日时分秒且完美适配MVVM模式的自定义控件。不同于简单的代码拼凑我们将采用标准的WPF控件开发范式涵盖依赖属性定义、控件模板设计、交互逻辑封装到最终的分发部署全流程。1. 项目架构设计与依赖属性定义1.1 创建自定义控件基础结构首先在Visual Studio中创建一个新的WPF自定义控件库项目。建议采用以下项目结构DateTimePickerControl/ ├── Controls/ │ └── DateTimePicker.cs ├── Themes/ │ └── Generic.xaml └── Properties/ └── AssemblyInfo.cs在DateTimePicker.cs中定义控件的核心依赖属性public class DateTimePicker : Control { public static readonly DependencyProperty SelectedDateTimeProperty DependencyProperty.Register( nameof(SelectedDateTime), typeof(DateTime?), typeof(DateTimePicker), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedDateTimeChanged)); public DateTime? SelectedDateTime { get (DateTime?)GetValue(SelectedDateTimeProperty); set SetValue(SelectedDateTimeProperty, value); } private static void OnSelectedDateTimeChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { // 属性变更处理逻辑 } }1.2 支持MVVM的关键属性设计为确保控件完美支持MVVM模式需要精心设计以下属性属性名类型说明绑定模式SelectedDateTimeDateTime?选择的完整日期时间TwoWayMinDateDateTime允许选择的最小日期OneWayMaxDateDateTime允许选择的最大日期OneWayTimeFormatstring时间显示格式字符串OneWaystatic DateTimePicker() { DefaultStyleKeyProperty.OverrideMetadata( typeof(DateTimePicker), new FrameworkPropertyMetadata(typeof(DateTimePicker))); }2. 控件模板与视觉状态设计2.1 编写Generic.xaml资源字典在Themes/Generic.xaml中定义控件的默认样式和模板Style TargetType{x:Type local:DateTimePicker} Setter PropertyTemplate Setter.Value ControlTemplate TargetType{x:Type local:DateTimePicker} Grid Border Background{TemplateBinding Background} BorderBrush{TemplateBinding BorderBrush} BorderThickness{TemplateBinding BorderThickness} Grid !-- 控件布局结构 -- /Grid /Border Popup x:NamePART_Popup !-- 日期时间选择面板 -- /Popup /Grid ControlTemplate.Triggers !-- 视觉状态触发器 -- /ControlTemplate.Triggers /ControlTemplate /Setter.Value /Setter /Style2.2 实现时分秒选择器在Popup中添加三个ListBox用于时分秒选择Grid x:NamePART_TimePanel Grid.ColumnDefinitions ColumnDefinition WidthAuto/ ColumnDefinition WidthAuto/ ColumnDefinition WidthAuto/ /Grid.ColumnDefinitions ListBox x:NamePART_HourList ItemsSource{Binding Hours} SelectedItem{Binding SelectedHour}/ ListBox x:NamePART_MinuteList Grid.Column1 ItemsSource{Binding Minutes} SelectedItem{Binding SelectedMinute}/ ListBox x:NamePART_SecondList Grid.Column2 ItemsSource{Binding Seconds} SelectedItem{Binding SelectedSecond}/ /Grid3. 交互逻辑与命令绑定3.1 处理模板部件重写OnApplyTemplate方法获取模板中的可视化元素private Calendar _calendar; private Popup _popup; private ListBox _hourList, _minuteList, _secondList; public override void OnApplyTemplate() { base.OnApplyTemplate(); _calendar GetTemplateChild(PART_Calendar) as Calendar; _popup GetTemplateChild(PART_Popup) as Popup; _hourList GetTemplateChild(PART_HourList) as ListBox; _minuteList GetTemplateChild(PART_MinuteList) as ListBox; _secondList GetTemplateChild(PART_SecondList) as ListBox; if (_calendar ! null) { _calendar.SelectedDatesChanged OnCalendarSelectedDatesChanged; } // 其他事件绑定... }3.2 实现确认命令创建确认命令并将按钮绑定到该命令private ICommand _confirmCommand; public ICommand ConfirmCommand _confirmCommand ?? new RelayCommand(() { if (_calendar?.SelectedDate.HasValue true) { var time new TimeSpan( _hourList.SelectedIndex, _minuteList.SelectedIndex, _secondList.SelectedIndex); SelectedDateTime _calendar.SelectedDate.Value time; _popup.IsOpen false; } });4. 控件打包与分发4.1 创建NuGet包编辑.nuspec文件配置包元数据package metadata idEnterprise.DateTimePicker/id version1.0.0/version authorsYourName/authors descriptionAn enterprise-grade DateTimePicker control for WPF/description dependencies dependency idMicrosoft.Xaml.Behaviors.Wpf version1.1.0 / /dependencies /metadata files file srcbin\Release\DateTimePickerControl.dll targetlib\net6.0-windows / file srcThemes\Generic.xaml targetthemes / /files /package4.2 版本控制策略采用语义化版本控制主版本号重大架构变更次版本号新增功能但向下兼容修订号Bug修复和小幅改进推荐使用GitHub Actions自动化构建和发布流程name: NuGet Publish on: release: types: [created] jobs: build: runs-on: windows-latest steps: - uses: actions/checkoutv2 - name: Setup .NET uses: actions/setup-dotnetv1 with: dotnet-version: 6.0.x - name: Build run: dotnet build --configuration Release - name: Pack run: dotnet pack --configuration Release - name: Publish run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json5. 高级功能扩展5.1 支持全球化通过资源字典实现多语言支持ResourceDictionary xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:systemclr-namespace:System;assemblymscorlib system:String x:KeyConfirmButtonText确定/system:String system:String x:KeyCancelButtonText取消/system:String !-- 其他文本资源 -- /ResourceDictionary5.2 性能优化技巧对于频繁更新的属性使用Freezable对象提高性能public class DateTimePicker : Control { private static readonly DependencyPropertyKey TimeItemsSourcePropertyKey DependencyProperty.RegisterReadOnly( TimeItemsSource, typeof(TimeItemsCollection), typeof(DateTimePicker), new PropertyMetadata(null)); public static readonly DependencyProperty TimeItemsSourceProperty TimeItemsSourcePropertyKey.DependencyProperty; public TimeItemsCollection TimeItemsSource (TimeItemsCollection)GetValue(TimeItemsSourceProperty); } public class TimeItemsCollection : FreezableCollectionint { protected override Freezable CreateInstanceCore() new TimeItemsCollection(); }5.3 响应式设计支持添加对高DPI和触摸操作的支持ControlTemplate TargetType{x:Type local:DateTimePicker} Grid VisualStateManager.VisualStateGroups VisualStateGroup x:NameCommonStates VisualState x:NameNormal/ VisualState x:NameMouseOver/ VisualState x:NamePressed/ VisualState x:NameDisabled/ /VisualStateGroup VisualStateGroup x:NamePopupStates VisualState x:NamePopupOpened/ VisualState x:NamePopupClosed/ /VisualStateGroup /VisualStateManager.VisualStateGroups !-- 控件模板内容 -- /Grid /ControlTemplate在实际项目中这种企业级的DateTimePicker控件相比原生控件可以提供更精确的时间选择能力同时保持与MVVM架构的无缝集成。通过合理的依赖属性设计和模板定制开发者可以轻松调整控件外观和行为以适应不同的应用场景。

更多文章