Files

12 KiB
Raw Permalink Blame History

SearchEdit 桌面版实现原理

本文档描述 SearchEdit 桌面版的内部模板组合、搜索请求事件流、按钮与输入框布局、状态传递和维护不变量。共享输入分层见 输入控件共享架构设计,公共设计与 API 契约见 SearchEdit 桌面版架构设计,Control Token 分层见 AtomUI Control Token 设计规范,变化记录见 SearchEdit Changelog。

1. 实现定位

SearchEdit 的实现以 LineEdit / AbstractTextInput 为文本输入内核,以 InputControlFrame 为输入表面边界;AtomUI 在主题和少量内部类中加入搜索按钮、按钮状态传递和一体化布局。实现文档聚焦 SearchEdit 增强层,不重新说明 LineEdit 的文本编辑、清除按钮、Form、CompactSpace 和 TextPresenter 细节。

2. 源码文件结构

主要源码:

  • src/AtomUI.Desktop.Controls/Input/SearchEdit.cs:public API、默认 clear icon、模板接入、Enter 处理和搜索请求事件抛出。
  • src/AtomUI.Desktop.Controls/Input/SearchEditDecoratedBox.cs:内部输入壳体,转接 SearchEdit 属性并订阅搜索按钮 click。
  • src/AtomUI.Desktop.Controls/Input/SearchEditPanel.cs:搜索输入布局面板,负责左侧 AddOn、搜索按钮和内容框重叠边框排布。
  • src/AtomUI.Desktop.Controls/Input/Themes/SearchEditTheme.axaml:SearchEdit 根模板、文本区域、内部 action 和 focus/status selector。
  • src/AtomUI.Desktop.Controls/Input/Themes/SearchEditDecoratedBoxTheme.axaml:搜索按钮和输入框一体化模板。
  • src/AtomUI.Desktop.Controls/Input/Themes/SearchButtonTheme.axaml:搜索按钮在不同输入表面下的状态视觉。
  • src/AtomUI.Desktop.Controls/Input/Themes/SearchButtonTheme.cs:SearchButton Semantic Part typed theme。
  • src/AtomUI.Desktop.Controls/AutoComplete/AutoCompleteSearchEdit.cs:AutoComplete 搜索输入入口,复用 SearchEdit 搜索按钮属性。
  • src/AtomUI.Desktop.Controls/AutoComplete/AutoCompleteSearchEditBox.cs:AutoComplete 内部 SearchEdit box,使用 SearchEdit style key。

3. 核心类职责

SearchEdit 只增加搜索相关契约。它不重写 LineEdit 文本输入算法,也不管理搜索任务。RaiseSearchRequested() 是统一内部事件出口,负责在 IsOperating=false 时抛出包含查询文本快照和触发来源的 SearchRequested。

SearchEditDecoratedBox 是 SearchEdit 根模板中的组合布局节点。它继承 AddOnDecoratedBox,复用 InputControlFrame 的边框、圆角、effective status 和 CompactSpace 计算,并把 SearchButtonStyle、SearchButtonText、IsSearchButtonLoading 传给内部搜索按钮;它不重新计算 variant/status/error/warning/disabled。

搜索按钮直接使用 public Button,没有 SearchEdit 专用 Button 子类。Button 保留自己的 identity、Own Token、 loading、icon 和 wave 能力;SearchEditDecoratedBoxTheme 根据 owner 的 StyleVariant 与 effective status 把组合视觉 投射到 Button#PART_RightAddOn。

SearchEditPanel 是三段布局面板。它把左侧 AddOn 放在左侧,把搜索按钮放在右侧,把内容框扩展到搜索按钮左边框下方,以形成一体化边框。

4. 状态与数据流

搜索按钮属性流:

SearchEdit.SearchButtonStyle / SearchButtonText / SearchButtonTheme / IsOperating
      ↓ TemplateBinding
SearchEditDecoratedBox.SearchButtonStyle / SearchButtonText / SearchButtonTheme / IsSearchButtonLoading
      ↓ TemplateBinding
Button#PART_RightAddOn.ButtonType / Content / Theme / IsLoading

输入状态流:

AbstractTextInput.SizeType / StyleVariant / Status / IsEnabled
      ↓ TemplateBinding
SearchEditDecoratedBox
      ↓ frame state binding / selector
Button#PART_RightAddOn + PART_ContentFrame

DataValidationErrors、FormStatus 和 Status 在 AbstractTextInput 侧接入,InputControlFrame 消费这些稳定来源并计算 EffectiveStatus;SearchEdit 不维护第二套 error/warning 状态。

搜索事件流:

Button#PART_RightAddOn.Click
      ↓
SearchEditDecoratedBox.HandleSearchButtonClick
      ↓
OwningSearchEdit.RaiseSearchRequested(Button)
      ↓
if !IsOperating raise SearchRequested

SearchEdit.OnKeyUp(Enter) when IsSearchOnEnterEnabled && !Handled
      ↓
mark KeyUp handled
      ↓
SearchEdit.RaiseSearchRequested(EnterKey)
      ↓
if !IsOperating raise SearchRequested

IsOperating 同时进入 Button#PART_RightAddOn.IsLoading 和 RaiseSearchRequested() 的重复请求保护。该状态不参与文本值同步,也不改变 Form value。SearchRequestedEventArgs.Query 在抛出事件前读取当前 Text,避免事件处理期间文本变化影响本次请求语义。

5. 生命周期与模板接入

SearchEdit.OnInitialized() 在未设置 ClearIcon 时写入默认 CloseCircleFilled,与搜索输入常见清除语义保持一致。

SearchEdit.OnApplyTemplate() 调用基类 LineEdit 模板接入后,查找 PART_InputControlFrame 并设置 SearchEditDecoratedBox.OwningSearchEdit。该 owner 引用只用于把搜索按钮点击回调到 SearchEdit;它不改变 frame 的状态归一或视觉 ownership。

SearchEditDecoratedBox.OnApplyTemplate() 必须先解除旧 _searchButton.Click 订阅,再查找新的 PART_RightAddOn 并订阅 click。模板重新应用时不能保留旧按钮事件订阅。

SearchEditDecoratedBox.NotifyAddOnBorderInfoCalculated() 把 RightAddOnBorderThickness 设置为当前 BorderThickness。搜索按钮右侧边框由 decorated box 统一计算,保证搜索按钮与输入框圆角、边框折叠和 CompactSpace 状态一致。

6. 交互与事件处理

搜索按钮点击和 Enter KeyUp 是 SearchEdit 的两个搜索入口。IsSearchOnEnterEnabled=true 且 Enter 事件尚未 handled 时,SearchEdit 先将事件标记为 handled,再进入统一搜索请求管线,避免上层默认按钮重复响应。关闭该属性时 SearchEdit 不消费 Enter 键。

SearchEdit 不会自动提交 Form,也不会在 TextChanged 时触发搜索。IsOperating=true 时按钮和 Enter 仍保持既有视觉与键盘所有权,但不会再次抛出 SearchRequested。

清除按钮、密码 reveal、文本选择、光标移动、复制粘贴和滚动仍由 LineEdit / Avalonia TextBox 路径处理。SearchEdit 不应在搜索按钮事件中直接修改这些状态。

Disabled 状态通过模板传递到 SearchEditDecoratedBox 和 Button#PART_RightAddOn。搜索按钮不应在 disabled 时成为独立可点击入口。

7. 内部算法与关键流程

7.1 SearchEditPanel 布局

SearchEditPanel.ArrangeOverride() 使用最终尺寸安排三个区域:

  1. 左侧 AddOn 使用自身 DesiredSize.Width,高度为 finalSize.Height。
  2. 搜索按钮使用自身 DesiredSize.Width,放在右侧,安排高度为 finalSize.Height。
  3. 内容框宽度为总宽度减去左右 AddOn 宽度,再加回搜索按钮左边框厚度,使输入框右边框与按钮左边框重叠成单线。

该布局要求搜索按钮的可视 frame 高度跟随实际安排高度。Custom 高度下,如果按钮模板仍按 Button 自身默认 Height 绘制 frame,会出现按钮边框和输入框边框不齐。

7.2 搜索按钮高度同步

SearchEditDecoratedBoxTheme.axaml 将 Button#PART_RightAddOn.Height 绑定到 SearchEditDecoratedBox.Bounds.Height。Button 模板中的 Frame 使用 Button Height 绘制,因此该绑定使 Custom 高度和内置尺寸下搜索按钮 frame 与输入框 frame 保持同高。

该同步属于 SearchEdit 组合模板职责,不应通过 Gallery 示例、固定 magic height 或 SearchButton 全局主题覆盖来修补。

7.3 搜索按钮 style 映射

SearchButtonStyle=Default 时,搜索按钮使用 ButtonType=Default。Outlined 表面下,按钮 hover/pressed 会提升 z-index,避免按钮边框被内容框遮住。

SearchButtonStyle=Primary 时,搜索按钮使用 ButtonType=Primary。Outlined 表面下按钮保持激活 z-index。

非 Outlined 表面下,搜索按钮统一映射为 ButtonType=Text,让 Filled、Borderless 和 Underlined 输入表面保持轻量视觉。

7.4 搜索图标状态

搜索图标由 SearchEditDecoratedBoxTheme.axaml 直接创建为带 skip-status class 的 SearchOutlined。Button 没有 SearchEdit 专用 OnPropertyChanged() 路径;按钮基础前景由 Button theme 管理,输入 status 组合视觉由 SearchEdit owner theme 的 selector 管理。

8. 资源、性能与 AOT 边界

SearchEdit 不依赖运行时反射发现模板结构。跨模板协作使用固定 template part、TemplateBinding、selector 和 owner 引用完成。

资源和生命周期边界:

  • 搜索按钮 click 订阅必须在重新套用模板时解绑旧实例。
  • OwningSearchEdit 只保存当前模板 owner,不创建全局订阅。
  • 搜索按钮高度同步使用 XAML binding,不在布局过程中写本地 Height 值。
  • 搜索按钮状态不创建异步任务;业务异步状态由外部设置 IsOperating。
  • SearchEdit 有独立 Control identity、没有 Own Token;运行时状态不得进入 Token schema。
  • SearchEditTokenResource 只读取 SearchEdit 专属组合值;InputControlFrameTheme 读取 SharedToken 表达通用输入表面,Button 基础视觉继续显式读取 ButtonTokenResource。

AOT 边界:

  • SearchEdit identity、强类型 TokenResource 和三个独立主题叶子通过生成 descriptor/asset manifest 注册。
  • AutoCompleteSearchEditBox 使用显式 StyleKeyOverride 复用 SearchEdit 主题。
  • API 与 Token 契约由控件文档、源码 public surface、Token 类型或生成数据维护,不依赖运行时反射扫描。

9. 维护不变量

内部重构必须保持以下不变量:

  • 搜索按钮和 Enter 键只能通过 SearchEdit.RaiseSearchRequested() 抛出 SearchRequested。
  • IsOperating=true 必须阻止重复搜索请求,并继续驱动按钮 loading。
  • IsSearchOnEnterEnabled=false 时不得消费 Enter;handled Enter 不得产生搜索请求。
  • SearchRequestedEventArgs.Query 和 Trigger 必须准确反映触发时的文本与输入来源。
  • 搜索按钮和内容框的边框必须在同一布局高度下绘制。
  • SearchEditPanel 的按钮左边框重叠算法不能破坏单线边框视觉。
  • 搜索按钮必须接收 SearchEdit 的 SizeType、IsEnabled 和 loading;StyleVariant 与 EffectiveStatus 的组合视觉由 shared frame theme 投射。
  • 搜索按钮必须保持 public Button 类型;不得重新引入借用 LineEdit 或 Button identity 的 internal SearchButton。
  • SearchButtonTheme 必须继续作为强类型 Semantic Part Theme,并允许实例级替换;其状态色只能消费 frame 投射的有效状态,不得成为 validation owner。
  • 搜索按钮右侧外部 AddOn 位置不可被用户内容替代。
  • InnerRightContent、clear、reveal 和文本 presenter 的绑定仍由 LineEdit 模板路径维护。
  • AutoCompleteSearchEdit 复用 SearchEdit 视觉时不能绕过 SearchEdit 搜索按钮契约。

10. 测试与验证

验证范围:

  • SearchEditBehaviorTests 覆盖默认 Enter 行为、按钮与 Enter 触发来源、查询文本快照和搜索中重复请求抑制。
  • SearchEditLayoutTests 覆盖 Custom 和内置 SizeType 下搜索按钮 frame 与输入框 frame 高度一致。
  • CustomizableSizeTypeContractTests 覆盖 SearchEdit 和 AutoCompleteSearchEdit 的 customizable size contract。
  • LineEditShowCasePageTests 覆盖 LineEdit / SearchEdit Gallery 示例结构、SearchEdit SizeType 示例和 snapshot。
  • Gallery 走查 SearchEdit 基础、状态、尺寸、Custom、loading、disabled、内部右侧内容和 AutoComplete 搜索示例。
  • 文档改动运行 git diff --check,并检查相对链接存在。