24 KiB
DatePicker 桌面版架构设计
本文档定义 DatePicker 桌面版的最新设计定位、公共契约、状态模型、视觉主题关系和兼容边界。共享输入分层见 输入控件共享架构设计,通用控件研发约束见 控件研发标准,内部实现原理见 DatePicker 桌面版实现原理,CalendarView 的系统性优化设计见 CalendarView 系统性优化设计,DatePicker Token 的专项设计见 DatePicker Token 设计,设计和契约变化记录见 DatePicker Changelog。
该控件的 Popup 钉住打开属于共享弹层契约,详见 Popup 钉住打开设计。本控件的语义 owner 为 InfoPickerInput,其 internal IsPopupPinnedOpen 只供测试和内部诊断使用;设置为 true 时保持 picker open state 并 relay 到 PickerPopup,设置为 false 时只解除关闭拦截。控件卸载、锚点失效、TopLevel 改变和模板重建仍按共享生命周期规则清理。
1. 控件定位
| 项 | 值 |
|---|---|
| NuGet 包 | AtomUI.Desktop.Controls |
| .NET 命名空间 | AtomUI.Desktop.Controls |
| AXAML 命名空间 | https://atomui.net |
| Gallery 页面 | controlgallery/AtomUIGallery/ShowCases/DataEntry/DatePicker |
| 控件状态 | Stable |
DatePicker 是 AtomUI 桌面控件体系中的日期选择控件,用于单日期、周、月份、季度、年份以及日期范围的日历面板选择,并通过可选的包含式日期边界约束面板导航和用户选择。
DatePicker 不负责业务日程系统、时间选择或完整日期时间解析服务。这些职责应由业务层、组合控件或更专用的 AtomUI 控件承担。
主要源码入口:
src/AtomUI.Desktop.Controls/DatePicker
2. 设计语言
DatePicker 的设计语言围绕控件职责、可观察状态和主题契约组织,而不是围绕模板节点组织。
| 维度 | 含义 | DatePicker 中的表达 |
|---|---|---|
| 产品语义 | 控件在界面中承担的稳定职责。 | DatePicker 是 AtomUI 桌面控件体系中的日期选择控件,用于单日期、日期范围和日历面板选择。 |
| 内容承载 | 用户数据、展示内容、集合项或操作入口如何进入控件。 | HeaderBackground。 |
| 状态反馈 | public API、内部状态和伪类如何形成用户可感知反馈。 | selection/checked/active、visual option。 |
| 主题语义 | ControlTheme、SharedToken、控件 Token 和模板绑定如何表达视觉。 | DatePicker Token + ControlTheme。 |
3. API 与契约模型
DatePicker 的公共契约由 public/protected 类型成员、Avalonia 属性、事件、命令、template part、伪类、ControlTheme key 和资源 key 共同组成。维护时应先确认这些契约是否已经被源码、Gallery 示例或文档暴露。
核心 public surface 按语义分组维护:
| 契约组 | 代表成员 | 维护含义 |
|---|---|---|
| 内容与数据 | HeaderBackground |
定义控件展示内容、输入数据、模板或业务对象入口。 |
| 选择与集合 | PickerMode、RangeEndSelectedDate、RangeStartSelectedDate、SelectedDateTime |
维护提交值、范围端点和选择颗粒度;SelectedDateTime、RangeStartSelectedDate、RangeEndSelectedDate 默认 TwoWay 绑定并启用 Avalonia data validation。 |
| 日期边界 | MinDate、MaxDate |
以包含边界限制可选 picker unit 和面板导航范围;默认值均为 null,表示对应方向无边界。 |
| 弹层显示游标 | PickerDisplayDate;内部 Calendar.DisplayDate、DisplayDateStart、DisplayDateEnd |
维护弹出面板打开时显示到哪个日期区域,不代表已选值。 |
| 交互与状态 | IsFloatingArrowPosition、IsHorizontalFlipped、IsNeedConfirm、IsShowNow、IsShowTime、IsTodayHighlighted |
表达用户可观察状态、可用性、清除、加载或反馈语义。 |
| 视觉与布局 | RangePickerIndicatorOffsetEnd、RangePickerIndicatorOffsetStart |
影响尺寸、位置、颜色、形状、密度和模板视觉变量。 |
| 其他稳定入口 | ClockIdentifier、DefaultDateTime、Format |
保留为 public surface,变更前需确认 Gallery 和用户 XAML 依赖。 |
日期边界 public API:
| 属性 | 类型 | 默认值 | 契约 |
|---|---|---|---|
MinDate |
DateTime? |
null |
最小可选日期,边界包含在有效范围内;忽略时间部分并按 PickerMode 归一化。 |
MaxDate |
DateTime? |
null |
最大可选日期,边界包含在有效范围内;忽略时间部分并按 PickerMode 归一化。 |
RangeDatePicker 通过 AddOwner 复用 DatePicker.MinDateProperty 和 DatePicker.MaxDateProperty,单值与范围选择使用同一日期边界契约。日期边界只限制可选值和面板导航,不公开内部 Calendar.BlackoutDates,也不承担动态业务禁用规则。
当前没有抽取到控件专属 public 事件;交互通知主要来自继承事件、命令或 Gallery 可观察状态。
主要公开类型与枚举:
- 类型:
Calendar、CalendarItem、ChoosingStatusEventArgs、DatePicker、DatePickerPresenter、DateSelectedEventArgs、DualMonthArrowDecoratedBox、DualMonthCalendarItem、DualMonthRangeCalendar、DualMonthRangeDatePickerPresenter、RangeCalendar、RangeCalendarItem、RangeDatePicker、RangeDatePickerPresenter等 19 项。 - 枚举:
DatePickerMode,取值为Date、Week、Month、Quarter、Year。
稳定 template part:
| Template Part | 类型 | 职责 |
|---|---|---|
PART_CalendarItem |
CalendarItem |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_CalendarView |
? |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_ClearUpButton |
? |
承载用户触发入口、导航或关闭动作。 |
PART_ConfirmButton |
? |
承载用户触发入口、导航或关闭动作。 |
PART_ContentRightAddOnPresenter |
? |
展示用户内容、文本、图标或模板化数据。 |
PART_HeaderButton |
HeadTextButton |
承载用户触发入口、导航或关闭动作。 |
PART_HeaderFrame |
? |
承载根视觉、边框、背景或尺寸基线。 |
PART_HeaderLayout |
? |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_InfoInputBox |
? |
承载文本输入、过滤、显示或编辑入口。 |
PART_ItemFrame |
? |
承载根视觉、边框、背景或尺寸基线。 |
PART_ItemRootLayout |
? |
承载根视觉、边框、背景或尺寸基线。 |
PART_MonthView |
Grid |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_MonthViewLayout |
? |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_NextButton |
IconButton |
承载用户触发入口、导航或关闭动作。 |
PART_NextMonthButton |
IconButton |
承载用户触发入口、导航或关闭动作。 |
PART_NowButton |
? |
承载用户触发入口、导航或关闭动作。 |
PART_Popup |
? |
承载弹层宿主、打开关闭或候选内容。 |
PART_PreviousButton |
IconButton |
承载用户触发入口、导航或关闭动作。 |
PART_PreviousMonthButton |
IconButton |
承载用户触发入口、导航或关闭动作。 |
PART_RangePickerArrow |
? |
稳定模板协作入口,重命名前必须同步主题和实现。 |
PART_RangePickerIndicator |
? |
展示指示器、进度、分页或状态反馈。 |
PART_SecondaryHeaderButton |
? |
承载用户触发入口、导航或关闭动作。 |
PART_SecondaryInfoInputBox |
? |
承载文本输入、过滤、显示或编辑入口。 |
PART_SecondaryMonthView |
? |
稳定模板协作入口,重命名前必须同步主题和实现。 |
| 其他 part | 7 项 | 参见源码和主题文件;维护时按同一生命周期规则检查。 |
控件专属或内部伪类包括 Blackout=:blackout、BtnFocusedPC、CalendarDayButtonPseudoClass.Blackout、CalendarDayButtonPseudoClass.DayFocused、CalendarDayButtonPseudoClass.RangeEnd、CalendarDayButtonPseudoClass.RangeMiddle、CalendarDayButtonPseudoClass.RangePreviewEnd、CalendarDayButtonPseudoClass.RangePreviewMiddle、CalendarDayButtonPseudoClass.RangePreviewStart、CalendarDayButtonPseudoClass.RangeStart、CalendarDayButtonPseudoClass.Today、CalendarDayButtonPseudoClass.WeekHoverStart、CalendarDayButtonPseudoClass.WeekHoverMiddle、CalendarDayButtonPseudoClass.WeekHoverEnd、CalendarDayButtonPseudoClass.WeekSelectionStart、CalendarDayButtonPseudoClass.WeekSelectionMiddle、CalendarDayButtonPseudoClass.WeekSelectionEnd、CalendarDisabledPC、DayFocused=:dayfocused、RangeEnd=:range-end、RangeMiddle=:range-middle、RangePreviewEnd=:range-preview-end、RangePreviewMiddle=:range-preview-middle、RangePreviewStart=:range-preview-start 等。这些伪类属于主题 selector 可观察契约,不能在未同步主题和 Gallery 的情况下重命名或删除。
4. 行为与状态模型
DatePicker 的状态流按以下路径收敛:
Public API / inherited command / item source / user input
-> 控件实例状态
-> effective state / pseudo-class / template property
-> ControlTheme selector / presenter / renderer
-> Gallery 可观察行为
状态维护规则:
- Disabled 或不可交互状态优先屏蔽 pointer、keyboard、motion 和提交类反馈。
- selection/checked/active、visual option 状态由控件实例或明确的数据 owner 推导,不能在 template part 之间双向竞争。
PickerMode决定选择颗粒度和初始面板:Date、Week使用月视图,Month、Quarter使用年视图,Year使用十年视图。目标颗粒度不能继续降级到更细面板。SelectedDateTime、DefaultDateTime和PickerDisplayDate必须保持语义分离:SelectedDateTime是已提交值,DefaultDateTime是默认选中值/reset 值,PickerDisplayDate只作为弹出面板打开时的显示锚点。SelectedDateTime是单值 DatePicker 的受控 Form 值入口,默认BindingMode.TwoWay,并通过 AvaloniaDataValidationErrors参与原生数据校验。MinDate和MaxDate是包含式 picker unit 边界。两者先忽略时间部分,再按当前PickerMode归一化;null表示对应方向不受限制。- 当归一化后的
MinDate晚于MaxDate时,有效范围收敛为MinDate所在的一个 picker unit,但控件不得修改或回写调用方设置的原始属性值。 - 外部受控值越界时,
SelectedDateTime、RangeStartSelectedDate和RangeEndSelectedDate保持不变,输入框继续显示外部值;Calendar 不标记越界值为选中,确认操作不可提交该值。用户选择有效日期后,才按既有 TwoWay 契约更新受控值。 - 设置
PickerDisplayDate后不得写入SelectedDateTime,不得改变输入框文本、Form value 或清除按钮状态;当已有已选值时,弹出面板仍优先围绕已选值展示。 - DatePicker / RangeDatePicker 必须把
DataValidationErrors、FormStatus 和显式 Status 投射到 sharedInputControlFrame;range indicator 等附属视觉读取EffectiveStatus,native error 优先于 Form/显式 warning/error 状态。Calendar 和 picker panel 只拥有日期选择、范围预览和面板交互状态。 PickerMode=Week的月视图是带周序号列的 8 列 week panel,不是普通日期面板的 7 个日期按钮逐个选中;选中视觉和 hover 视觉都必须按整周连续行渲染,不能退回单个日期按钮的普通 pointerover 背景。- 非
Date颗粒度仍使用DateTime?保存提交值:Week保存 ISO 周起始日,Month保存当月 1 日,Quarter保存季度首月 1 日,Year保存当年 1 月 1 日。 IsShowTime只在PickerMode=Date时形成有效时间选择;其他颗粒度忽略时间面板和时间拼接。- 范围选择的 committed 状态和 hover preview 状态必须分开:
:selected、:range-start、:range-end、:range-middle只来自真实端点;hover 只写入:range-preview-start、:range-preview-end、:range-preview-middle,其中 preview start/end 在视觉上按临时端点显示,但不能污染真实提交状态。 - 模板重套用时必须把 public API 对应状态回放到新的 part、伪类和主题变量。
- 集合、弹层、异步、动效或窗口相关状态必须能处理 reset、close、cancel、detach 和 owner 释放。
5. 视觉与主题模型
DatePicker 的视觉模型由 InputControlFrame 输入表面、InfoPicker 输入子控件、控件模板、ControlTheme、SharedToken 和必要的控件 Token 共同构成。输入表面状态由 shared frame 统一表达,DatePicker 主题只扩展日期、范围和弹层内容。
| 主题文件 | 职责 |
|---|---|
CalendarButtonTheme.axaml |
定义局部操作入口、按钮或 handle 的状态视觉。 |
CalendarDayButtonTheme.axaml |
定义局部操作入口、按钮或 handle 的状态视觉。 |
CalendarItemTheme.axaml |
定义集合项、容器项或局部单元的状态视觉。 |
CalendarTheme.axaml |
提供控件模板、selector、资源绑定和状态视觉。 |
InfoPickerTextBoxTheme.axaml |
通过 StyleVariant=Borderless 提供内部日期文本输入的无 chrome 布局。 |
InputControlFrameTheme.axaml |
提供输入表面 variant、effective status、focus、disabled、error、warning、CompactSpace 和 motion。 |
DualMonthCalendarItemTheme.axaml |
定义集合项、容器项或局部单元的状态视觉。 |
DualMonthRangeCalendarTheme.axaml |
提供控件模板、selector、资源绑定和状态视觉。 |
RangeCalendarTheme.axaml |
提供控件模板、selector、资源绑定和状态视觉。 |
DatePickerPresenterTheme.axaml |
定义布局、内容承载或框架节点视觉。 |
DatePickerTheme.axaml |
提供控件模板、selector、资源绑定和状态视觉。 |
DualMonthRangeDatePickerPresenterTheme.axaml |
定义布局、内容承载或框架节点视觉。 |
RangeDatePickerPresenterTheme.axaml |
定义布局、内容承载或框架节点视觉。 |
RangeDatePickerTheme.axaml |
提供控件模板、selector、资源绑定和状态视觉。 |
TimedRangeDatePickerPresenterTheme.axaml |
定义布局、内容承载或框架节点视觉。 |
DatePicker 使用 DatePickerToken 作为控件 Token scope。Token 只表达组件视觉语义,不承载 selection/checked/active、visual option 运行时状态。
主题维护规则:
- 不删除或重命名已经稳定的 ControlTheme key、template part、伪类和资源 key。
- 不把可由 AXAML 表达的模板状态迁移为 C# 动态创建视觉。
- 不把 hover、pressed、selected、expanded、loading、filter、popup open 等运行时状态写入 Token。
- 日期边界外的 Calendar cell 保持可见并使用 disabled 状态视觉,不通过隐藏 cell 表达不可选择状态。
- Browser 或平台特化主题必须保持同一 API 的语义一致。
6. 控件家族或集成关系
DatePicker 与同分类控件共享尺寸、状态、Token、Gallery 展示和验证规则。组合或派生控件应显式说明哪些 API 被继承、覆盖或不支持。
主要协作类型:
Calendar:控件核心或内部协作类型,维护 public surface 与主题可观察行为。CalendarButtonTheme:ControlTheme 类型入口,连接主题资源和控件类型。CalendarItem:集合项、节点或容器类型,承载单项状态和模板协作。CalendarItemTheme:ControlTheme 类型入口,连接主题资源和控件类型。CalendarTheme:ControlTheme 类型入口,连接主题资源和控件类型。DatePicker:控件核心或内部协作类型,维护 public surface 与主题可观察行为。DatePickerPresenter:模板协作类型,承载内容展示、宿主或视觉边界。DatePickerPresenterTheme:ControlTheme 类型入口,连接主题资源和控件类型。InfoPickerTextBox:internalAbstractTextInput输入子控件,负责日期文本编辑、Form/native validation 接入和 Borderless 输入布局。InputControlFrame:internal 输入表面组合控件,负责 DatePicker/RangeDatePicker 的 variant、effective status、边框、背景和 CompactSpace 视觉。DatePickerToken:控件 Token scope,负责从全局 token 派生控件语义变量。DualMonthArrowDecoratedBox:模板协作类型,承载内容展示、宿主或视觉边界。DualMonthCalendarItem:集合项、节点或容器类型,承载单项状态和模板协作。DualMonthRangeCalendar:控件核心或内部协作类型,维护 public surface 与主题可观察行为。DualMonthRangeDatePickerPresenter:模板协作类型,承载内容展示、宿主或视觉边界。RangeCalendar:控件核心或内部协作类型,维护 public surface 与主题可观察行为。RangeCalendarItem:集合项、节点或容器类型,承载单项状态和模板协作。RangeDatePicker:控件核心或内部协作类型,维护 public surface 与主题可观察行为。RangeDatePickerPresenter:模板协作类型,承载内容展示、宿主或视觉边界。RangeDatePickerPresenterTheme:ControlTheme 类型入口,连接主题资源和控件类型。TimedRangeDatePickerPresenter:模板协作类型,承载内容展示、宿主或视觉边界。DatePickerLangResourceKind:稳定的本地化 Catalog enum;内置翻译由同目录三种语言 XLIFF 提供并在编译期生成。
集成关系:
- 与 ThemeManager、SharedToken、ControlTheme、控件文档和 Gallery ShowCase 示例保持一致。
- 涉及 ItemsSource、Popup、Flyout、Window、Form 或 CompactSpace 的路径必须保持生命周期释放和数据状态同步。
- 源码目录中的共享基类和内部协作类型形成维护边界,不能只修改桌面包装类而忽略共享状态 owner。
DatePicker与RangeDatePicker的日期边界必须通过 presenter 投影到内部 CalendarView;内部DisplayDateStart、DisplayDateEnd和BlackoutDates不是外层控件的替代 public API。
7. 兼容性不变量
维护 DatePicker 时必须保持以下不变量:
- 不擅自新增、删除、重命名或改变 public/protected API、Avalonia 属性、事件和默认值。
- 不破坏 template part、伪类、ControlTheme key、Token 名称和资源 key。
- 不改变 Gallery 已展示的 XAML 用法、默认外观、交互顺序和状态优先级。
- Template part 重新应用、集合替换、弹层关闭、窗口失活和控件 detach 时必须释放旧订阅和资源宿主。
- 不通过隐藏延迟、强制刷新或吞异常掩盖状态同步问题。
- 不引入运行时反射扫描作为 API、Token 或数据路径发现机制。
- 文档只描述当前稳定设计;历史变化记录在
changelog.md。
8. 专项模型
8.1 选择与当前项模型
DatePicker 的当前项状态必须由单一 owner 推导。public 选择属性、集合项容器和伪类之间只能做单向同步,集合替换、清空和模板重套用时必须回放当前状态。
8.2 视觉选项模型
DatePicker 的视觉选项通过 public API 归一为 theme variables、伪类或模板绑定。Token 保存组件语义值,不能保存实例运行时状态或业务色值。
8.3 PickerMode 颗粒度模型
DatePicker.PickerMode 和 RangeDatePicker.PickerMode 对齐 Ant Design 的 picker 模型,用一个核心控件表达日期、周、月份、季度和年份选择,不为每种颗粒度拆分独立控件。
Format 为空时,控件按颗粒度选择默认显示格式:日期 yyyy-MM-dd,周 yyyy-ww周,月份 yyyy-MM,季度 yyyy-Qn,年份 yyyy。自定义 Format 优先级高于默认格式。
CalendarView 必须把目标颗粒度作为状态模型的一部分处理。用户从更粗面板返回时,只能回到目标颗粒度面板;例如月份选择可以从年面板进入十年面板选择年份,但选完年份后回到月份面板,不继续进入日期面板。
8.4 日期边界约束模型
日期边界按当前 PickerMode 归一为可比较的 picker unit:
PickerMode |
边界归一化单位 |
|---|---|
Date |
当天 |
Week |
ISO 周起始日 |
Month |
当月第一天 |
Quarter |
当季度第一天 |
Year |
当年第一天 |
有效边界同时约束日期 cell、周行、月份、季度、年份和面板导航。范围外单元保持可见但不可通过 pointer、keyboard 或提交动作选中;Today / Now 在目标 picker unit 越界时不可用。RangeDatePicker 的两个端点共享同一组边界,各端点按自身 active 状态独立校验,双面板显示锚点也必须落在有效范围内。
日期边界与受控值是两个独立状态域。边界变化可以使已有受控值失效,但不能隐式改写 ViewModel;Calendar 只投影当前有效的选中状态,输入和数据校验层继续拥有外部值的显示与校验责任。
9. 文档导航、LLMS 导出与验证策略
关联文档:
LLMS 语义区域:
| Part | AtomUI 节点 | 职责 | 相关 API | 相关 Token | 稳定性 |
|---|---|---|---|---|---|
root |
DatePicker |
数据录入控件根语义区域,承载 public API、值状态、验证状态和主题入口。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
input |
输入或编辑区域 |
承载用户输入、当前值、占位、格式化或只读状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
trigger |
触发区域 |
承载清除、展开、提交、步进、上传或辅助操作。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
popup |
弹层或候选区域 |
承载下拉、候选项、日历、颜色面板或异步内容。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
validation |
校验反馈区域 |
承载 Form、status、错误、警告、help 或 loading 状态。 | 见 API 与契约模型 | 见视觉与主题模型 | stable |
LLMS 导出来源:
| LLMS 内容 | 来源 | 说明 |
|---|---|---|
| 单控件完整文档 | overview.md + implementation.md + token.md + Gallery ShowCase |
生成 controls/date-picker/index-cn.md |
| 单控件语义文档 | overview.md + implementation.md + theme/template 信息 |
生成 controls/date-picker/semantic-cn.md |
| API 表 | overview.md 语义摘要 + 源码 public surface | 不在 overview.md 中复制完整 API 表 |
| Design Token 表 | token.md、Token 类型或第 5 节主题模型 | 不在生成产物中手工维护第二份 Token 表 |
| 示例 | Gallery ShowCase + source snippet catalog | 只引用稳定示例 |
| 源码索引 | implementation.md |
用于定位控件源码、主题和测试 |
验证策略:
| 改动类型 | 验证要求 |
|---|---|
| 文档改动 | 运行 git diff --check,检查相对链接存在。 |
| Public API | 覆盖属性默认值、事件触发、命令和继承语义。 |
| 状态模型 | 覆盖 selection/checked/active、visual option、disabled、hover、pressed、focus 以及控件特有状态。 |
| AXAML/Theme | 检查 template part、伪类、资源 key、Light/Dark 主题和 Browser 主题。 |
| Token | 检查 TokenKind、AXAML token resource、Token 类型、生成数据和 token.md和文档同步。 |
| Gallery | 走查对应 ShowCase 示例和源码片段入口。 |