docs: update architecture

This commit is contained in:
2025-10-16 15:08:42 +08:00
parent 6b4c227d3c
commit 6c17e02731
15 changed files with 4889 additions and 18 deletions

2439
docs/architecture.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
# Coding Standards
本节定义**最小但关键**的编码规范,专注于项目特定的规则,防止常见错误。这些规范将被开发者和 AI Agent 遵循。
---
### Critical Rules
以下规则是**强制性**的,违反将导致代码审查不通过:
- **命名空间组织:** 所有控件必须放在 `Penguin.AvaloniaUI.Controls.*` 命名空间下,布局控件在 `Penguin.AvaloniaUI.Layouts`,主题在 `Penguin.AvaloniaUI.Themes`。不得跨命名空间引用内部实现细节。
- **依赖属性定义:** 所有公开的可绑定属性必须定义为 `StyledProperty`,使用 `AvaloniaProperty.Register<>` 注册,不得使用字段或普通属性。
- **XAML 资源引用:** 所有颜色、字体、间距必须从主题资源字典引用,禁止硬编码颜色值(如 `#FFFFFF`)。使用 `{DynamicResource TextPrimary}` 而非 `{StaticResource}`,确保主题切换生效。
- **ReactiveUI 集成:** ViewModel 必须继承 `ReactiveObject`,属性变化使用 `this.RaiseAndSetIfChanged(ref _field, value)`,不得直接触发 `PropertyChanged` 事件。
- **错误处理:** 公开 API 方法必须验证参数(如 `ArgumentNullException`),内部方法可使用 `Debug.Assert`。不得吞没异常(空 catch 块)。
- **XML 文档注释:** 所有 `public``protected` 成员必须有 XML 注释(`///`),包括 `<summary>``<param>``<returns>`。示例:
```csharp
/// <summary>
/// 应用指定的主题类型
/// </summary>
/// <param name="type">主题类型Light 或 Dark</param>
public static void ApplyTheme(ThemeType type)
```
- **禁止反射动态创建控件:** PropertyGrid 可以使用反射解析属性,但不得使用 `Activator.CreateInstance` 动态创建编辑器控件。使用工厂模式或字典映射。
- **主题资源命名约定:** 语义化颜色必须使用统一前缀:`TextPrimary`、`TextSecondary`、`BackgroundPrimary`、`SurfaceElevated`。不得使用 `Color1`、`MyBlue` 等非语义化命名。
---
### Naming Conventions
| 元素类型 | 命名规则 | 示例 |
|---------|---------|------|
| 控件类 | PascalCase功能名称 + 控件类型后缀(可选) | `PropertyGrid`, `TwoColumnLayout` |
| 依赖属性 | PascalCase属性名 + `Property` 后缀 | `SelectedObjectProperty` |
| 私有字段 | camelCase下划线前缀 | `_currentTheme` |
| 事件 | PascalCase动词过去式 | `ThemeChanged`, `PropertyValueChanged` |
| 方法 | PascalCase动词开头 | `ApplyTheme()`, `RefreshProperties()` |
| XAML 文件 | PascalCase与类名一致 | `PropertyGrid.axaml` |
| 测试方法 | PascalCaseMethodName_Scenario_ExpectedResult | `ApplyTheme_WithDarkTheme_ShouldUpdateResources()` |
---
### File Organization Rules
- **一个文件一个类**:每个控件类单独一个 `.cs` 文件,不得在同一文件中定义多个公开类(嵌套私有类除外)。
- **XAML 与代码分离**:控件的 XAML 模板放在 `Themes/` 子目录下,与类定义分离:
```
Controls/PropertyGrid/PropertyGrid.cs
Controls/PropertyGrid/Themes/PropertyGrid.axaml
```
- **数据模型独立文件**:数据模型类(如 `PropertyItem`, `GuideStep`)与控件类分离,放在同级目录下。
- **文件格式规范**:命名空间单独放在文件顶部,减少缩进层级:
```csharp
namespace Penguin.AvaloniaUI.Controls;
public class PropertyGrid : TemplatedControl
{
// 类实现
}
```
---
### Code Style (基于 .editorconfig)
项目使用 `.editorconfig` 强制执行以下规则:
```ini
root = true
[*.cs]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# C# 规则
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true:warning
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
[*.axaml]
indent_size = 2
```
**代码风格要点:**
- 使用 `var` 声明局部变量(如 `var count = 100;`
- 命名空间单独一行,不使用文件范围命名空间的花括号嵌套
**运行格式化检查:**
```bash
dotnet format --verify-no-changes
```
---

View File

@@ -0,0 +1,357 @@
# Components
基于架构模式和数据模型,系统划分为以下核心组件。每个组件负责特定功能,并通过清晰的接口与其他组件交互。
---
### PropertyGrid
**职责**:自动生成属性编辑 UI支持多种属性类型、分组、只读属性和基础验证。
**关键接口**
- `SelectedObject`(依赖属性)- 绑定的数据对象,变化时自动刷新属性列表
- `Properties`ObservableCollection<PropertyItem>- 解析后的属性列表
- `PropertyValueChanged`(事件)- 属性值变化时触发
**依赖关系**
- 依赖 TwoColumnLayout 进行布局
- 依赖 PropertyEditorsTextBox、NumericUpDown、ComboBox 等)
- 依赖 ThemeManager 获取当前主题颜色
- 使用反射System.Reflection解析对象属性
**技术细节**
- 继承自 `TemplatedControl`
- 使用 ReactiveUI 的 `WhenAnyValue` 监听 SelectedObject 变化
- 通过 `PropertyInfo.GetCustomAttributes<T>()` 读取 Attribute`[Category]``[Browsable]`
- 编辑器选择逻辑:根据 `PropertyItem.PropertyType` 映射到对应控件
**实现示例**(核心逻辑):
```csharp
public class PropertyGrid : TemplatedControl
{
public static readonly StyledProperty<object?> SelectedObjectProperty =
AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));
public object? SelectedObject
{
get => GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}
public ObservableCollection<PropertyItem> Properties { get; } = new();
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
this.GetObservable(SelectedObjectProperty)
.Subscribe(obj => RefreshProperties(obj));
}
private void RefreshProperties(object? obj)
{
Properties.Clear();
if (obj == null) return;
var properties = obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead)
.Select(p => CreatePropertyItem(obj, p))
.OrderBy(p => p.Category)
.ThenBy(p => p.Order);
foreach (var prop in properties)
{
Properties.Add(prop);
}
}
}
```
---
### TwoColumnLayout
**职责**:提供"标签-编辑器"配对的布局控件,左列固定或自适应宽度,右列占据剩余空间。
**关键接口**
- `Items`ObservableCollection<LayoutItem>- 行集合
- `LabelWidth`double- 左列宽度(默认 Auto
- `RowSpacing`double- 行间距(默认 8px
**依赖关系**
- 无外部依赖(纯布局控件)
- 被 PropertyGrid 使用
**技术细节**
- 继承自 `Panel` 或内部使用 `Grid`
- 重写 `MeasureOverride``ArrangeOverride` 实现自定义布局
- 响应主题系统(标签文本颜色使用 `TextSecondary`
**布局逻辑**
```
┌─────────────────────────────────┐
│ Label 1 │ Editor 1 │
│ Label 2 │ Editor 2 │
│ Label 3 │ Editor 3 │
└─────────────────────────────────┘
← LabelWidth → ← 剩余空间 →
```
---
### PropertyEditors编辑器集合
**职责**:为不同属性类型提供专用编辑器控件。
**组件列表**
| 属性类型 | 编辑器控件 | 说明 |
|---------|-----------|------|
| string | TextBox | Avalonia 内置 |
| int, double | NumericUpDown | Avalonia 内置或自定义 |
| bool | CheckBox | Avalonia 内置 |
| enum | ComboBox | 自动填充枚举值 |
| DateTime | DatePicker + TimePicker | 组合控件 |
**关键接口**
- 所有编辑器实现双向绑定TwoWay Binding
- 支持 IsEnabled 控制只读状态
- 支持 Avalonia 的 DataValidation 机制
**依赖关系**
- 被 PropertyGrid 使用
- 依赖 ThemeManager 获取主题颜色
**扩展性**
- 开发者可通过 `PropertyGrid.EditorTemplateSelector` 注册自定义编辑器
---
### UserGuide
**职责**:管理多步引导流程,协调 Overlay 和引导提示框的显示。
**关键接口**
- `Steps`ObservableCollection<GuideStep>- 引导步骤集合
- `CurrentStepIndex`int- 当前步骤索引
- `NextStepCommand`ICommand- 下一步命令
- `PreviousStepCommand`ICommand- 上一步命令
- `SkipCommand`ICommand- 跳过命令
- `Completed`(事件)- 引导完成事件
**依赖关系**
- 依赖 Overlay 控件显示遮罩
- 依赖 RichTooltip 显示引导提示框
- 依赖 GuideStep 数据模型
**技术细节**
- 继承自 `ContentControl`
- 使用 ReactiveUI 的 `ReactiveCommand` 实现步骤流转
- 步骤切换时触发动画淡入淡出200ms
**核心逻辑**
```csharp
public class UserGuide : ContentControl
{
public ObservableCollection<GuideStep> Steps { get; } = new();
private int _currentStepIndex;
public ReactiveCommand<Unit, Unit> NextStepCommand { get; }
public ReactiveCommand<Unit, Unit> PreviousStepCommand { get; }
public ReactiveCommand<Unit, Unit> SkipCommand { get; }
public UserGuide()
{
NextStepCommand = ReactiveCommand.Create(() =>
{
if (_currentStepIndex < Steps.Count - 1)
{
_currentStepIndex++;
ShowCurrentStep();
}
else
{
Complete();
}
});
// ... 其他 Command 实现
}
}
```
---
### Overlay
**职责**:在窗口上层显示半透明遮罩,可选地挖空特定控件区域以聚焦目标。
**关键接口**
- `IsVisible`bool- 显示/隐藏遮罩
- `TargetControl`Control?- 挖空目标控件
- `BackgroundOpacity`double- 不透明度(默认 0.5
- `BackgroundColor`Color- 遮罩颜色
**依赖关系**
- 被 UserGuide 使用
- 依赖 ThemeManager暗色模式下调整遮罩颜色
**技术细节**
- 继承自 `ContentControl``Panel`
- 使用绝对定位覆盖整个窗口
- 挖空逻辑:通过 `Clip` 或多个 `Rectangle` 实现
**视觉效果**
```
┌─────────────────────────────────┐
│████████████████████████████████│ ← 半透明遮罩
│████████┌──────────┐████████████│
│████████│ Target │████████████│ ← 挖空区域清晰可见
│████████└──────────┘████████████│
│████████████████████████████████│
└─────────────────────────────────┘
```
---
### RichTooltip
**职责**:显示支持基础富文本的增强 Tooltip。
**关键接口**
- `Content`string- 提示内容,支持简单 Markdown 语法(`**粗体**``*斜体*``\n` 换行)
- `MaxWidth`double- 最大宽度(默认 300px
**依赖关系**
- 扩展 Avalonia 的 `ToolTip`
- 被 UserGuide 使用
**技术细节**
- 内部使用 `TextBlock` 配合 `Inlines`Run、Bold、Italic
- 简单的 Markdown 解析器(不依赖第三方库)
- 响应主题系统(背景色 `Surface`,文本色 `TextPrimary`
**解析示例**
```
输入:"**注意**: 这是一个重要提示。\n请仔细阅读。"
输出:
<TextBlock>
<Bold>注意</Bold>: 这是一个重要提示。
<LineBreak />
请仔细阅读。
</TextBlock>
```
---
### ThemeManager
**职责**:管理主题切换,提供全局主题访问接口。
**关键接口**
- `ApplyTheme(ThemeType type)`(方法)- 切换主题
- `CurrentTheme`ThemeType- 当前主题类型
- `ThemeChanged`(事件)- 主题变化事件
**依赖关系**
- 被所有控件使用(通过静态方法或单例)
- 依赖 Avalonia 的 `Application.Current.Resources`
**技术细节**
- 静态类或单例模式
- 主题切换通过替换 `ResourceDictionary` 实现
- 支持动画过渡可选100ms 淡入淡出)
**实现示例**
```csharp
public static class ThemeManager
{
private static ThemeType _currentTheme = ThemeType.Light;
public static event EventHandler<ThemeType>? ThemeChanged;
public static void ApplyTheme(ThemeType type)
{
if (_currentTheme == type) return;
var app = Application.Current;
if (app == null) return;
// 移除旧主题
var oldTheme = app.Resources.MergedDictionaries
.FirstOrDefault(d => d.Source?.ToString().Contains("Theme") == true);
if (oldTheme != null)
app.Resources.MergedDictionaries.Remove(oldTheme);
// 加载新主题
var uri = type == ThemeType.Light
? new Uri("avares://Penguin.AvaloniaUI/Themes/LightTheme.axaml")
: new Uri("avares://Penguin.AvaloniaUI/Themes/DarkTheme.axaml");
var newTheme = new ResourceDictionary { Source = uri };
app.Resources.MergedDictionaries.Add(newTheme);
_currentTheme = type;
ThemeChanged?.Invoke(null, type);
}
}
```
---
### Component Diagrams
```mermaid
graph TB
subgraph "业务控件层"
PropertyGrid[PropertyGrid]
UserGuide[UserGuide]
end
subgraph "基础控件层"
TwoColumnLayout[TwoColumnLayout]
Overlay[Overlay]
RichTooltip[RichTooltip]
Editors[PropertyEditors]
end
subgraph "工具层"
ThemeManager[ThemeManager]
end
subgraph "数据模型"
PropertyItem[PropertyItem]
GuideStep[GuideStep]
end
PropertyGrid --> TwoColumnLayout
PropertyGrid --> Editors
PropertyGrid --> PropertyItem
PropertyGrid --> ThemeManager
UserGuide --> Overlay
UserGuide --> RichTooltip
UserGuide --> GuideStep
UserGuide --> ThemeManager
TwoColumnLayout --> ThemeManager
Overlay --> ThemeManager
RichTooltip --> ThemeManager
Editors --> ThemeManager
style PropertyGrid fill:#4A90E2
style UserGuide fill:#4A90E2
style TwoColumnLayout fill:#7ED321
style Overlay fill:#7ED321
style RichTooltip fill:#7ED321
style ThemeManager fill:#F5A623
```
---

View File

@@ -0,0 +1,207 @@
# Data Models
核心数据模型定义了控件库中业务逻辑层的数据结构。这些模型在 UI 控件层和业务逻辑层之间共享。
---
### PropertyItem
**用途**:表示 PropertyGrid 中的单个属性项,封装属性的元数据和值。
**关键属性**
- `Name`string- 属性名称,显示在左列标签
- `Value`object- 属性当前值,支持双向绑定
- `PropertyType`Type- 属性的 .NET 类型,用于选择合适的编辑器
- `IsReadOnly`bool- 是否只读,只读属性禁用编辑器
- `Category`string?- 分组类别,用于属性分组显示
- `Description`string?- 属性描述,可选的辅助说明
- `DisplayName`string?- 显示名称,如果为空则使用 Name
**C# 类定义**
```csharp
namespace Penguin.AvaloniaUI.Controls.PropertyGrid
{
/// <summary>
/// 表示 PropertyGrid 中的单个属性项
/// </summary>
public class PropertyItem : ReactiveObject
{
private object? _value;
/// <summary>
/// 属性名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 属性显示名称(如果为空则使用 Name
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// 属性值(支持双向绑定)
/// </summary>
public object? Value
{
get => _value;
set => this.RaiseAndSetIfChanged(ref _value, value);
}
/// <summary>
/// 属性的 .NET 类型
/// </summary>
public Type PropertyType { get; set; } = typeof(object);
/// <summary>
/// 是否只读
/// </summary>
public bool IsReadOnly { get; set; }
/// <summary>
/// 分组类别(可选)
/// </summary>
public string? Category { get; set; }
/// <summary>
/// 属性描述(可选)
/// </summary>
public string? Description { get; set; }
/// <summary>
/// 原始 PropertyInfo用于反射场景
/// </summary>
public PropertyInfo? PropertyInfo { get; set; }
}
}
```
**关系**
- PropertyGrid 包含多个 PropertyItem`ObservableCollection<PropertyItem>`
- PropertyItem 通过 PropertyType 决定使用哪个编辑器控件
---
### GuideStep
**用途**:表示 UserGuide 引导流程中的单个步骤。
**关键属性**
- `TargetControl`Control?- 引导目标控件Overlay 将聚焦此控件
- `Title`string- 步骤标题,显示在引导提示框顶部
- `Content`string- 步骤内容/提示文本,支持基础富文本
- `Position`TooltipPosition- 提示框相对于目标控件的位置
- `Order`int- 步骤顺序,用于排序
**C# 类定义**
```csharp
namespace Penguin.AvaloniaUI.Controls.UserGuide
{
/// <summary>
/// 提示框位置枚举
/// </summary>
public enum TooltipPosition
{
Bottom,
Top,
Left,
Right
}
/// <summary>
/// 表示 UserGuide 中的单个引导步骤
/// </summary>
public class GuideStep : ReactiveObject
{
/// <summary>
/// 引导目标控件(可选,如果为空则显示全屏提示)
/// </summary>
public Control? TargetControl { get; set; }
/// <summary>
/// 步骤标题
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// 步骤内容(支持基础富文本)
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// 提示框位置
/// </summary>
public TooltipPosition Position { get; set; } = TooltipPosition.Bottom;
/// <summary>
/// 步骤顺序(用于排序)
/// </summary>
public int Order { get; set; }
/// <summary>
/// 是否允许跳过此步骤
/// </summary>
public bool Skippable { get; set; } = true;
}
}
```
**关系**
- UserGuide 包含多个 GuideStep`ObservableCollection<GuideStep>`
- GuideStep 通过 TargetControl 关联到具体的 UI 控件
- Overlay 根据 TargetControl 的位置和大小进行挖空显示
---
### ThemeInfo辅助模型
**用途**:表示主题信息,用于 ThemeManager 管理主题切换。
**关键属性**
- `ThemeType`ThemeType- 主题类型枚举Light、Dark
- `ResourceUri`Uri- 主题资源字典的 URI
**C# 类定义**
```csharp
namespace Penguin.AvaloniaUI.Themes
{
/// <summary>
/// 主题类型枚举
/// </summary>
public enum ThemeType
{
Light,
Dark
}
/// <summary>
/// 主题信息
/// </summary>
public class ThemeInfo
{
/// <summary>
/// 主题类型
/// </summary>
public ThemeType Type { get; set; }
/// <summary>
/// 主题资源字典 URI
/// </summary>
public Uri ResourceUri { get; set; } = null!;
/// <summary>
/// 主题显示名称
/// </summary>
public string DisplayName { get; set; } = string.Empty;
}
}
```
**关系**
- ThemeManager 使用 ThemeInfo 管理可用主题
- 主题切换时通过 ResourceUri 加载对应的 ResourceDictionary
---

View File

@@ -0,0 +1,180 @@
# Development Workflow
本节定义控件库的开发环境配置和日常开发流程,确保开发者能够快速上手并保持高效开发。
---
### Local Development Setup
**前置要求:**
```bash
# 必需
- .NET 9.0 SDK (https://dotnet.microsoft.com/download)
- Visual Studio 2022 (17.8+)
- Git 2.40+
# 推荐
- Visual Studio Avalonia 扩展 (用于 XAML 预览)
```
**初始化项目:**
```bash
# 克隆仓库
git clone <repository-url>
cd 32_avalonia.ui
# 还原 NuGet 包
dotnet restore
# 构建解决方案
dotnet build
# 运行示例应用
dotnet run --project src/Example/Example.csproj
```
**Visual Studio 配置:**
1. 打开 `Penguin.AvaloniaUI.sln`
2. 设置 `Example` 为启动项目
3. 选择调试配置Debug/Release
4. 按 F5 启动调试
---
### Development Commands
**常用命令:**
```bash
# 启动示例应用(开发模式,支持热重载)
dotnet watch --project src/Example/Example.csproj
# 运行所有单元测试
dotnet test
# 运行特定测试类
dotnet test --filter "FullyQualifiedName~PropertyGridTests"
# 生成代码覆盖率报告
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=html
# 清理构建产物
dotnet clean
# 格式化代码(基于 .editorconfig
dotnet format
# 打包 NuGet 包Post-MVP
dotnet pack src/Penguin.AvaloniaUI/Penguin.AvaloniaUI.csproj -c Release
```
---
### Hot Reload 配置
Avalonia 11.x 支持 .NET Hot Reload但有限制
**支持的修改:**
- C# 方法体内的代码修改
- XAML 资源字典的修改(颜色、样式)
- 部分 XAML 控件属性修改
**不支持的修改(需要重启):**
- 新增或删除控件
- 修改控件模板结构
- 修改依赖属性定义
- 修改 ViewModel 属性签名
**启用 Hot Reload**
`Example.csproj` 中确保已启用:
```xml
<PropertyGroup>
<AvaloniaUseHotReload>true</AvaloniaUseHotReload>
</PropertyGroup>
```
---
### Environment Configuration
**控件库无需环境变量**MVP 阶段),但示例应用可能需要:
```bash
# Example/.env (可选,用于测试外部 API 集成)
# MVP 阶段不需要
```
**调试配置launchSettings.json**
```json
{
"profiles": {
"Example": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
```
---
### Git Workflow
**Commit Message 格式Conventional Commits**
```
<type>: <description>
[optional body]
```
**Type 类型:**
- `feat`: 新功能(如 `feat: 增加了UserControl的DarkStyle`
- `fix`: Bug 修复(如 `fix: 修复PropertyGrid在主题切换时崩溃`
- `refactor`: 重构(如 `refactor: 优化ThemeManager的资源加载逻辑`
- `test`: 测试相关(如 `test: 添加PropertyGrid的分组功能测试`
- `docs`: 文档更新(如 `docs: 更新README的快速开始指南`
- `style`: 代码格式调整(如 `style: 统一缩进为4空格`
- `chore`: 构建/工具相关(如 `chore: 升级Avalonia到11.3.8`
**示例:**
```bash
git commit -m "feat: 实现PropertyGrid的属性分组功能"
git commit -m "fix: 修复TwoColumnLayout在窗口缩放时的对齐问题"
git commit -m "test: 添加ThemeManager的主题切换测试"
```
**分支命名规范:**
- `feat/<feature-name>` - 新功能分支(如 `feat/usercontrol`
- `fix/<issue-number>` - Bug 修复分支(如 `fix/1123`
- `refactor/<description>` - 重构分支(如 `refactor/theme-system`
- `test/<description>` - 测试分支(如 `test/integration`
**日常开发流程:**
1. **创建功能分支**`git checkout -b feat/two-column-layout`
2. **编写代码**:在 `src/Penguin.AvaloniaUI/` 中实现控件
3. **编写测试**:在 `src/Penguin.AvaloniaUI.Tests/` 中添加单元测试
4. **更新示例**:在 `src/Example/Views/Pages/` 中添加演示页面
5. **本地验证**
- 运行单元测试:`dotnet test`
- 启动示例应用:`dotnet run --project src/Example`
- 手动测试主题切换和控件交互
6. **提交代码**
```bash
git add .
git commit -m "feat: 实现TwoColumnLayout布局控件"
git push origin feat/two-column-layout
```
---

View File

@@ -0,0 +1,428 @@
# Error Handling
本节定义控件库的错误处理策略,确保异常情况下的稳定性和良好的开发者体验。
---
### Error Handling Principles
**核心原则:**
1. **Fail Fast快速失败**:公开 API 在参数无效时立即抛出异常,不延迟到执行阶段
2. **Clear Error Messages清晰错误信息**:异常消息应明确指出问题和解决方案
3. **No Silent Failures不吞没异常**:禁止空 catch 块,必须记录或重新抛出异常
4. **Defensive Programming防御性编程**:内部方法使用 `Debug.Assert` 验证前置条件
---
### Exception Handling Patterns
**公开 API 参数验证:**
```csharp
namespace Penguin.AvaloniaUI.Themes;
public static class ThemeManager
{
/// <summary>
/// 应用指定的主题类型
/// </summary>
/// <param name="type">主题类型</param>
/// <exception cref="ArgumentException">当主题类型无效时抛出</exception>
public static void ApplyTheme(ThemeType type)
{
// 参数验证
if (!Enum.IsDefined(typeof(ThemeType), type))
{
throw new ArgumentException(
$"Invalid theme type: {type}. Valid values are: {string.Join(", ", Enum.GetNames(typeof(ThemeType)))}",
nameof(type));
}
// 核心逻辑
try
{
LoadThemeResources(type);
_currentTheme = type;
ThemeChanged?.Invoke(null, type);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to apply theme '{type}'. Ensure theme resources are available.",
ex);
}
}
}
```
---
**PropertyGrid 错误处理:**
```csharp
namespace Penguin.AvaloniaUI.Controls.PropertyGrid;
public class PropertyGrid : TemplatedControl
{
public static readonly StyledProperty<object?> SelectedObjectProperty =
AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));
public object? SelectedObject
{
get => GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}
private void RefreshProperties(object? obj)
{
Properties.Clear();
if (obj == null)
{
// 空对象是合法的,清空属性列表即可
return;
}
try
{
var properties = obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead);
foreach (var prop in properties)
{
try
{
var item = CreatePropertyItem(obj, prop);
Properties.Add(item);
}
catch (Exception ex)
{
// 单个属性解析失败不应影响其他属性
Debug.WriteLine($"[PropertyGrid] Failed to create property item for '{prop.Name}': {ex.Message}");
// 可选:添加错误占位符
Properties.Add(new PropertyItem
{
Name = prop.Name,
DisplayName = $"{prop.Name} (Error)",
IsReadOnly = true,
Value = $"Error: {ex.Message}"
});
}
}
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to parse properties for object of type '{obj.GetType().Name}'.",
ex);
}
}
private PropertyItem CreatePropertyItem(object obj, PropertyInfo prop)
{
Debug.Assert(obj != null, "Object should not be null");
Debug.Assert(prop != null, "PropertyInfo should not be null");
try
{
var value = prop.GetValue(obj);
return new PropertyItem
{
Name = prop.Name,
DisplayName = GetDisplayName(prop),
Value = value,
PropertyType = prop.PropertyType,
IsReadOnly = !prop.CanWrite,
Category = GetCategory(prop),
PropertyInfo = prop
};
}
catch (TargetInvocationException ex)
{
throw new InvalidOperationException(
$"Property '{prop.Name}' getter threw an exception.",
ex.InnerException ?? ex);
}
}
}
```
---
**UserGuide 错误处理:**
```csharp
namespace Penguin.AvaloniaUI.Controls.UserGuide;
public class UserGuide : ContentControl
{
private void ShowCurrentStep()
{
if (CurrentStepIndex < 0 || CurrentStepIndex >= Steps.Count)
{
Debug.WriteLine($"[UserGuide] Invalid step index: {CurrentStepIndex}");
return;
}
var step = Steps[CurrentStepIndex];
// 验证目标控件是否可用
if (step.TargetControl == null)
{
Debug.WriteLine($"[UserGuide] Step '{step.Title}' has no target control, showing full-screen overlay.");
// 显示全屏提示
ShowFullScreenGuide(step);
return;
}
// 检查目标控件是否在可视化树中
if (!step.TargetControl.IsVisible || !IsControlInVisualTree(step.TargetControl))
{
Debug.WriteLine($"[UserGuide] Target control for step '{step.Title}' is not visible or not in visual tree. Skipping step.");
// 自动跳到下一步
if (CurrentStepIndex < Steps.Count - 1)
{
CurrentStepIndex++;
ShowCurrentStep();
}
else
{
Complete();
}
return;
}
// 正常显示引导
ShowGuideForControl(step);
}
private bool IsControlInVisualTree(Control control)
{
try
{
return control.GetVisualRoot() != null;
}
catch
{
return false;
}
}
}
```
---
### Logging Strategy
**使用 Debug.WriteLine 进行调试日志:**
```csharp
using System.Diagnostics;
namespace Penguin.AvaloniaUI.Utils;
internal static class Logger
{
[Conditional("DEBUG")]
public static void Debug(string message)
{
System.Diagnostics.Debug.WriteLine($"[Penguin.AvaloniaUI] {message}");
}
[Conditional("DEBUG")]
public static void Warning(string message)
{
System.Diagnostics.Debug.WriteLine($"[WARNING] {message}");
}
public static void Error(string message, Exception? ex = null)
{
System.Diagnostics.Debug.WriteLine($"[ERROR] {message}");
if (ex != null)
{
System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
}
}
}
```
**使用示例:**
```csharp
private void RefreshProperties(object? obj)
{
Logger.Debug($"RefreshProperties called with object type: {obj?.GetType().Name ?? "null"}");
try
{
// 核心逻辑
}
catch (Exception ex)
{
Logger.Error("Failed to refresh properties", ex);
throw;
}
}
```
---
### Error Messages Guidelines
**好的错误消息示例:**
```csharp
// ❌ 不好的错误消息
throw new Exception("Error");
throw new ArgumentException("Invalid parameter");
// ✅ 好的错误消息
throw new ArgumentNullException(nameof(selectedObject),
"SelectedObject cannot be null. Please provide a valid object instance.");
throw new InvalidOperationException(
"Cannot apply theme: Application.Current is null. Ensure ThemeManager is called after App initialization.");
throw new NotSupportedException(
$"Property type '{propertyType.Name}' is not supported by PropertyGrid. " +
$"Supported types: string, int, double, bool, enum, DateTime.");
```
**错误消息模板:**
```
{What happened}: {Why it happened}. {How to fix it}.
```
---
### Exception Types
**使用合适的异常类型:**
| 场景 | 异常类型 | 示例 |
|------|---------|------|
| 参数为 null | `ArgumentNullException` | `throw new ArgumentNullException(nameof(obj))` |
| 参数值无效 | `ArgumentException` | `throw new ArgumentException("Invalid theme type", nameof(type))` |
| 参数超出范围 | `ArgumentOutOfRangeException` | `throw new ArgumentOutOfRangeException(nameof(index))` |
| 操作在当前状态无效 | `InvalidOperationException` | `throw new InvalidOperationException("Theme resources not loaded")` |
| 功能未实现 | `NotImplementedException` | `throw new NotImplementedException("Custom editors not supported in MVP")` |
| 功能不支持 | `NotSupportedException` | `throw new NotSupportedException($"Type {type} not supported")` |
---
### Try-Catch Guidelines
**何时使用 try-catch**
```csharp
// ✅ 捕获特定异常并提供上下文
try
{
var value = property.GetValue(obj);
}
catch (TargetInvocationException ex)
{
throw new InvalidOperationException(
$"Property '{property.Name}' getter threw an exception.",
ex.InnerException ?? ex);
}
// ✅ 记录异常并继续处理
try
{
LoadThemeResource(uri);
}
catch (Exception ex)
{
Logger.Error($"Failed to load theme resource: {uri}", ex);
// 使用默认主题
LoadDefaultTheme();
}
// ❌ 不要捕获所有异常并吞没
try
{
DoSomething();
}
catch
{
// 什么都不做 - 这是错误的!
}
// ❌ 不要捕获异常后重新抛出同一个异常
try
{
DoSomething();
}
catch (Exception ex)
{
throw ex; // 错误:会丢失调用栈
}
// ✅ 如果需要记录后重新抛出,使用 throw;
try
{
DoSomething();
}
catch (Exception ex)
{
Logger.Error("Operation failed", ex);
throw; // 正确:保留调用栈
}
```
---
### Validation Helpers
**创建参数验证辅助方法:**
```csharp
namespace Penguin.AvaloniaUI.Utils;
internal static class Guard
{
public static void NotNull<T>(T value, string paramName) where T : class
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void NotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty.", paramName);
}
}
public static void InRange(int value, int min, int max, string paramName)
{
if (value < min || value > max)
{
throw new ArgumentOutOfRangeException(paramName,
$"Value must be between {min} and {max}, but was {value}.");
}
}
}
// 使用示例
public void SetProperty(string name, object value)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(value, nameof(value));
// 核心逻辑
}
```
---

View File

@@ -0,0 +1,144 @@
# High Level Architecture
### Technical Summary
Penguin.AvaloniaUI 是一个基于 Avalonia 11.3.7 的桌面控件库专注于业务场景控件PropertyGrid、UserGuide 等)。采用 ReactiveUI 实现 MVVM 模式和响应式交互,目标是为上位机和 AI 桌面应用提供开箱即用的复合控件。
**核心特性**
- **业务场景控件**PropertyGrid属性编辑、UserGuide新手引导等高级控件
- **多主题系统**:基于 Semi Design 和苹果颜色系统,支持浅色/暗色主题运行时切换
- **响应式架构**:基于 ReactiveUI支持 Command、Reactive、Event 混合使用
- **国际化预留**架构支持后续多语言扩展MVP 阶段使用单一语言
- **AOT 友好设计**:架构考虑未来 AOT 编译支持MVP 阶段允许使用反射
**架构目标**
- 降低上位机和 AI 桌面应用的开发成本(减少 30-50% 重复工作)
- 提供专业级 UI 体验(信息密度优先、暗色模式友好)
- 确保良好的扩展性(开发者可通过 Template 和 Style 自定义外观)
---
### Platform and Infrastructure Choice
**开发平台**
- **IDE**Visual Studio 2022 或 JetBrains Rider
- **运行时**.NET 9.0
- **版本控制**Git本地或私有仓库
- **包管理**NuGet
**目标平台**
- **主要**Windows 10/11桌面应用
- **次要**Linux 桌面Ubuntu 20.04+、Debian 11+,主要是工业平板场景)
- **可选**macOS架构不排斥但非 MVP 测试平台)
**最小支持分辨率**1366x768
**推荐分辨率**1920x1080 及以上
**输入方式**
- 主要:鼠标 + 键盘
- 次要触控笔Linux Pad 场景)
- 不支持:多点触控手势
---
### Repository Structure
**结构类型**Monorepo
**组织策略**
```
D:\32_avalonia.ui/
├── src/
│ ├── Penguin.AvaloniaUI/ # 核心控件库
│ ├── Penguin.AvaloniaUI.SourceGenerators/ # Source GeneratorPost-MVP
│ ├── Example/ # 示例应用
│ └── Penguin.AvaloniaUI.Tests/ # 单元测试
├── docs/ # 文档
│ ├── prd.md
│ ├── architecture.md
│ └── ...
└── .bmad-core/ # BMAD 框架配置
```
**核心控件库内部结构**(命名空间组织):
- `Penguin.AvaloniaUI.Controls` - 业务场景控件PropertyGrid、UserGuide 等)
- `Penguin.AvaloniaUI.Layouts` - 布局控件TwoColumnLayout 等)
- `Penguin.AvaloniaUI.Themes` - 主题和样式系统
- `Penguin.AvaloniaUI.Utils` - 工具类ThemeManager 等)
**Rationale**
- Monorepo 简化控件库与示例应用的协同开发
- 依赖版本统一管理,避免版本冲突
- MVP 阶段项目数量少3-4 个),复杂度可控
---
### High Level Architecture Diagram
```mermaid
graph TB
subgraph "开发者应用"
App[Avalonia Application]
end
subgraph "Penguin.AvaloniaUI 控件库"
Controls[Controls Layer<br/>PropertyGrid, UserGuide]
Layouts[Layouts Layer<br/>TwoColumnLayout, Overlay]
Themes[Themes Layer<br/>ThemeManager, ColorSystem]
Utils[Utils Layer<br/>LocalizationManager, Helpers]
end
subgraph "基础框架"
Avalonia[Avalonia UI 11.3.7]
ReactiveUI[ReactiveUI 11.3.0]
NET[.NET 9.0]
end
App --> Controls
App --> Layouts
App --> Themes
Controls --> Layouts
Controls --> Themes
Controls --> Utils
Layouts --> Themes
Themes --> Avalonia
Controls --> ReactiveUI
Avalonia --> NET
ReactiveUI --> NET
style Controls fill:#4A90E2
style Layouts fill:#7ED321
style Themes fill:#F5A623
style Utils fill:#BD10E0
```
---
### Architectural Patterns
- **MVVM (Model-View-ViewModel)**:所有控件遵循 MVVM 模式,使用 ReactiveUI 实现数据绑定和 Command
- _Rationale_Avalonia 原生支持 MVVMReactiveUI 提供强大的响应式能力,降低复杂交互的实现难度
- **Templated Control Pattern**:业务控件继承自 `TemplatedControl`,通过 `ControlTemplate` 分离逻辑和外观
- _Rationale_确保控件可定制开发者可以通过 Style 和 Template 覆盖默认外观
- **Resource Dictionary 主题系统**:主题通过 `ResourceDictionary` 定义,运行时通过替换 `Application.Current.Resources` 实现切换
- _Rationale_Avalonia 标准机制,无需引入额外框架,主题切换自动传播到所有控件
- **Reactive Extensions (Rx)**:使用 ReactiveUI 的 Reactive 模式处理异步事件流和属性变化
- _Rationale_简化复杂的事件处理逻辑如 PropertyGrid 的属性变化监听、UserGuide 的步骤流转)
- **Dependency Injection (可选)**MVP 阶段不强制 DIThemeManager 等服务可通过静态类或单例访问
- _Rationale_控件库不需要复杂的 DI 容器保持简单Post-MVP 可根据需要引入
- **Attribute-Driven Configuration**PropertyGrid 通过 Attribute`[Category]``[Browsable]`)配置属性显示
- _Rationale_符合 .NET 生态习惯(类似 WinForms PropertyGrid降低学习成本
- **Composition over Inheritance**:复杂控件通过组合基础控件实现(如 UserGuide 组合 Overlay + RichTooltip
- _Rationale_提高代码复用性避免深层继承带来的维护问题
---

View File

@@ -0,0 +1,17 @@
# Penguin.AvaloniaUI Architecture Document
## Table of Contents
- [Penguin.AvaloniaUI Architecture Document](#table-of-contents)
- [Introduction](#introduction)
- [High Level Architecture](#high-level-architecture)
- [Tech Stack](#tech-stack)
- [Data Models](#data-models)
- [Components](#components)
- [Unified Project Structure](#unified-project-structure)
- [Development Workflow](#development-workflow)
- [Coding Standards](#coding-standards)
- [Testing Strategy](#testing-strategy)
- [Error Handling](#error-handling)
- [Performance Optimization](#performance-optimization)
- [文档完成](#文档完成)

View File

@@ -0,0 +1,42 @@
# Introduction
本文档定义了 **Penguin.AvaloniaUI** 控件库的完整架构,作为 AI 驱动开发的技术蓝图。
项目基于以下核心技术:
- **UI 框架**: Avalonia 11.3.7
- **MVVM 和响应式**: ReactiveUI.Avalonia 11.3.0
- **运行时**: .NET 9.0
- **目标平台**: Windows主要、Linux、macOS次要
架构范围涵盖:
- 控件组件设计PropertyGrid、UserGuide 等)
- 主题和样式系统
- 项目结构和开发工作流
- 编码规范和测试策略
---
### Starter Template or Existing Project
**项目类型**: 现有代码基础上的开发
**当前状态**:
- 项目结构已建立(`src/Penguin.AvaloniaUI/`
- 核心依赖已配置Avalonia 11.3.7、ReactiveUI 11.3.0
- PRD 已完成,定义了 3 个 Epic 和 16 个 Story
- Monorepo 结构(控件库 + 示例应用 + 测试)
**待评估项**:
- Semi.Avalonia 样式库的可用性Story 1.1 前置任务)
- 如不可用,将采用自定义样式系统
---
### Change Log
| Date | Version | Description | Author |
|------------|---------|----------------------------------|-----------|
| 2025-10-16 | 1.0 | 初始架构文档创建(基于 PRD v1.0 | Architect |
---

View File

@@ -0,0 +1,415 @@
# Performance Optimization
本节定义性能优化策略和最佳实践,确保控件库满足 NFR 性能要求。
---
### Performance Targets (from NFR)
| 指标 | 目标值 | 测量方法 |
|------|--------|----------|
| UI 渲染帧率 | 60fps (16ms/frame) | Visual Studio Performance Profiler |
| 主题切换时间 | < 100ms | Stopwatch 测量 |
| PropertyGrid 生成50 属性) | < 200ms | Stopwatch 测量 |
| 应用启动时间 | < 2s | 从启动到窗口显示 |
| 内存占用 | < 100MB (空闲时) | Task Manager / dotnet-counters |
---
### UI Rendering Performance
**关键原则:避免在 UI 线程执行耗时操作**
**❌ 反面示例 - 阻塞 UI 线程:**
```csharp
// 错误:在属性 getter 中执行反射
public ObservableCollection<PropertyItem> Properties
{
get
{
var items = new ObservableCollection<PropertyItem>();
if (SelectedObject != null)
{
// 耗时操作会阻塞 UI 渲染
var properties = SelectedObject.GetType().GetProperties();
foreach (var prop in properties)
{
items.Add(CreatePropertyItem(SelectedObject, prop));
}
}
return items;
}
}
```
**✅ 正确示例 - 异步加载:**
```csharp
private ObservableCollection<PropertyItem> _properties = new();
public ObservableCollection<PropertyItem> Properties => _properties;
private async void RefreshProperties(object? obj)
{
_properties.Clear();
if (obj == null) return;
// 在后台线程执行反射
var items = await Task.Run(() =>
{
var result = new List<PropertyItem>();
var properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
result.Add(CreatePropertyItem(obj, prop));
}
return result;
});
// 回到 UI 线程更新集合
foreach (var item in items)
{
_properties.Add(item);
}
}
```
---
### Reflection Optimization
**问题:反射是 PropertyGrid 的性能瓶颈**
**优化策略 1缓存 PropertyInfo**
```csharp
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _propertyCache = new();
private PropertyInfo[] GetCachedProperties(Type type)
{
return _propertyCache.GetOrAdd(type, t =>
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
);
}
```
**优化策略 2缓存 Attribute 读取**
```csharp
private static readonly ConcurrentDictionary<PropertyInfo, CategoryAttribute?> _categoryCache = new();
private string? GetCategory(PropertyInfo prop)
{
var attr = _categoryCache.GetOrAdd(prop, p =>
p.GetCustomAttribute<CategoryAttribute>()
);
return attr?.Category;
}
```
**优化策略 3使用编译表达式替代反射调用可选**
```csharp
// 反射调用(慢)
var value = propertyInfo.GetValue(obj);
// 编译表达式(快 10-100 倍)
private static Func<object, object> CreateGetter(PropertyInfo prop)
{
var instance = Expression.Parameter(typeof(object), "instance");
var convert = Expression.Convert(instance, prop.DeclaringType!);
var property = Expression.Property(convert, prop);
var convertResult = Expression.Convert(property, typeof(object));
return Expression.Lambda<Func<object, object>>(convertResult, instance).Compile();
}
```
**性能对比:**
```
反射 GetValue: ~1000 ns/调用
编译表达式: ~10 ns/调用
直接属性访问: ~1 ns/调用
```
---
### Theme Switching Performance
**优化主题切换速度(目标 < 100ms**
**❌ 反面示例 - 逐个替换资源:**
```csharp
// 错误:逐个替换资源会触发多次 UI 刷新
foreach (var key in themeColors.Keys)
{
Application.Current.Resources[key] = themeColors[key];
}
```
**✅ 正确示例 - 批量替换 ResourceDictionary**
```csharp
public static void ApplyTheme(ThemeType type)
{
var app = Application.Current;
if (app == null) return;
// 移除旧主题
var oldTheme = app.Resources.MergedDictionaries
.FirstOrDefault(d => d.Source?.ToString().Contains("Theme") == true);
if (oldTheme != null)
app.Resources.MergedDictionaries.Remove(oldTheme);
// 一次性加载新主题(单次 UI 刷新)
var uri = type == ThemeType.Light
? new Uri("avares://Penguin.AvaloniaUI/Themes/LightTheme.axaml")
: new Uri("avares://Penguin.AvaloniaUI/Themes/DarkTheme.axaml");
var newTheme = new ResourceDictionary { Source = uri };
app.Resources.MergedDictionaries.Add(newTheme);
_currentTheme = type;
ThemeChanged?.Invoke(null, type);
}
```
**预加载主题资源(可选优化):**
```csharp
private static ResourceDictionary? _cachedLightTheme;
private static ResourceDictionary? _cachedDarkTheme;
static ThemeManager()
{
// 应用启动时预加载主题
_cachedLightTheme = LoadThemeResource(ThemeType.Light);
_cachedDarkTheme = LoadThemeResource(ThemeType.Dark);
}
public static void ApplyTheme(ThemeType type)
{
var theme = type == ThemeType.Light ? _cachedLightTheme : _cachedDarkTheme;
// 直接使用缓存的主题,无需重新加载
}
```
---
### Memory Management
**避免内存泄漏的常见陷阱:**
**问题 1事件订阅未取消**
```csharp
// ❌ 可能导致内存泄漏
public class PropertyGrid : TemplatedControl
{
public PropertyGrid()
{
ThemeManager.ThemeChanged += OnThemeChanged;
// 没有取消订阅!
}
private void OnThemeChanged(object? sender, ThemeType type)
{
RefreshTheme();
}
}
// ✅ 正确实现 IDisposable
public class PropertyGrid : TemplatedControl, IDisposable
{
public PropertyGrid()
{
ThemeManager.ThemeChanged += OnThemeChanged;
}
public void Dispose()
{
ThemeManager.ThemeChanged -= OnThemeChanged;
}
private void OnThemeChanged(object? sender, ThemeType type)
{
RefreshTheme();
}
}
```
**问题 2ObservableCollection 过度使用**
```csharp
// ❌ 每次都创建新集合
public ObservableCollection<PropertyItem> Properties
{
get => new ObservableCollection<PropertyItem>(GetProperties());
}
// ✅ 复用同一个集合
private ObservableCollection<PropertyItem> _properties = new();
public ObservableCollection<PropertyItem> Properties => _properties;
private void RefreshProperties()
{
_properties.Clear();
foreach (var item in GetProperties())
{
_properties.Add(item);
}
}
```
---
### Virtualization (Post-MVP)
**如果 PropertyGrid 需要支持数百个属性,使用虚拟化:**
```xml
<ItemsControl Items="{Binding Properties}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- VirtualizingStackPanel 仅渲染可见项 -->
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
```
**虚拟化性能对比:**
```
100 个属性(非虚拟化): ~300ms 生成 + 50MB 内存
100 个属性(虚拟化): ~100ms 生成 + 10MB 内存
```
---
### Avoid Common Pitfalls
**陷阱 1在循环中触发属性变化通知**
```csharp
// ❌ 每次循环触发 UI 刷新
for (int i = 0; i < 1000; i++)
{
Properties.Add(new PropertyItem()); // 触发 CollectionChanged
}
// ✅ 批量添加后触发一次刷新
var items = new List<PropertyItem>();
for (int i = 0; i < 1000; i++)
{
items.Add(new PropertyItem());
}
Properties.Clear();
foreach (var item in items)
{
Properties.Add(item);
}
```
**陷阱 2频繁的字符串拼接**
```csharp
// ❌ 每次拼接创建新字符串
string result = "";
for (int i = 0; i < 100; i++)
{
result += properties[i].Name + ", ";
}
// ✅ 使用 StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 100; i++)
{
sb.Append(properties[i].Name).Append(", ");
}
var result = sb.ToString();
```
**陷阱 3XAML 中的复杂绑定**
```xml
<!-- ❌ 每次渲染都执行转换 -->
<TextBlock Text="{Binding PropertyType, Converter={StaticResource ComplexConverter}}" />
<!-- ✅ 在 ViewModel 中预计算 -->
<TextBlock Text="{Binding PropertyTypeDisplayName}" />
```
---
### Performance Measurement
**使用 Stopwatch 测量关键操作:**
```csharp
#if DEBUG
private void RefreshProperties(object? obj)
{
var sw = Stopwatch.StartNew();
// 核心逻辑
_properties.Clear();
var items = GetProperties(obj);
foreach (var item in items)
{
_properties.Add(item);
}
sw.Stop();
Debug.WriteLine($"[PropertyGrid] RefreshProperties took {sw.ElapsedMilliseconds}ms for {items.Count} properties");
}
#endif
```
**Visual Studio Performance Profiler**
1. 调试 → 性能探查器
2. 选择"CPU 使用情况"或".NET 对象分配跟踪"
3. 启动分析
4. 执行性能关键操作(如生成 50 个属性)
5. 停止分析并查看热点代码
**dotnet-counters 监控内存:**
```bash
# 安装工具
dotnet tool install --global dotnet-counters
# 监控运行中的应用
dotnet-counters monitor --process-id <pid> --counters System.Runtime
```
---
### Optimization Checklist
**开发时检查清单:**
- [ ] 反射操作已缓存PropertyInfo、Attribute
- [ ] 耗时操作(> 50ms在后台线程执行
- [ ] 事件订阅在控件销毁时取消
- [ ] ObservableCollection 复用而非重建
- [ ] 批量更新集合,避免频繁触发 CollectionChanged
- [ ] 字符串拼接使用 StringBuilder
- [ ] XAML 绑定避免复杂转换器
- [ ] 主题切换批量替换 ResourceDictionary
**发布前性能测试:**
- [ ] PropertyGrid 50 属性生成 < 200ms
- [ ] 主题切换 < 100ms
- [ ] 应用启动 < 2s
- [ ] 空闲时内存 < 100MB
- [ ] 无明显的 UI 卡顿(帧率 > 50fps
---

View File

@@ -0,0 +1,64 @@
# Tech Stack
本表是项目的**单一技术事实来源**,所有开发必须使用这些确定的技术和版本。
### Technology Stack Table
| Category | Technology | Version | Purpose | Rationale |
|----------|-----------|---------|---------|-----------|
| **运行时** | .NET | 9.0 | 应用运行时环境 | 最新版本,支持最新 C# 特性,性能优异 |
| **UI 框架** | Avalonia | 11.3.7 | 跨平台桌面 UI 框架 | 现代化、跨平台、活跃社区,不向后兼容旧版本 |
| **MVVM 框架** | ReactiveUI.Avalonia | 11.3.0 | 响应式 MVVM 实现 | 强大的响应式能力,简化复杂交互逻辑,与 Avalonia 深度集成 |
| **状态管理** | ReactiveUI Observable | Built-in | 属性变化监听和数据流 | ReactiveUI 内置,无需额外依赖,统一状态管理方式 |
| **样式系统** | Semi.Avalonia + 自定义 | Latest | UI 样式和主题 | 基于 Semi Design结合苹果颜色系统的自定义实现 |
| **颜色系统** | 苹果颜色系统(自定义实现) | N/A | 语义化颜色定义 | Primary/Secondary/Success/Warning/Error 等语义化颜色,支持浅色/暗色主题 |
| **布局系统** | Avalonia Layouts + 自定义 | Built-in + Custom | 控件布局 | Avalonia 内置 Panel 系统 + 自定义 TwoColumnLayout |
| **数据绑定** | Avalonia Binding | Built-in | XAML 数据绑定 | Avalonia 原生支持,与 ReactiveUI 无缝集成 |
| **单元测试** | xUnit | 2.6+ | 单元测试框架 | .NET 生态主流选择,简洁易用 |
| **包管理** | NuGet + 统一包版本管理 | Built-in | 依赖包管理 | 使用 Directory.Packages.props 统一管理包版本,避免版本冲突 |
| **版本控制** | Git | 2.40+ | 代码版本管理 | 行业标准 |
| **CI/CD** | 手动构建MVP | N/A | 持续集成/部署 | MVP 阶段不强制 CI/CDPost-MVP 可引入 GitHub Actions |
| **代码格式化** | .editorconfig | Built-in | 代码风格统一 | 定义缩进、换行等规则 |
| **文档生成** | XML 文档注释 | Built-in | API 文档 | 所有 public API 必须有 XML 注释 |
| **国际化** | Avalonia IResourceProvider预留 | Built-in | 多语言支持 | MVP 单一语言,架构预留扩展点 |
| **性能分析** | Visual Studio Profiler / dotnet-trace | Built-in | 性能测量 | 验证 NFR60fps、100ms 主题切换、200ms PropertyGrid 生成) |
---
### 统一包版本管理配置
为了避免多项目间的包版本冲突,项目使用 **Central Package Management**
需要在项目根目录创建 `Directory.Packages.props`
```xml
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.7" />
<PackageVersion Include="ReactiveUI.Avalonia" Version="11.3.0" />
<PackageVersion Include="xUnit" Version="2.6.6" />
<PackageVersion Include="xUnit.runner.visualstudio" Version="2.5.6" />
</ItemGroup>
</Project>
```
各项目的 `.csproj` 文件中引用包时**不指定版本号**
```xml
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="ReactiveUI.Avalonia" />
</ItemGroup>
```
**优势**
- 所有项目使用相同的包版本
- 升级版本时只需修改一处
- 避免版本冲突导致的编译或运行时错误
---

View File

@@ -0,0 +1,330 @@
# Testing Strategy
本节定义控件库的测试方法和组织策略,确保核心功能的稳定性和可靠性。
---
### Testing Pyramid
```
E2E Tests (手动测试)
/ \
Integration Tests (可选)
/ \
Unit Tests (核心逻辑)
```
**测试重点分配:**
- **Unit Tests (70%)**:核心业务逻辑、数据模型、工具类
- **Integration Tests (可选)**:控件与 ReactiveUI 的集成、主题系统
- **Manual Tests (必须)**UI 渲染、主题切换、用户交互
**MVP 阶段不包括:**
- UI 自动化测试Avalonia 提供 Headless 测试包,但 MVP 阶段依赖手动测试)
- 性能基准测试(除非发现明显性能问题)
- 跨平台兼容性测试(仅在 Windows 上测试)
---
### Test Organization
**测试项目结构:**
```
Penguin.AvaloniaUI.Tests/
├── Controls/
│ ├── PropertyGridTests.cs
│ ├── UserGuideTests.cs
│ └── ...
├── Layouts/
│ └── TwoColumnLayoutTests.cs
├── Themes/
│ └── ThemeManagerTests.cs
├── Utils/
│ ├── ReflectionHelperTests.cs
│ └── Converters/
│ └── TypeToEditorConverterTests.cs
└── TestHelpers/
├── MockObjects.cs
└── TestBase.cs
```
**命名约定:**
- 测试类:`{ClassName}Tests`
- 测试方法:`{MethodName}_{Scenario}_{ExpectedResult}`
---
### Unit Test Examples
**示例 1测试 PropertyGrid 的反射逻辑**
```csharp
namespace Penguin.AvaloniaUI.Tests.Controls;
public class PropertyGridTests
{
[Fact]
public void RefreshProperties_WithValidObject_ShouldParseAllPublicProperties()
{
// Arrange
var testObject = new TestSettings
{
Name = "Test",
Age = 25,
IsEnabled = true
};
var propertyGrid = new PropertyGrid
{
SelectedObject = testObject
};
// Act
var properties = propertyGrid.Properties;
// Assert
Assert.Equal(3, properties.Count);
Assert.Contains(properties, p => p.Name == "Name" && p.PropertyType == typeof(string));
Assert.Contains(properties, p => p.Name == "Age" && p.PropertyType == typeof(int));
Assert.Contains(properties, p => p.Name == "IsEnabled" && p.PropertyType == typeof(bool));
}
[Fact]
public void RefreshProperties_WithCategoryAttribute_ShouldGroupByCategory()
{
// Arrange
var testObject = new CategorizedSettings();
var propertyGrid = new PropertyGrid
{
SelectedObject = testObject
};
// Act
var properties = propertyGrid.Properties;
var generalCategory = properties.Where(p => p.Category == "General").ToList();
var advancedCategory = properties.Where(p => p.Category == "Advanced").ToList();
// Assert
Assert.NotEmpty(generalCategory);
Assert.NotEmpty(advancedCategory);
}
[Fact]
public void RefreshProperties_WithNullObject_ShouldClearProperties()
{
// Arrange
var propertyGrid = new PropertyGrid
{
SelectedObject = new TestSettings()
};
// Act
propertyGrid.SelectedObject = null;
// Assert
Assert.Empty(propertyGrid.Properties);
}
}
```
---
**示例 2测试 ThemeManager**
```csharp
namespace Penguin.AvaloniaUI.Tests.Themes;
public class ThemeManagerTests
{
[Fact]
public void ApplyTheme_WithLightTheme_ShouldLoadLightResources()
{
// Arrange
var app = CreateTestApplication();
// Act
ThemeManager.ApplyTheme(ThemeType.Light);
// Assert
var textPrimary = app.Resources["TextPrimary"] as SolidColorBrush;
Assert.NotNull(textPrimary);
// 浅色主题的主要文本应为深色
Assert.True(textPrimary.Color.R < 128);
}
[Fact]
public void ApplyTheme_SwitchingThemes_ShouldTriggerThemeChangedEvent()
{
// Arrange
var eventTriggered = false;
ThemeManager.ThemeChanged += (sender, type) => eventTriggered = true;
// Act
ThemeManager.ApplyTheme(ThemeType.Dark);
// Assert
Assert.True(eventTriggered);
}
private Application CreateTestApplication()
{
// 创建测试用的 Application 实例
return new Application();
}
}
```
---
**示例 3测试 UserGuide 步骤流转**
```csharp
namespace Penguin.AvaloniaUI.Tests.Controls;
public class UserGuideTests
{
[Fact]
public void NextStepCommand_WhenNotLastStep_ShouldIncrementIndex()
{
// Arrange
var userGuide = new UserGuide();
userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });
userGuide.Steps.Add(new GuideStep { Title = "Step 2", Order = 1 });
userGuide.Steps.Add(new GuideStep { Title = "Step 3", Order = 2 });
// Act
userGuide.NextStepCommand.Execute(null);
// Assert
Assert.Equal(1, userGuide.CurrentStepIndex);
}
[Fact]
public void NextStepCommand_WhenLastStep_ShouldTriggerCompleteEvent()
{
// Arrange
var userGuide = new UserGuide();
userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });
var completedTriggered = false;
userGuide.Completed += (sender, args) => completedTriggered = true;
// Act
userGuide.NextStepCommand.Execute(null);
// Assert
Assert.True(completedTriggered);
}
[Fact]
public void PreviousStepCommand_WhenFirstStep_ShouldNotExecute()
{
// Arrange
var userGuide = new UserGuide();
userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });
// Act
var canExecute = userGuide.PreviousStepCommand.CanExecute(null);
// Assert
Assert.False(canExecute);
}
}
```
---
### Manual Testing Checklist
以下场景**必须通过示例应用手动测试**
**PropertyGrid**
- [ ] 绑定包含 6 种基础类型的对象,所有编辑器正确显示
- [ ] 修改属性值SelectedObject 对象同步更新
- [ ] 只读属性禁用编辑器或显示为灰色
- [ ] 属性按 Category 分组显示,分组标题清晰
- [ ] 主题切换时PropertyGrid 颜色正确更新
- [ ] 50 个属性的生成时间 < 200ms使用秒表或 Stopwatch 测量)
**UserGuide**
- [ ] 引导流程正确显示 Overlay 遮罩
- [ ] 目标控件区域挖空显示(清晰可见)
- [ ] 提示框在目标控件附近正确定位Top/Bottom/Left/Right
- [ ] "下一步"/"上一步"按钮功能正常
- [ ] 步骤进度显示正确(如 "2/5"
- [ ] "跳过"和"完成"按钮触发完成事件
**主题系统:**
- [ ] 主题切换在 100ms 内完成,无闪烁
- [ ] 所有控件的颜色正确响应主题变化
- [ ] 浅色和暗色主题的对比度舒适(文本清晰可读)
- [ ] 主题切换时无控件错位或布局异常
**跨功能集成:**
- [ ] 在 PropertyGrid 编辑时切换主题,无崩溃
- [ ] 在 UserGuide 进行中修改 PropertyGrid无异常
- [ ] 窗口缩放时,所有控件布局正确调整
---
### Test Helpers
**创建测试辅助类减少重复代码:**
```csharp
namespace Penguin.AvaloniaUI.Tests.TestHelpers;
public static class MockObjects
{
public class TestSettings
{
public string Name { get; set; } = "Default";
public int Age { get; set; } = 0;
public bool IsEnabled { get; set; } = false;
}
public class CategorizedSettings
{
[Category("General")]
public string Name { get; set; } = "Default";
[Category("Advanced")]
public int Timeout { get; set; } = 5000;
}
}
public class TestBase
{
protected Application CreateTestApplication()
{
var app = new Application();
// 加载测试用主题资源
return app;
}
}
```
---
### Running Tests
```bash
# 运行所有测试
dotnet test
# 运行特定测试类
dotnet test --filter "FullyQualifiedName~PropertyGridTests"
# 运行特定测试方法
dotnet test --filter "Name~RefreshProperties_WithValidObject"
# 生成代码覆盖率报告
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=html
```
**Visual Studio 测试运行器:**
1. 打开 Test Explorer测试 → 测试资源管理器)
2. 点击"运行所有测试"或右键运行特定测试
3. 查看测试结果和失败详情
---

View File

@@ -0,0 +1,135 @@
# Unified Project Structure
基于 Monorepo 策略和 .NET 项目组织最佳实践,以下是详细的项目文件结构:
```plaintext
D:\32_avalonia.ui/
├── .github/ # GitHub 工作流Post-MVP
│ └── workflows/
│ ├── ci.yml # 持续集成
│ └── release.yml # 发布流程
├── src/ # 源代码目录
│ ├── Penguin.AvaloniaUI/ # 核心控件库项目
│ │ ├── Controls/ # 业务场景控件
│ │ │ ├── PropertyGrid/
│ │ │ │ ├── PropertyGrid.cs # 主控件类
│ │ │ │ ├── PropertyItem.cs # 数据模型
│ │ │ │ ├── PropertyEditorFactory.cs # 编辑器工厂
│ │ │ │ └── Themes/
│ │ │ │ ├── PropertyGrid.axaml # 默认模板
│ │ │ │ └── PropertyGrid.axaml.cs
│ │ │ ├── UserGuide/
│ │ │ │ ├── UserGuide.cs
│ │ │ │ ├── GuideStep.cs
│ │ │ │ ├── Overlay.cs
│ │ │ │ ├── RichTooltip.cs
│ │ │ │ └── Themes/
│ │ │ │ ├── UserGuide.axaml
│ │ │ │ ├── Overlay.axaml
│ │ │ │ └── RichTooltip.axaml
│ │ │ └── ... (其他控件)
│ │ │
│ │ ├── Layouts/ # 布局控件
│ │ │ ├── TwoColumnLayout.cs
│ │ │ └── Themes/
│ │ │ └── TwoColumnLayout.axaml
│ │ │
│ │ ├── Themes/ # 主题系统
│ │ │ ├── ThemeManager.cs # 主题管理器
│ │ │ ├── ThemeInfo.cs # 主题信息模型
│ │ │ ├── ColorSystem/ # 颜色系统定义
│ │ │ │ ├── LightColors.axaml # 浅色主题颜色
│ │ │ │ └── DarkColors.axaml # 暗色主题颜色
│ │ │ ├── LightTheme.axaml # 浅色主题资源
│ │ │ └── DarkTheme.axaml # 暗色主题资源
│ │ │
│ │ ├── Utils/ # 工具类
│ │ │ ├── Converters/ # 值转换器
│ │ │ │ ├── BoolToVisibilityConverter.cs
│ │ │ │ └── TypeToEditorConverter.cs
│ │ │ ├── Helpers/ # 辅助类
│ │ │ │ ├── ReflectionHelper.cs # 反射工具
│ │ │ │ └── ValidationHelper.cs # 验证工具
│ │ │ └── Extensions/ # 扩展方法
│ │ │ └── ObservableExtensions.cs
│ │ │
│ │ ├── Assets/ # 资源文件
│ │ │ └── Icons/ # 图标资源
│ │ │
│ │ ├── Penguin.AvaloniaUI.csproj # 项目文件
│ │ └── AssemblyInfo.cs # 程序集信息
│ │
│ ├── Penguin.AvaloniaUI.SourceGenerators/ # Source GeneratorPost-MVP
│ │ ├── PropertyGridGenerator.cs
│ │ └── Penguin.AvaloniaUI.SourceGenerators.csproj
│ │
│ ├── Example/ # 示例应用项目
│ │ ├── App.axaml # 应用程序资源
│ │ ├── App.axaml.cs
│ │ ├── ViewModels/ # 视图模型
│ │ │ ├── MainWindowViewModel.cs
│ │ │ ├── PropertyGridDemoViewModel.cs
│ │ │ └── UserGuideDemoViewModel.cs
│ │ ├── Views/ # 视图/页面
│ │ │ ├── MainWindow.axaml
│ │ │ ├── MainWindow.axaml.cs
│ │ │ ├── Pages/
│ │ │ │ ├── ColorSystemPage.axaml # 颜色系统演示
│ │ │ │ ├── PropertyGridPage.axaml # PropertyGrid 演示
│ │ │ │ ├── UserGuidePage.axaml # UserGuide 演示
│ │ │ │ └── ComprehensiveDemoPage.axaml # 综合演示
│ │ │ └── ...
│ │ ├── Models/ # 测试数据模型
│ │ │ ├── DemoSettings.cs # PropertyGrid 测试类
│ │ │ └── LargeSettings.cs # 50 属性测试类
│ │ ├── Assets/ # 示例应用资源
│ │ ├── Example.csproj
│ │ └── Program.cs
│ │
│ └── Penguin.AvaloniaUI.Tests/ # 单元测试项目
│ ├── Controls/
│ │ ├── PropertyGridTests.cs
│ │ ├── UserGuideTests.cs
│ │ └── ...
│ ├── Layouts/
│ │ └── TwoColumnLayoutTests.cs
│ ├── Themes/
│ │ └── ThemeManagerTests.cs
│ ├── Utils/
│ │ └── ReflectionHelperTests.cs
│ └── Penguin.AvaloniaUI.Tests.csproj
├── docs/ # 项目文档
│ ├── prd.md # 产品需求文档
│ ├── architecture.md # 架构文档(本文档)
│ ├── brief.md # 项目简介
│ └── brainstorm.md # 头脑风暴
├── .bmad-core/ # BMAD 框架配置
│ ├── core-config.yaml
│ ├── agents/
│ ├── tasks/
│ └── templates/
├── .editorconfig # 编辑器配置
├── .gitignore # Git 忽略规则
├── Directory.Packages.props # 统一包版本管理
├── README.md # 项目说明
├── LICENSE # 许可证Post-MVP
└── Penguin.AvaloniaUI.sln # 解决方案文件
```
**关键目录说明:**
| 目录 | 用途 | 命名空间 |
|------|------|----------|
| `Controls/` | 业务场景控件实现 | `Penguin.AvaloniaUI.Controls.*` |
| `Layouts/` | 布局控件实现 | `Penguin.AvaloniaUI.Layouts` |
| `Themes/` | 主题和样式系统 | `Penguin.AvaloniaUI.Themes` |
| `Utils/` | 工具类和辅助方法 | `Penguin.AvaloniaUI.Utils.*` |
| `Example/Views/Pages/` | 示例应用页面 | `Example.Views.Pages` |
| `Example/ViewModels/` | 示例应用 ViewModel | `Example.ViewModels` |
---

View File

@@ -1,22 +1,21 @@
using System; using System;
using Avalonia; using Avalonia;
namespace Example namespace Example;
{
internal class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer. internal class Program
public static AppBuilder BuildAvaloniaApp() {
=> AppBuilder.Configure<App>() // Initialization code. Don't use any Avalonia, third-party APIs or any
.UsePlatformDetect() // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
.WithInterFont() // yet and stuff might break.
.LogToTrace(); [STAThread]
} public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
} }

View File

@@ -6,4 +6,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.7" />
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.0" />
</ItemGroup>
</Project> </Project>