227 lines
9.3 KiB
Markdown
227 lines
9.3 KiB
Markdown
# Epic 2 Details: PropertyGrid - Auto-Generated Configuration UI
|
||
|
||
### Epic Goal
|
||
|
||
实现 PropertyGrid 控件,这是控件库的核心价值所在。该控件能够从数据模型自动生成属性编辑 UI,支持 string, int, double, bool, enum, DateTime 六种基础类型,以及属性分组、只读属性等功能。Epic 完成后,上位机和 AI 应用开发者可以快速构建配置界面,无需手写大量 XAML。该 Epic 在开发过程中会产出必要的布局控件(如 TwoColumnLayout)和工具类。
|
||
|
||
---
|
||
|
||
### Story 2.1: 实现 2xN Layout 布局控件
|
||
|
||
**As a** 控件库开发者,
|
||
**I want** 创建一个专用的 2xN Layout 布局控件(左侧标签,右侧编辑器),
|
||
**so that** PropertyGrid 可以使用统一的布局来呈现属性列表,且这个布局控件可以复用到其他需要"标签-值"配对的场景。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. 在 `Penguin.AvaloniaUI/Layouts/` 下创建 `TwoColumnLayout.cs` 控件类:
|
||
- 继承自 `Panel` 或使用 `Grid` 作为基础
|
||
- 支持添加多行,每行包含左侧标签(Label)和右侧内容(Content)
|
||
|
||
2. 提供简单的 API 来添加行:
|
||
- 可以通过 Items 集合添加行
|
||
- 每个 Item 包含 Label(string)和 Content(Control)
|
||
|
||
3. 布局行为:
|
||
- 左列(标签列)宽度固定或自适应最长标签的宽度
|
||
- 右列(内容列)占据剩余空间
|
||
- 行与行之间有合适的垂直间距(如 8px)
|
||
- 标签垂直对齐到内容控件的中心或顶部
|
||
|
||
4. 响应主题系统:
|
||
- 标签文本颜色使用 `TextSecondary`
|
||
- 背景色透明或使用 `Surface`
|
||
|
||
5. 在 Example 中创建测试页面,演示 TwoColumnLayout 的使用:
|
||
- 至少 5 行测试数据
|
||
- 包含不同类型的右侧内容(TextBox, CheckBox, ComboBox)
|
||
|
||
6. 布局在窗口缩放时应正确响应,不出现重叠或错位
|
||
|
||
---
|
||
|
||
### Story 2.2: 实现基础属性编辑器控件
|
||
|
||
**As a** 控件库开发者,
|
||
**I want** 为 PropertyGrid 支持的 6 种属性类型创建或选择合适的编辑器控件,
|
||
**so that** PropertyGrid 可以根据属性类型自动选择正确的编辑器。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. 为以下属性类型选择或创建编辑器控件:
|
||
- **string**: 使用 Avalonia 的 `TextBox`
|
||
- **int/double**: 使用 Avalonia 的 `NumericUpDown`(如果 Semi 或 Avalonia 提供)或自定义数字输入框
|
||
- **bool**: 使用 Avalonia 的 `CheckBox`
|
||
- **enum**: 使用 Avalonia 的 `ComboBox`,Items 为枚举值列表
|
||
- **DateTime**: 使用 Avalonia 的 `DatePicker` 和 `TimePicker`(或组合控件)
|
||
|
||
2. 所有编辑器控件应响应主题系统,使用语义化颜色
|
||
|
||
3. 编辑器控件应支持基础的数据绑定:
|
||
- 双向绑定(TwoWay Binding)
|
||
- 支持 `INotifyPropertyChanged` 机制
|
||
|
||
4. 在 Example 中创建测试页面,演示所有 6 种编辑器:
|
||
- 每种编辑器独立展示
|
||
- 显示当前绑定的值(使用 TextBlock)
|
||
- 修改编辑器后,绑定的值应自动更新
|
||
|
||
5. 编辑器控件应具备基础的验证提示能力:
|
||
- 依赖 Avalonia 的内置验证机制(如 DataValidationErrors)
|
||
- 验证失败时显示红色边框或错误图标
|
||
|
||
---
|
||
|
||
### Story 2.3: 实现 PropertyGrid 核心逻辑和数据模型
|
||
|
||
**As a** 控件库开发者,
|
||
**I want** 定义 PropertyGrid 的数据模型和核心逻辑,支持从对象反射属性信息,
|
||
**so that** PropertyGrid 可以自动生成属性列表,为后续的 UI 呈现做好准备。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. 在 `Penguin.AvaloniaUI/Controls/PropertyGrid/` 下创建以下类:
|
||
- `PropertyGrid.cs`: PropertyGrid 主控件类
|
||
- `PropertyItem.cs`: 表示单个属性的数据模型,包含:
|
||
- Name(属性名称)
|
||
- Value(属性值)
|
||
- Type(属性类型)
|
||
- IsReadOnly(是否只读)
|
||
- Category(分组类别,可选)
|
||
- Description(描述,可选)
|
||
|
||
2. PropertyGrid 提供 `SelectedObject` 属性(依赖属性):
|
||
- 接受任意对象
|
||
- 当 SelectedObject 变化时,自动通过反射解析对象的属性
|
||
|
||
3. 反射逻辑:
|
||
- 获取对象的所有 public 属性
|
||
- 过滤掉不应显示的属性(如索引器、只写属性)
|
||
- 支持通过 Attribute 控制属性的显示(可选,如 `[Browsable(false)]`)
|
||
|
||
4. 将反射得到的属性转换为 `PropertyItem` 集合:
|
||
- 存储在 PropertyGrid 的内部集合中
|
||
- 支持属性变化通知(使用 ReactiveUI 或 INotifyPropertyChanged)
|
||
|
||
5. 创建单元测试,验证反射逻辑:
|
||
- 测试包含 6 种基础类型的测试对象
|
||
- 验证属性数量、名称、类型正确解析
|
||
- 验证只读属性标记正确识别
|
||
|
||
6. 在 Example 中创建测试页面,验证数据模型:
|
||
- 定义一个包含 6 种类型属性的测试类
|
||
- 将测试对象赋值给 PropertyGrid.SelectedObject
|
||
- 使用调试输出或 ListBox 显示解析出的 PropertyItem 列表(暂时不渲染为编辑器)
|
||
|
||
---
|
||
|
||
### Story 2.4: 实现 PropertyGrid UI 呈现和属性编辑
|
||
|
||
**As a** PropertyGrid 的用户(开发者),
|
||
**I want** PropertyGrid 能够自动将属性列表渲染为可编辑的 UI,
|
||
**so that** 我可以直接使用 PropertyGrid 控件,无需手动编写属性编辑界面。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. PropertyGrid 使用 TwoColumnLayout 呈现属性列表:
|
||
- 左列显示属性名称(PropertyItem.Name)
|
||
- 右列根据属性类型显示对应的编辑器控件
|
||
|
||
2. 属性类型到编辑器的映射逻辑:
|
||
- string → TextBox
|
||
- int/double → NumericUpDown
|
||
- bool → CheckBox
|
||
- enum → ComboBox(自动填充枚举值)
|
||
- DateTime → DatePicker/TimePicker
|
||
|
||
3. 编辑器控件与属性值双向绑定:
|
||
- 修改编辑器时,PropertyItem.Value 自动更新
|
||
- PropertyItem.Value 变化时,编辑器自动刷新(如果 SelectedObject 外部修改)
|
||
|
||
4. 只读属性的处理:
|
||
- 如果 PropertyItem.IsReadOnly = true,编辑器应禁用(IsEnabled = false)或显示为只读文本
|
||
|
||
5. 在 Example 中创建完整的 PropertyGrid 演示页面:
|
||
- 定义一个包含所有 6 种类型的配置类(如 `DemoSettings`)
|
||
- 使用 PropertyGrid 绑定到 DemoSettings 实例
|
||
- 在页面底部显示当前配置对象的 JSON 或字符串表示,验证属性编辑生效
|
||
|
||
6. PropertyGrid 应响应主题切换,所有编辑器控件使用主题颜色
|
||
|
||
7. PropertyGrid 的默认宽度应至少 400px,高度自适应内容(或支持滚动)
|
||
|
||
---
|
||
|
||
### Story 2.5: 实现属性分组功能
|
||
|
||
**As a** PropertyGrid 的用户(开发者),
|
||
**I want** PropertyGrid 支持将属性按类别分组显示,
|
||
**so that** 当属性数量较多时,配置界面更清晰易读。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. PropertyItem 支持 Category 属性(字符串):
|
||
- 如果未指定 Category,默认分组为 "General" 或不分组
|
||
|
||
2. 支持通过 Attribute 在数据模型上标记分组:
|
||
- 例如使用 `[Category("Network")]` 标记属性
|
||
- 反射逻辑自动读取 Category Attribute
|
||
|
||
3. PropertyGrid 按 Category 对 PropertyItem 进行分组:
|
||
- 相同 Category 的属性显示在一起
|
||
- 分组之间有视觉分隔(如分隔线或分组标题)
|
||
|
||
4. 分组标题的呈现:
|
||
- 使用稍大的字体或加粗显示分组名称
|
||
- 分组标题使用 `TextPrimary` 颜色
|
||
- 分组标题上方有一定的间距(如 16px)
|
||
|
||
5. 支持可选的分组折叠功能(可选,MVP 可以暂时不实现折叠):
|
||
- 如果时间允许,分组可以展开/折叠
|
||
- 默认所有分组展开
|
||
|
||
6. 在 Example 中扩展演示页面:
|
||
- DemoSettings 类的属性使用 `[Category]` 标记,分为至少 2 个分组(如 "General"、"Advanced")
|
||
- PropertyGrid 正确显示分组
|
||
- 验证分组标题和属性的视觉层次清晰
|
||
|
||
---
|
||
|
||
### Story 2.6: PropertyGrid 集成测试和优化
|
||
|
||
**As a** 控件库开发者,
|
||
**I want** 对 PropertyGrid 进行全面测试和性能优化,
|
||
**so that** 确保 PropertyGrid 在真实场景下稳定可用,并满足性能要求(50 属性 < 200ms)。
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. 创建综合测试类,包含 50 个属性,覆盖所有支持的类型和分组
|
||
|
||
2. 在 Example 中使用该测试类验证 PropertyGrid:
|
||
- PropertyGrid 正确渲染所有 50 个属性
|
||
- UI 生成时间 < 200ms(符合 NFR5)
|
||
- 所有编辑器可正常交互,值绑定正确
|
||
|
||
3. 测试边缘情况:
|
||
- SelectedObject 为 null 时,PropertyGrid 显示空状态或提示信息
|
||
- 对象属性在运行时变化时,PropertyGrid 能够正确响应(如果支持动态刷新)
|
||
- 只读属性不能编辑
|
||
|
||
4. 主题切换测试:
|
||
- 在浅色和暗色主题下,PropertyGrid 的视觉呈现正确
|
||
- 主题切换时,PropertyGrid 无闪烁或错位
|
||
|
||
5. 性能优化(如果需要):
|
||
- 如果初始加载超过 200ms,使用虚拟化或延迟加载优化
|
||
- 如果属性数量超过 100,考虑使用 `VirtualizingStackPanel`
|
||
|
||
6. 修复 Example 测试中发现的所有 Bug
|
||
|
||
7. 在 README 中添加 PropertyGrid 的使用文档:
|
||
- 基础用法示例(XAML 和代码)
|
||
- 支持的属性类型列表
|
||
- 如何使用 Category Attribute
|
||
- 已知限制(如暂不支持嵌套对象)
|
||
|
||
---
|
||
|