docs: update architecture
This commit is contained in:
2439
docs/architecture.md
Normal file
2439
docs/architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
109
docs/architecture/coding-standards.md
Normal file
109
docs/architecture/coding-standards.md
Normal 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` |
|
||||
| 测试方法 | PascalCase,MethodName_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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
357
docs/architecture/components.md
Normal file
357
docs/architecture/components.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# Components
|
||||
|
||||
基于架构模式和数据模型,系统划分为以下核心组件。每个组件负责特定功能,并通过清晰的接口与其他组件交互。
|
||||
|
||||
---
|
||||
|
||||
### PropertyGrid
|
||||
|
||||
**职责**:自动生成属性编辑 UI,支持多种属性类型、分组、只读属性和基础验证。
|
||||
|
||||
**关键接口**:
|
||||
- `SelectedObject`(依赖属性)- 绑定的数据对象,变化时自动刷新属性列表
|
||||
- `Properties`(ObservableCollection<PropertyItem>)- 解析后的属性列表
|
||||
- `PropertyValueChanged`(事件)- 属性值变化时触发
|
||||
|
||||
**依赖关系**:
|
||||
- 依赖 TwoColumnLayout 进行布局
|
||||
- 依赖 PropertyEditors(TextBox、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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
207
docs/architecture/data-models.md
Normal file
207
docs/architecture/data-models.md
Normal 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
|
||||
|
||||
---
|
||||
|
||||
180
docs/architecture/development-workflow.md
Normal file
180
docs/architecture/development-workflow.md
Normal 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
428
docs/architecture/error-handling.md
Normal file
428
docs/architecture/error-handling.md
Normal 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));
|
||||
|
||||
// 核心逻辑
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
144
docs/architecture/high-level-architecture.md
Normal file
144
docs/architecture/high-level-architecture.md
Normal 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 Generator(Post-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 原生支持 MVVM,ReactiveUI 提供强大的响应式能力,降低复杂交互的实现难度
|
||||
|
||||
- **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 阶段不强制 DI,ThemeManager 等服务可通过静态类或单例访问
|
||||
- _Rationale_:控件库不需要复杂的 DI 容器,保持简单;Post-MVP 可根据需要引入
|
||||
|
||||
- **Attribute-Driven Configuration**:PropertyGrid 通过 Attribute(如 `[Category]`、`[Browsable]`)配置属性显示
|
||||
- _Rationale_:符合 .NET 生态习惯(类似 WinForms PropertyGrid),降低学习成本
|
||||
|
||||
- **Composition over Inheritance**:复杂控件通过组合基础控件实现(如 UserGuide 组合 Overlay + RichTooltip)
|
||||
- _Rationale_:提高代码复用性,避免深层继承带来的维护问题
|
||||
|
||||
---
|
||||
|
||||
17
docs/architecture/index.md
Normal file
17
docs/architecture/index.md
Normal 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)
|
||||
- [文档完成](#文档完成)
|
||||
42
docs/architecture/introduction.md
Normal file
42
docs/architecture/introduction.md
Normal 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 |
|
||||
|
||||
---
|
||||
|
||||
415
docs/architecture/performance-optimization.md
Normal file
415
docs/architecture/performance-optimization.md
Normal 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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**问题 2:ObservableCollection 过度使用**
|
||||
|
||||
```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();
|
||||
```
|
||||
|
||||
**陷阱 3:XAML 中的复杂绑定**
|
||||
|
||||
```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)
|
||||
|
||||
---
|
||||
|
||||
64
docs/architecture/tech-stack.md
Normal file
64
docs/architecture/tech-stack.md
Normal 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/CD,Post-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 | 性能测量 | 验证 NFR(60fps、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>
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 所有项目使用相同的包版本
|
||||
- 升级版本时只需修改一处
|
||||
- 避免版本冲突导致的编译或运行时错误
|
||||
|
||||
---
|
||||
|
||||
330
docs/architecture/testing-strategy.md
Normal file
330
docs/architecture/testing-strategy.md
Normal 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. 查看测试结果和失败详情
|
||||
|
||||
---
|
||||
|
||||
135
docs/architecture/unified-project-structure.md
Normal file
135
docs/architecture/unified-project-structure.md
Normal 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 Generator(Post-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` |
|
||||
|
||||
---
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Avalonia;
|
||||
|
||||
namespace Example
|
||||
namespace Example;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
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.
|
||||
@@ -18,5 +18,4 @@ namespace Example
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.7" />
|
||||
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user