Files
ui/docs/architecture/components.md
2025-10-16 15:08:42 +08:00

11 KiB
Raw Blame History

Components

基于架构模式和数据模型,系统划分为以下核心组件。每个组件负责特定功能,并通过清晰的接口与其他组件交互。


PropertyGrid

职责:自动生成属性编辑 UI支持多种属性类型、分组、只读属性和基础验证。

关键接口

  • SelectedObject(依赖属性)- 绑定的数据对象,变化时自动刷新属性列表
  • PropertiesObservableCollection- 解析后的属性列表
  • PropertyValueChanged(事件)- 属性值变化时触发

依赖关系

  • 依赖 TwoColumnLayout 进行布局
  • 依赖 PropertyEditorsTextBox、NumericUpDown、ComboBox 等)
  • 依赖 ThemeManager 获取当前主题颜色
  • 使用反射System.Reflection解析对象属性

技术细节

  • 继承自 TemplatedControl
  • 使用 ReactiveUI 的 WhenAnyValue 监听 SelectedObject 变化
  • 通过 PropertyInfo.GetCustomAttributes<T>() 读取 Attribute[Category][Browsable]
  • 编辑器选择逻辑:根据 PropertyItem.PropertyType 映射到对应控件

实现示例(核心逻辑):

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

职责:提供"标签-编辑器"配对的布局控件,左列固定或自适应宽度,右列占据剩余空间。

关键接口

  • ItemsObservableCollection- 行集合
  • LabelWidthdouble- 左列宽度(默认 Auto
  • RowSpacingdouble- 行间距(默认 8px

依赖关系

  • 无外部依赖(纯布局控件)
  • 被 PropertyGrid 使用

技术细节

  • 继承自 Panel 或内部使用 Grid
  • 重写 MeasureOverrideArrangeOverride 实现自定义布局
  • 响应主题系统(标签文本颜色使用 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 和引导提示框的显示。

关键接口

  • StepsObservableCollection- 引导步骤集合
  • CurrentStepIndexint- 当前步骤索引
  • NextStepCommandICommand- 下一步命令
  • PreviousStepCommandICommand- 上一步命令
  • SkipCommandICommand- 跳过命令
  • Completed(事件)- 引导完成事件

依赖关系

  • 依赖 Overlay 控件显示遮罩
  • 依赖 RichTooltip 显示引导提示框
  • 依赖 GuideStep 数据模型

技术细节

  • 继承自 ContentControl
  • 使用 ReactiveUI 的 ReactiveCommand 实现步骤流转
  • 步骤切换时触发动画淡入淡出200ms

核心逻辑

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

职责:在窗口上层显示半透明遮罩,可选地挖空特定控件区域以聚焦目标。

关键接口

  • IsVisiblebool- 显示/隐藏遮罩
  • TargetControlControl?- 挖空目标控件
  • BackgroundOpacitydouble- 不透明度(默认 0.5
  • BackgroundColorColor- 遮罩颜色

依赖关系

  • 被 UserGuide 使用
  • 依赖 ThemeManager暗色模式下调整遮罩颜色

技术细节

  • 继承自 ContentControlPanel
  • 使用绝对定位覆盖整个窗口
  • 挖空逻辑:通过 Clip 或多个 Rectangle 实现

视觉效果

┌─────────────────────────────────┐
│████████████████████████████████│ ← 半透明遮罩
│████████┌──────────┐████████████│
│████████│ Target   │████████████│ ← 挖空区域清晰可见
│████████└──────────┘████████████│
│████████████████████████████████│
└─────────────────────────────────┘

RichTooltip

职责:显示支持基础富文本的增强 Tooltip。

关键接口

  • Contentstring- 提示内容,支持简单 Markdown 语法(**粗体***斜体*\n 换行)
  • MaxWidthdouble- 最大宽度(默认 300px

依赖关系

  • 扩展 Avalonia 的 ToolTip
  • 被 UserGuide 使用

技术细节

  • 内部使用 TextBlock 配合 InlinesRun、Bold、Italic
  • 简单的 Markdown 解析器(不依赖第三方库)
  • 响应主题系统(背景色 Surface,文本色 TextPrimary

解析示例

输入:"**注意**: 这是一个重要提示。\n请仔细阅读。"
输出:
  <TextBlock>
    <Bold>注意</Bold>: 这是一个重要提示。
    <LineBreak />
    请仔细阅读。
  </TextBlock>

ThemeManager

职责:管理主题切换,提供全局主题访问接口。

关键接口

  • ApplyTheme(ThemeType type)(方法)- 切换主题
  • CurrentThemeThemeType- 当前主题类型
  • ThemeChanged(事件)- 主题变化事件

依赖关系

  • 被所有控件使用(通过静态方法或单例)
  • 依赖 Avalonia 的 Application.Current.Resources

技术细节

  • 静态类或单例模式
  • 主题切换通过替换 ResourceDictionary 实现
  • 支持动画过渡可选100ms 淡入淡出)

实现示例

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

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