Files

12 KiB
Raw Permalink Blame History

TimePicker 桌面版实现原理

本文档描述 TimePicker 桌面版的内部实现范围、源码职责、状态流、生命周期、资源边界和维护规则。共享输入分层见 输入控件共享架构设计,公共设计与 API 契约见 TimePicker 桌面版架构设计,变化记录见 TimePicker Changelog。涉及控件 Token 的实现应同时阅读 TimePicker Token 设计。

Popup 接入边界:InfoPickerInput 负责业务状态和内容准备,PickerPopup 负责实际显示。模板重建或宿主切换时必须先释放旧 relay,再绑定新的 Popup;普通外点、Escape、失焦和业务关闭在 pinned 状态下被拦截,detach、窗口销毁、跨 TopLevel 和无效锚点必须走生命周期关闭并释放 Popup host。完整状态机见 Popup 钉住打开设计。

1. 实现定位

本文档覆盖 TimePicker 的控件实现、主题接入、状态同步和 Gallery 可见维护边界。具体属性注册、默认值、绘制细节和 AXAML selector 仍应直接阅读源码;本文只记录维护者必须理解的稳定结构和不变量。

2. 源码文件结构

主要源码文件:

  • src/AtomUI.Desktop.Controls/TimePicker/Localization/TimePickerLangResourceKind.cs
  • src/AtomUI.Desktop.Controls/TimePicker/Localization/en-US.xlf
  • src/AtomUI.Desktop.Controls/TimePicker/Localization/zh-CN.xlf
  • src/AtomUI.Desktop.Controls/TimePicker/Localization/zh-TW.xlf
  • src/AtomUI.Desktop.Controls/TimePicker/RangeTimePicker.cs
  • src/AtomUI.Desktop.Controls/TimePicker/Themes/RangeTimePickerTheme.axaml
  • src/AtomUI.Desktop.Controls/TimePicker/Themes/TimePickerPresenterTheme.axaml
  • src/AtomUI.Desktop.Controls/TimePicker/Themes/TimePickerTheme.axaml
  • src/AtomUI.Desktop.Controls/TimePicker/Themes/TimeViewCellTheme.axaml
  • src/AtomUI.Desktop.Controls/TimePicker/Themes/TimeViewTheme.axaml
  • src/AtomUI.Desktop.Controls/TimePicker/TimePicker.cs
  • src/AtomUI.Desktop.Controls/TimePicker/TimePickerPresenter.cs
  • src/AtomUI.Desktop.Controls/TimePicker/TimePickerToken.cs
  • src/AtomUI.Desktop.Controls/TimePicker/TimeView/DateTimePickerPanel.cs
  • src/AtomUI.Desktop.Controls/TimePicker/TimeView/TimeView.cs
  • src/AtomUI.Desktop.Controls/TimePicker/TimeView/TimeViewCell.cs
  • src/AtomUI.Desktop.Controls/Primitives/InfoPickerInput/InfoPickerTextBox.cs 与 Themes/InfoPickerTextBoxTheme.axaml:TimePicker 输入框使用的 internal 子控件及其文本 presenter、padding 基础视觉。

职责边界:

  • 控件主文件保留 public/protected API、Avalonia 属性注册、事件和主要生命周期入口。
  • Theme 文件负责静态视觉结构、template part、selector 和资源绑定。
  • Token 文件只提供组件视觉变量,不保存实例状态。
  • Gallery 文件只展示用法和示例,不作为运行时逻辑 owner。

3. 核心类职责

  • DateTimePickerPanel:布局面板,负责测量、排列、虚拟化或集合内容布局。
  • RangeTimePicker:控件核心或内部协作类型,维护 public surface 与主题可观察行为。
  • TimePicker:控件核心或内部协作类型,维护 public surface 与主题可观察行为。
  • TimePickerPresenter:模板协作类型,承载内容展示、宿主或视觉边界。
  • InfoPickerTextBox:internal AbstractTextInput 输入子控件,使用 StyleVariant=Borderless 表达无 chrome 语义;TimePicker/RangeTimePicker 主题只负责 picker 专用内容和布局。
  • TimePickerToken:控件 Token scope,负责从全局 token 派生控件语义变量。
  • TimeView:控件核心或内部协作类型,维护 public surface 与主题可观察行为。
  • TimeViewCell:集合项、节点或容器类型,承载单项状态和模板协作。
  • TimePickerLangResourceKind:稳定的本地化 Catalog enum;三个 XLIFF 文件提供随模块发布的内置翻译,生成器负责编译资源表和 XAML 扩展。

核心协作规则:

  • 控件实例是 public API 和运行时状态 owner。
  • Template part 是视觉协作对象,生命周期必须受 OnApplyTemplate 或模板加载流程管理。
  • 数据对象、选项对象、任务对象或节点对象只保存业务数据,不应反向持有不可释放的视觉对象。
  • 弹层、窗口、计时器、异步 loader 和全局管理器必须有明确关闭、解绑或释放路径。

4. 状态与数据流

TimePicker 的状态流遵循下面路径:

Public API / ItemsSource / Command / Event
  -> 控件实例状态
  -> internal state / effective state / pseudo-class
  -> template part property / AXAML selector
  -> renderer / popup / adorner / Gallery observable behavior

源码中的状态入口按以下语义维护:

  • 内容与数据:IsShowHeader、ItemFormat、ItemHeight。
  • 选择与集合:RangeEndSelectedTime、RangeStartSelectedTime、SelectedTime、SelectorRowCount。
  • 交互与状态:IsNeedConfirm、IsShowNow、ShouldLoop。
  • 其他稳定入口:ClockIdentifier、DefaultTime、MinuteIncrement、PanelType、RangeEndDefaultTime、RangeStartDefaultTime、SecondIncrement。

维护要求:

  • SelectedTime 必须保持默认 TwoWay 绑定和 Avalonia data validation 能力,与 RangeTimePicker 的范围选择属性保持一致。
  • 外部设置的 Avalonia 属性必须在模板应用前后保持一致。
  • 集合、选择、展开、过滤、分页、上传任务或异步 loader 必须能处理 reset、replace 和 clear。
  • 伪类和 internal state 必须从单一 owner 推导,避免双向同步导致循环更新。
  • overview.md 的 API 契约说明应与源码实际状态流一致。

5. 生命周期与模板接入

生命周期规则:

  • 构造阶段只注册必要状态,不依赖 template part。
  • 模板应用时获取 part、建立事件订阅和绑定,并先释放旧 part 订阅。
  • 控件卸载、弹层关闭、窗口关闭、集合替换或 container recycle 时释放事件订阅和资源宿主。
  • DynamicResource、TokenResourceBinder 或 C# binding 必须有明确 owner 和释放点。
  • Browser 和 Desktop 宿主下的主题加载顺序不得影响 public API 语义。

稳定 template part 接入点:

  • PART_ButtonsFrame:承载用户触发入口、导航或关闭动作。
  • PART_ButtonsLayout:承载用户触发入口、导航或关闭动作。
  • PART_ConfirmButton:承载用户触发入口、导航或关闭动作。
  • PART_ContentPresenter:展示用户内容、文本、图标或模板化数据。
  • PART_FirstSpacer:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_HeaderText:承载文本输入、过滤、显示或编辑入口。
  • PART_HourHost:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_HourSelector:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_MainFrame:承载根视觉、边框、背景或尺寸基线。
  • PART_MainLayout:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_MinuteHost:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_MinuteSelector:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_NowButton:承载用户触发入口、导航或关闭动作。
  • PART_PeriodHost:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_PeriodSelector:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_PickerContainer:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_RootLayout:承载根视觉、边框、背景或尺寸基线。
  • PART_SecondHost:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_SecondSelector:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_SecondSpacer:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_ThirdSpacer:稳定模板协作入口,重命名前必须同步主题和实现。
  • PART_TimeView:稳定模板协作入口,重命名前必须同步主题和实现。

6. 交互与事件处理

TimePicker 的交互事件应从输入源收敛到控件级语义事件:

  • Pointer、keyboard、focus 和 command 事件不应绕过 Avalonia 基础控件语义。
  • 没有弹层职责的路径不应引入额外 popup 或全局输入捕获。
  • 非集合控件不应通过隐藏集合状态模拟业务数据。
  • 值提交或命令触发必须保持继承控件的事件顺序。

当前没有抽取到控件专属 public 事件;交互语义主要通过继承事件、命令、属性变化和 Gallery 可观察行为体现。

7. 内部算法与关键流程

维护者需要重点关注以下流程:

  • API 默认值到 effective state 的归一。
  • Template part 重新应用时的状态回放。
  • 主题资源、Token 和 SharedToken 计算后的视觉更新。
  • 内容、命令和视觉状态在模板节点之间的同步。
  • 状态变化时避免创建不必要的视觉对象、订阅或动画对象。

输入宽度维护规则:

  • TimePicker 的默认输入预留宽度以 ClockIdentifier、AM/PM 文本和字体对应的最宽格式化时间,与 AtomUI 定义的单选输入基线两者的较大值为准。RangeTimePicker 使用同一时间格式宽度与范围输入基线的较大值。
  • 单选和范围输入基线是 AtomUI 的稳定视觉契约,由本地化默认提示文本、字体和输入结构共同计算;它不依赖外部项目的文件路径、私有实现或源码快照。
  • PlaceholderText 和 SecondaryPlaceholderText 不参与 PreferredInputWidth / PreferredWidth 计算;placeholder 只能在已预留的输入内容区域内显示,超出时由文本呈现层使用 ellipsis 省略,不能反向撑大控件默认宽度。
  • Text 和 SecondaryText 只表达当前显示值或 hover preview,不作为 PreferredInputWidth / PreferredWidth 的计算来源。
  • ClockIdentifier、AM/PM 文本和字体变化会重新计算格式预留宽度和 AtomUI 默认输入基线;选中值、hover 值和范围端点切换不得改变预留宽度。
  • Width 显式设置或 HorizontalAlignment=Stretch 时,控件应交给外部布局系统决定实际宽度,不再强制内部预留宽度。
  • 范围选择的两端输入使用同一个格式预留宽度,RangePickerIndicator 和 popup placement 只跟随稳定输入框 bounds,不反向驱动输入框测量。
  • 范围输入模板的内部 AddOnDecoratedBox 和 content presenter 必须在控件内部 stretch;范围整体测量以 base.MeasureOverride 的完整宽度为基础,只替换两端输入框宽度为 PreferredWidth,不得重新手算 padding、spacing、icon 或 add-on 宽度。

实现文档不逐行解释私有方法。若某个私有算法成为稳定维护入口,应在本节补充算法不变量,而不是把代码复述为说明书。

8. 资源、性能与 AOT 边界

资源和 AOT 约束:

  • 不通过运行时反射扫描 public API、Token 或 Gallery 示例数据。
  • 不把可静态声明的模板结构迁移到 C# 动态创建。
  • 异步加载、上传、弹层和窗口生命周期必须能取消或释放。
  • 缓存对象必须与控件、窗口、弹层或数据 owner 生命周期一致。
  • Source generator 生成文件不手工编辑;需要修改时改输入源或 generator。

性能边界:

  • 控件应优先复用 Avalonia 原生虚拟化、模板绑定和资源系统。
  • 避免为每次状态变化创建不必要的视觉对象、订阅或动画对象。
  • 大集合控件必须保证 container recycle 后不会泄漏旧 item 状态。

9. 维护不变量

维护 TimePicker 时不得破坏:

  • Public API、默认值、事件顺序和 Gallery 可观察行为。
  • Template part 名称、ControlTheme key、伪类和资源 key。
  • 旧 template part、事件订阅、Popup/Flyout/Window host 和 collection view 的释放路径。
  • Light/Dark、Browser/Desktop 和不同 SizeType 下的主题一致性。
  • 控件文档、源码 public surface、Token 类型或生成数据与源码契约的一致性。

10. 测试与验证

推荐验证:

  • 纯文档改动运行 git diff --check 并检查相对链接。
  • 控件 API 或行为变更运行对应 tests/AtomUI.Desktop.Controls.Tests 或专用包测试。
  • DataGrid 相关变更运行 tests/AtomUI.Desktop.Controls.DataGrid.Tests。
  • Gallery 示例或源码片段变更运行 tests/AtomUIGallery.Tests。
  • AOT、生成器或动态数据路径变更按 Gallery NativeAOT 发布流程验证。