HandyControl WPF TimePicker 时间选择器完全指南:从下拉时钟到数据校验的实战用法

原创2026-10-02 16:43:38134 阅读
文章标签:UI组件桌面应用

HandyControl WPF TimePicker 时间选择器完全指南:从下拉时钟到数据校验的实战用法

HandyControl 是面向 WPF 的一套常用控件库,其中 TimePicker(时间选择器)允许用户通过在文本框中直接键入时间,或点击右侧按钮展开下拉时钟控件来选择时间。本文以 doc/source/handycontrol/extend_controls/timePicker/index.md 文档为主线,结合 TimePicker 源码 与官方主题样式,系统讲解 TimePicker 的创建、时间选择、时钟样式切换、标题与占位符配置、核心属性/方法与底层实现原理,帮助你快速在实际项目中落地并自由定制。

TimePicker 控件概览与模板结构

TimePicker 本质上是一个「文本框 + 下拉按钮 + Popup 弹层」的组合控件。它在源码中的类声明如下(见 TimePicker.cs):

[TemplatePart(Name = ElementRoot, Type = typeof(Grid))]
[TemplatePart(Name = ElementTextBox, Type = typeof(WatermarkTextBox))]
[TemplatePart(Name = ElementButton, Type = typeof(Button))]
[TemplatePart(Name = ElementPopup, Type = typeof(Popup))]
public class TimePicker : Control
{
    private const string ElementRoot = "PART_Root";
    private const string ElementTextBox = "PART_TextBox";
    private const string ElementButton = "PART_Button";
    private const string ElementPopup = "PART_Popup";
}

四个模板部件分别承担不同职责:

模板部件 类型 作用
PART_Root Grid 整体布局容器
PART_TextBox WatermarkTextBox 可输入时间的文本框,支持水印占位文本
PART_Button Button 右侧下拉按钮(时钟图标)
PART_Popup Popup 承载下拉时钟的弹层,默认 StaysOpen="False"

其中 PART_TextBox 使用 HandyControl 的 WatermarkTextBox,意味着它天然支持水印(Placeholder)显示,这正是后文「占位符文本」功能的基础。OnApplyTemplate() 中会通过 GetTemplateChild 获取这四个部件并绑定事件(源码位置),若模板缺失对应命名部件会抛出异常。

创建 TimePicker

创建 TimePicker 只需一行代码。XAML 方式:

<hc:TimePicker />

C# 方式:

var timePicker = new TimePicker();

默认样式由 TimePicker.xaml 中的隐式样式提供,它基于 TimePickerBaseStyle(定义于 TimePickerBaseStyle.xaml)。基础模板由两列 Grid 构成:左侧为文本框、右侧为时钟图标按钮,外层 Border 根据「鼠标悬停 / 焦点 / 弹层打开」状态切换 SecondaryBorderBrush 与 PrimaryBrush 边框颜色。控件被禁用时整体透明度降至 0.4。

选择时间

TimePicker 提供两条选择时间的路径:

  1. 文本框键入:直接输入如 14:30:00 的时间文本,回车或失焦后触发解析。
  2. 下拉时钟选择:点击右侧按钮打开 Popup 中的时钟控件,在时钟上选择并确认。

两条路径最终都会写入 SelectedTime 依赖属性。键入文本的解析与回写逻辑在 SetSelectedTime() 中完成:文本非空时尝试 DateTime.Parse,解析成功则同步更新 SelectedTime 与 DisplayTime,解析失败则回退到当前选中值或 DisplayTime;文本清空时则将 SelectedTime 置为 null(源码位置)。

除了鼠标操作,TimePicker 还内置了键盘快捷方式(源码位置):

  • Alt + ↓:切换下拉时钟的打开 / 关闭;
  • Enter:提交当前文本框中的时间。

设置选中时间同样可以在 XAML 或代码中进行:

<hc:TimePicker SelectedTime="{x:Static system:DateTime.Now}"/>
timePicker.SelectedTime = DateTime.Now;

SelectedTime 类型为 DateTime?,默认值为 null,并且注册时带有 BindsTwoWayByDefault,因此它天然支持双向绑定(源码位置)。当它变化时,控件内部会把时间按 TimeFormat 格式化后写回文本框,并抛出 SelectedTimeChanged 路由事件(FunctionEventArgs<DateTime?> 类型),方便在 MVVM 或事件模型中监听。

更改时钟样式

TimePicker 的 Clock 属性类型为 ClockBase(定义于 ClockBase.cs),它是一个抽象控件基类,提供 SelectedTime、DisplayTime、TimeFormat 等公共依赖属性,以及 Confirmed 事件——用户在时钟上点击确认按钮时触发。仓库中提供了两个具体时钟实现:

  • Clock:默认的时钟样式(Clock.cs);
  • ListClock:列表式时钟样式(ListClock.cs)。

当未显式设置 Clock 时,OnApplyTemplate 会通过 EnsureClock() 自动创建一个默认 Clock 实例注入(源码位置)。

替换时钟样式只需设置 Clock 属性,示例(配合 TimePickerPlus 样式):

<hc:TimePicker ShowClearButton="True" Style="{StaticResource TimePickerPlus}">
    <hc:TimePicker.Clock>
        <hc:ListClock/>
    </hc:TimePicker.Clock>
</hc:TimePicker>

源码层面的时钟联动机制值得注意:Clock 依赖属性注册为 NotDataBindable,其变更回调 OnClockChanged 会先解除旧时钟的 SelectedTimeChanged / Confirmed 订阅,再为新时钟订阅事件、强制设置 ShowConfirmButton = true,并将其挂到 Popup 的 Child 上(源码位置)。也就是说,Clock 必须通过 XAML 属性或代码设置,而不能用绑定。时钟中选中的时间通过 SelectedTimeChanged 事件回传,确认后通过 Confirmed 事件让 TimePicker 收起弹层。

设置标题与占位符文本

TimePicker 支持通过 HandyControl 的附加属性 InfoElement.Title(标题)与 InfoElement.Placeholder(占位符)向用户说明输入用途。要使用这两个属性,必须应用 TimePickerExtend 或 TimePickerPlus 样式(默认基础样式不含标题与水印布局):

<hc:TimePicker hc:InfoElement.Placeholder="{x:Static langs:Lang.PlsEnterContent}"
               hc:InfoElement.Title="{x:Static langs:Lang.TitleDemoStr2}"
               Style="{StaticResource TimePickerExtend}"/>

TimePickerExtend 与 TimePickerPlus 的差异在于:TimePickerPlus 额外提供了一个清除按钮(ButtonClear),在鼠标悬停且 InfoElement.ShowClearButton="True" 时显示,点击后通过 ControlCommands.Clear 命令清空 SelectedTime 与文本框(模板定义见 TimePickerBaseStyle.xaml,命令绑定见 TimePicker.cs 构造函数)。

这两种「扩展样式」都支持标题的两种排版位置,通过 InfoElement.TitlePlacement 切换:

  • 默认(Top):标题显示在输入框上方,使用 TimePickerExtendTopTemplate;
  • Left:标题显示在输入框左侧,需配合 InfoElement.TitleWidth 指定标题列宽度。

官方 Demo(TimePickerDemo.xaml)中给出了非常完整的组合用法,包括必填标记、清除按钮、标题居左、紧凑尺寸等:

<!-- 必填 + 占位符 + 标题居左 + 清除按钮 -->
<hc:TimePicker Width="380"
               hc:InfoElement.TitleWidth="140"
               hc:InfoElement.TitlePlacement="Left"
               hc:InfoElement.Placeholder="{ex:Lang Key={x:Static langs:LangKeys.PlsEnterContent}}"
               hc:InfoElement.Title="{ex:Lang Key={x:Static langs:LangKeys.TitleDemoStr3}}"
               hc:InfoElement.Necessary="True"
               hc:InfoElement.ShowClearButton="True"
               Style="{StaticResource TimePickerPlus}"/>

与标题排版相关的附加属性还包括 InfoElement.ContentHeight / MinContentHeight(控制输入区高度)和 TitleElement.MarginOnTheTop / MarginOnTheLeft(标题间距),这些都在扩展样式的模板中被绑定。

样式一览

HandyControl 为 TimePicker 提供了完整的样式族(定义于 TimePicker.xaml):

样式键 基于 说明
TimePicker(隐式) TimePickerBaseStyle 默认样式
TimePicker.Small TimePickerBaseStyle 紧凑尺寸,MinHeight=20、Padding=6,0
TimePickerExtend TimePickerExtendBaseStyle 支持标题 / 占位符 / 必填标记
TimePickerExtend.Small TimePickerExtend 紧凑版扩展样式
TimePickerPlus TimePickerPlusBaseStyle 在扩展样式基础上增加清除按钮
TimePickerPlus.Small TimePickerPlus 紧凑版 Plus 样式

在 Demo 中还可以看到「禁用状态」组合:IsEnabled="False" 时控件以 40% 透明度显示;配合 InfoElement.IsReadOnly="True" 时,下拉按钮被隐藏、文本框变为只读(触发逻辑见 TimePickerBaseStyle.xaml)。

属性详解

下表汇总 TimePicker 的核心属性,并补充了源码中的默认值与行为细节:

属性 类型 / 默认值 描述
SelectedTime DateTime? / null,双向绑定 获取或设置当前选中的时间;变化时同步文本框并触发 SelectedTimeChanged 事件
Clock ClockBase / 默认 Clock 获取或设置下拉时钟;不可绑定,只能通过 XAML 属性或代码设置
TimeFormat string / "HH:mm:ss" 获取或设置用于显示选中时间的格式
DisplayTime DateTime / DateTime.Now,双向绑定 获取或设置时钟上要显示的时间(打开弹层时的初始时间)
IsDropDownOpen bool / false,双向绑定 指示下拉时钟的打开 / 关闭状态;控件禁用时强制关闭
Text string / "" 获取 TimePicker 显示的文本,或设置选定的时间(设置文本会触发时间解析)
VerifyFunc 数据验证委托 获取或设置数据验证委托
IsError bool 获取或设置数据是否错误
ErrorStr string 获取或设置错误提示
TextType 文本类型 获取或设置文本类型
ShowClearButton bool(附加属性 InfoElement.ShowClearButton) 是否在悬停时显示清除按钮(需 TimePickerPlus 系列样式)

其中 TimeFormat、DisplayTime、IsDropDownOpen、SelectedTime、Text 的实现细节都已在源码中确认(TimePicker.cs)。例如 DisplayTime 的强制值回调会把时间同步给 Clock.DisplayTime,保证打开弹层时时钟显示的是当前时间;IsDropDownOpen 打开时会记录 _originalSelectedTime 并将焦点移到时钟上。

说明:VerifyFunc、IsError、ErrorStr、TextType 是官方文档列出的数据校验相关属性。需要留意的是,当前仓库中 TimePicker 源码 的类声明仅继承 Control,尚未包含这些校验成员的实现,实际使用前建议以你所引用版本的程序集 API 为准。

此外,TimePicker 还继承了 TextBoxBase 的文本编辑外观属性:SelectionBrush、SelectionOpacity、CaretBrush(以及非 .NET 4.x 下的 SelectionTextBrush),这些属性会在 OnApplyTemplate 中绑定到内部文本框(源码位置),可用于自定义选中文本与光标的颜色。

事件与方法

路由事件:

事件 参数类型 触发时机
SelectedTimeChanged FunctionEventArgs<DateTime?> 选中时间发生变化时
ClockOpened RoutedEventArgs 下拉时钟打开时
ClockClosed RoutedEventArgs 下拉时钟关闭时

后两个事件由 Popup 的 Opened / Closed 内部处理触发,并会回调 ClockBase 的虚方法 OnClockOpened() / OnClockClosed()(见 ClockBase.cs),便于自定义时钟实现打开 / 关闭时的行为。

方法:

方法 描述
VerifyData() 验证数据(与数据校验属性配套)

VerifyData() 属于文档描述的数据验证接口能力。除数据校验外,TimePicker 重写了 ToString(),返回 SelectedTime 按 TimeFormat 格式化后的字符串,未选中时返回空字符串(源码位置)。键盘交互方面,控件将 TabNavigation 覆盖为 Once 模式,自身不作为 Tab 停靠点,聚焦时光标会进入文本框并全选文本(源码位置 与 OnGotFocus)。

完整实战示例

综合上述特性,一个包含标题、必填、占位符、清除按钮与 ListClock 时钟的完整示例:

<Window xmlns:hc="https://handyorg.github.io/handycontrol"
        xmlns:system="clr-namespace:System;assembly=mscorlib">
    <StackPanel>
        <!-- 基础用法 + 默认时钟 -->
        <hc:TimePicker SelectedTime="{x:Static system:DateTime.Now}"/>

        <!-- 带标题、占位符、必填标记与清除按钮 -->
        <hc:TimePicker hc:InfoElement.Title="会议开始时间"
                       hc:InfoElement.Placeholder="请选择时间"
                       hc:InfoElement.Necessary="True"
                       hc:InfoElement.ShowClearButton="True"
                       Style="{StaticResource TimePickerPlus}"/>

        <!-- 切换为列表式时钟 -->
        <hc:TimePicker Style="{StaticResource TimePickerExtend}">
            <hc:TimePicker.Clock>
                <hc:ListClock/>
            </hc:TimePicker.Clock>
        </hc:TimePicker>
    </StackPanel>
</Window>

后台代码方式:

var timePicker = new TimePicker
{
    Style = (Style) FindResource("TimePickerExtend"),
    SelectedTime = DateTime.Now
};
timePicker.SelectedTimeChanged += (s, e) =>
    Console.WriteLine($"选中时间:{e.Info:HH:mm:ss}");

小结

HandyControl 的 TimePicker 在标准的「文本框 + 下拉时钟」交互之上,提供了可替换的 ClockBase 时钟体系、TimeFormat 格式化、DisplayTime 初始显示时间、键盘快捷操作、SelectedTimeChanged 事件,以及通过 InfoElement 附加属性实现的标题 / 占位符 / 必填 / 清除按钮等表单能力。配合 TimePickerExtend、TimePickerPlus 及其 .Small 系列样式,可以快速搭建风格统一且交互完整的时间输入控件。深入阅读 TimePicker 源码、基础模板 与 官方 Demo,即可按需定制属于自己的时间选择器。

登录后查看全文
HandyControl