7.7 KiB
DatePicker 桌面版实现原理
本文说明当前实现架构。关联 架构、共享模型、 Token、Semantic Part与 Changelog。
1. 实现定位
输入 owner、编辑会话、公共日期面板和 Popup 分层。旧 CalendarView/CalendarItem/按钮渲染链已删除。 Picker 不生成 CellModel 或复制范围、焦点与导航算法。
2. 源码职责结构
| 子域 | 职责 |
|---|---|
| 输入控件 | 提交属性、显示文本、Clear/Reset、Form、验证和打开状态。 |
| Presenter | 日期面板、时间与 Footer 的模板接线。 |
| 编辑会话 | 候选日期时间、活动端点、确认进度与操作转换。 |
| Formatting | 默认/显式格式、周/季度文本、12/24 小时制与输入宽度。 |
| Theme/Token/Localization | 产品视觉与专属资源。 |
| DateViewer 模块 | 日期单位、拓扑、范围、导航、Cell、面板主题。 |
这些职责已由 DatePicker、DatePickerPresenter、DatePickerEditSession 与 DateViewer 子域实现。日期算法不依赖 Picker FormattingHelper,格式化不迁入日期内核。
3. 核心角色
输入控件拥有外部提交值,编辑会话拥有打开期间候选值、活动端点和本次确认进度。Presenter 接线,不持有日期副本。 托管面板只读取会话投影;TimeView 报告时间操作,由会话组合活动日期。 Footer 报告操作,确认策略不进入 DatePanel。范围已有两端点只代表提交值完整,不代表本次打开后的两个端点已经确认完成。
4. 状态与数据流
外部属性 → 会话初始化/输入更新 → 约束与候选投影 → 日期/时间呈现。 用户操作 → 会话转换 → 候选快照 → 预览/确认 → 输入写回 → 绑定/Form 通知。 范围编辑期间的输入文本可投影候选与 Hover;弹层关闭后只能从输入 owner 的已确认值格式化,不能保留 Presenter 的候选或旧 Hover 文本。
范围快照原子包含端点、活动端点与确认进度,左右 Panel 不 TwoWay 同步。 范围编辑每确认一个端点后由编辑会话翻转活动端,Presenter 再把该活动端投影到输入 owner、Popup 定位和 DatePanel。 活动端切换和约束等配置变化只能更新对应状态,不能从 Presenter 的候选属性重建已确认端点;外部端点属性变化才更新会话中的提交值。 托管面板读取会话活动端,而不是从提交值是否为空或外层输入焦点临时推断。 活动端翻转只改变后续编辑目标,不能清空已提交端点、候选端点、输入框可视文本或 DateViewer 上的已选范围状态。 面板唯一拥有 Hover、焦点和浏览状态,会话只消费。原始确认请求与有效时间确认策略分别解释, 不用属性备份或抑制 flag;外部可观察行为仍须按旧基线验收。
5. 组合结构模型
| 区域 | 组合与 owner |
|---|---|
| 输入 | InputControlFrame 与 InfoPicker 输入,输入控件拥有。 |
| Popup | Presenter 内容根,由 Popup/输入 owner 管理。 |
| 日期 | DateViewer 或 RangeDateViewer,面板管理 DatePanel/Cell。 |
| 时间 | TimeView,会话拥有候选时间。 |
| Footer | Today/Now/Confirm,会话决定可用性与提交。 |
这是当前职责树;真实 Name/route 来自现有 AXAML。布局、可见性与固定关系留在主题,各子控件维护自己的模板。 单/双面板与时间是配置,不再用 DualMonthCalendar 继承链表达。 Compact 日期面板使用共享的 36 单位日期行与正文留白,Presenter 为日期、月和年面板分配 301 单位高度, 季度面板保持 86 单位;托管 DateViewer 隐藏自己的默认 Today 页脚,Picker Footer 仍由 Presenter 负责。
6. 生命周期与模板接入
重建模板先解除旧节点事件/可替换绑定,再回放完整会话投影,不重置提交值。 Popup 隐藏造成的视觉树 detach 不解除仍会复用的模板按钮、日期和时间事件;模板更换时解除旧节点事件,视觉树 detach 只释放日期宿主和窗口级订阅。 打开先确定单位、约束、活动端点,再应用候选、锚点、时间和按钮。重开基于当前输入初始化。 范围普通关闭恢复已确认端点并清除未确认候选与预览;detach/宿主失效释放日期宿主与 Popup host,即使 pinned 也执行。
7. 交互与通知
日历指针跟踪只消费当前日历所在活输入根的事件,并把原始位置转换到月份/年份面板使用的 TopLevel 坐标系。本窗口退出或日历卸载时统一结束月份悬停与范围预览,其他窗口的坐标不更新日历选择过程。
| 操作 | 责任 |
|---|---|
| 浏览/下钻 | DatePanel,不提交输入。 |
| Hover | 面板预览,会话按原时间来源生成活动端候选,输入 owner 使用固定端与 hover 端按日期顺序更新两侧预览文本。 |
| 选择/时间编辑 | 更新活动候选,决定立即或等待确认。 |
| 部分确认 | 按原时机写回刚确认端点、会话自动切换活动端并同步活动输入。 |
| 最终确认 | 校验、按原规则排序、写回并关闭。 |
| 范围 Cancel/关闭 | 丢弃未确认候选与预览,保留已确认的单端或完整范围;只有显式 Clear 清空范围提交值。 |
Pointer、键盘、滚轮与 Automation 共用面板算法,事件消费由 Picker 策略决定。 确认不是 Selected 的直接转发,不能提前关闭或重复提交。
8. 算法与关键流程
单位归一、范围视觉与导航由共享内核提供;Formatting 保留输入格式和宽度。 仅有终点值时,打开按该端点与面板周期定位;起点不能通过同步改写浏览区域。 已有完整范围重新打开时,本次确认进度从当前活动端开始重建;选择当前端点后保持弹层打开并转移到另一端, 只有另一端也在本次编辑中确认后才执行最终确认和关闭。 需要确认的范围每次 Confirm 只写回当前端点。普通关闭、Escape、light-dismiss 或宿主消失时会话恢复已确认值,输入文本同步恢复;单端已确认值在重开后仍可见,默认编辑缺失端,显式点击某端则以点击端为活动端。 完整范围双面板打开时以 start 所在周期作为左面板锚点;active end 不能倒推一个周期来改变面板锚点。 Today/Now/Confirm 共用有效约束,但时间来源和确认行为按原规则。 视觉顺序与候选端点分离,范围预览不能改写真实值,反向 hover 只改变预览文本和预览高亮的显示顺序。 选择/确认一个端点时必须结束旧 Hover 预览,避免旧 hover 在活动端翻转后被解释成新活动端的预览并覆盖 viewer 已提交选中态。
9. 资源、性能与 AOT
面板资源由 DateViewer 拥有,Picker 保留真实静态依赖,不依靠全量 Gallery。 会话不持有 DynamicResource;主题消费资源,非 Visual 资源对象有 scoped lifecycle。 测量首开/重开、Hover、双面板、时间与真实 Gallery,保持相同功能与动效。
10. 维护不变量
唯一候选 owner、唯一日期引擎、统一激活防线,无旧 Calendar 回退、双向环或延迟刷新。 保持外部越界值、输入宽度、native validation、所有关闭路径及端点确认。 父主题不跨入子模板修补视觉,共享基础设施不进行无关改造。
11. 测试与验证
基线包含逐步值、文本、焦点、面板、活动端点、确认、通知与 Popup 状态;新旧同序列比较。 最终工作树分别验证模块、真实 Gallery、资源释放和 AOT;未执行项目不得写成已通过。 共享验收要求的证据不能由普通单测替代。