feat.文档完善

This commit is contained in:
lincube
2026-06-08 12:18:58 +08:00
parent 49af6601aa
commit 8d1dbaea54
8 changed files with 4841 additions and 0 deletions

View File

@@ -0,0 +1,683 @@
# 插件生命周期
本文档详细介绍阑山桌面插件的生命周期、加载流程和各个阶段的职责。
## 生命周期概览
插件从加载到卸载经历以下阶段:
```
┌─────────────────────────────────────────────────────────┐
│ 插件生命周期 │
└─────────────────────────────────────────────────────────┘
1. 发现 (Discovery)
├─ 扫描插件目录
├─ 读取 plugin.json
└─ 验证基本信息
2. 加载 (Load)
├─ 创建 PluginLoadContext
├─ 加载程序集
├─ 解析依赖关系
└─ 验证兼容性
3. 实例化 (Instantiate)
├─ 反射查找 IPlugin 实现
├─ 创建插件实例
└─ 注入依赖
4. 初始化 (Initialize)
├─ 调用 InitializeAsync()
├─ 注册组件
├─ 注册设置页
├─ 注册服务
└─ 订阅事件
5. 运行中 (Running)
├─ 组件渲染和更新
├─ 处理用户交互
├─ 响应事件
└─ 执行后台任务
6. 关闭 (Shutdown)
├─ 调用 ShutdownAsync()
├─ 保存状态
├─ 取消订阅
├─ 清理资源
└─ 卸载程序集
```
## 详细阶段说明
### 1. 发现阶段 (Discovery)
**时机**: 宿主启动时
**职责**: 扫描和识别插件
**流程**:
```csharp
// PluginDiscoveryService.cs (宿主代码)
public List<PluginDescriptor> DiscoverPlugins()
{
var pluginsDir = Path.Combine(
AppDataPath,
"plugins"
);
var descriptors = new List<PluginDescriptor>();
// 1. 扫描插件目录
foreach (var dir in Directory.GetDirectories(pluginsDir))
{
var manifestPath = Path.Combine(dir, "plugin.json");
// 2. 读取 plugin.json
if (!File.Exists(manifestPath))
{
_logger.LogWarning($"Plugin manifest not found: {dir}");
continue;
}
try
{
var json = File.ReadAllText(manifestPath);
var manifest = JsonSerializer.Deserialize<PluginManifest>(json);
// 3. 验证基本信息
if (string.IsNullOrEmpty(manifest?.Id))
{
_logger.LogWarning($"Invalid plugin manifest: {dir}");
continue;
}
descriptors.Add(new PluginDescriptor
{
Id = manifest.Id,
Name = manifest.Name,
Version = manifest.Version,
Directory = dir,
Manifest = manifest
});
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to read plugin manifest: {dir}");
}
}
return descriptors;
}
```
**开发者注意事项**:
- ✅ 确保 `plugin.json` 存在且格式正确
- ✅ 确保插件 ID 唯一
- ✅ 确保版本号符合语义化版本规范
### 2. 加载阶段 (Load)
**时机**: 发现插件后
**职责**: 加载插件程序集
**流程**:
```csharp
// PluginLoader.cs (宿主代码)
public PluginLoadResult LoadPlugin(PluginDescriptor descriptor)
{
try
{
// 1. 创建隔离的加载上下文
var loadContext = new PluginLoadContext(descriptor.Directory);
// 2. 查找主程序集
var assemblyPath = Path.Combine(
descriptor.Directory,
$"{descriptor.Id}.dll"
);
if (!File.Exists(assemblyPath))
{
return PluginLoadResult.Failed(
$"Plugin assembly not found: {assemblyPath}"
);
}
// 3. 加载程序集
var assembly = loadContext.LoadFromAssemblyPath(assemblyPath);
// 4. 验证依赖
if (!ValidateDependencies(descriptor.Manifest.Dependencies))
{
return PluginLoadResult.Failed("Dependency validation failed");
}
// 5. 验证宿主版本兼容性
if (!IsHostVersionCompatible(descriptor.Manifest.MinHostVersion))
{
return PluginLoadResult.Failed(
$"Incompatible host version. Required: {descriptor.Manifest.MinHostVersion}"
);
}
return PluginLoadResult.Success(assembly, loadContext);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to load plugin: {descriptor.Id}");
return PluginLoadResult.Failed(ex.Message);
}
}
```
**依赖解析**:
```json
// plugin.json
{
"Dependencies": [
{
"PluginId": "com.example.anotherplugin",
"MinVersion": "1.0.0"
}
]
}
```
**开发者注意事项**:
- ✅ 主程序集名称应与插件 ID 匹配(或在清单中指定)
- ✅ 所有依赖的 DLL 应在插件目录中
- ✅ 声明对其他插件的依赖关系
### 3. 实例化阶段 (Instantiate)
**时机**: 程序集加载后
**职责**: 创建插件实例
**流程**:
```csharp
// PluginActivator.cs (宿主代码)
public IPlugin? CreatePluginInstance(Assembly assembly)
{
try
{
// 1. 查找 IPlugin 实现类
var pluginType = assembly.GetTypes()
.FirstOrDefault(t =>
typeof(IPlugin).IsAssignableFrom(t) &&
!t.IsAbstract &&
!t.IsInterface
);
if (pluginType == null)
{
_logger.LogError("No IPlugin implementation found");
return null;
}
// 2. 创建实例
var plugin = Activator.CreateInstance(pluginType) as IPlugin;
if (plugin == null)
{
_logger.LogError("Failed to create plugin instance");
return null;
}
return plugin;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to instantiate plugin");
return null;
}
}
```
**开发者注意事项**:
- ✅ 插件类必须有公共无参构造函数
- ✅ 一个插件程序集只能有一个 IPlugin 实现
- ✅ 不要在构造函数中执行耗时操作
### 4. 初始化阶段 (Initialize)
**时机**: 插件实例创建后
**职责**: 注册组件、服务和事件
**插件代码示例**:
```csharp
public class MyPlugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
// 1. 记录日志
context.Logger.LogInformation($"{Name} is initializing...");
// 2. 注册组件
var componentRegistry = context.Services
.GetService<IComponentRegistry>();
if (componentRegistry != null)
{
// 注册多个组件
componentRegistry.RegisterComponent<WeatherComponent>();
componentRegistry.RegisterComponent<ClockComponent>();
context.Logger.LogInformation("Components registered");
}
// 3. 注册设置页
var settingsRegistry = context.Services
.GetService<ISettingsPageRegistry>();
if (settingsRegistry != null)
{
settingsRegistry.RegisterPage<MySettingsPage>(
title: "我的插件",
category: "插件"
);
context.Logger.LogInformation("Settings page registered");
}
// 4. 注册公共 IPC 服务(如果需要)
var ipcBuilder = context.Services
.GetService<IPluginPublicIpcBuilder>();
if (ipcBuilder != null)
{
ipcBuilder.AddService<IMyPublicService>(
objectId: "default",
notifyIds: new[] { "myplugin.event.changed" }
);
}
// 5. 订阅宿主事件
var eventBus = context.Services
.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
}
// 6. 初始化后台服务(如果有)
await InitializeBackgroundServicesAsync(context);
context.Logger.LogInformation($"{Name} initialized successfully");
}
private void OnThemeChanged(ThemeChangedEvent evt)
{
// 响应主题变更
}
private async Task InitializeBackgroundServicesAsync(IPluginContext context)
{
// 启动定时任务等
await Task.CompletedTask;
}
}
```
**初始化最佳实践**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
try
{
// ✅ 使用 try-catch 捕获异常
// ✅ 记录详细的日志
// ✅ 验证服务是否可用
// ✅ 使用 async/await 处理异步操作
// ❌ 不要阻塞 UI 线程
// ❌ 不要执行超过 5 秒的操作
_context = context;
// 快速初始化
RegisterComponents(context);
RegisterSettings(context);
// 耗时操作使用后台任务
_ = Task.Run(async () =>
{
await LoadDataAsync();
});
}
catch (Exception ex)
{
context.Logger.LogError(ex, "Plugin initialization failed");
throw; // 让宿主知道初始化失败
}
}
```
**开发者注意事项**:
- ✅ InitializeAsync 应尽快完成(< 5 秒)
- ✅ 耗时操作放在后台线程
- ✅ 妥善处理异常
- ✅ 保存 IPluginContext 引用供后续使用
- ❌ 不要在此阶段访问其他插件的服务(可能还未加载)
### 5. 运行中阶段 (Running)
**时机**: 初始化完成后
**职责**: 响应用户交互和系统事件
**组件更新循环**:
```csharp
// 宿主会定时调用组件的 UpdateAsync()
public class MyComponent : ComponentBase
{
private HttpClient _httpClient;
private DateTime _lastUpdate;
public override async Task UpdateAsync()
{
// 定时更新数据(默认 1 秒调用一次)
if (DateTime.Now - _lastUpdate > TimeSpan.FromMinutes(5))
{
await FetchDataAsync();
_lastUpdate = DateTime.Now;
}
}
private async Task FetchDataAsync()
{
try
{
var data = await _httpClient.GetStringAsync("https://api.example.com/data");
// 更新组件属性
Data = ParseData(data);
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to fetch data");
}
}
}
```
**事件响应**:
```csharp
public class MyPlugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
// 订阅系统事件
var eventBus = context.Services.GetService<IEventBus>();
eventBus?.Subscribe<ThemeChangedEvent>(OnThemeChanged);
eventBus?.Subscribe<LanguageChangedEvent>(OnLanguageChanged);
eventBus?.Subscribe<SettingChangedEvent>(OnSettingChanged);
}
private void OnThemeChanged(ThemeChangedEvent evt)
{
_logger.LogInformation($"Theme changed to: {evt.NewTheme}");
// 更新组件外观
}
private void OnLanguageChanged(LanguageChangedEvent evt)
{
_logger.LogInformation($"Language changed to: {evt.NewLanguage}");
// 重新加载本地化资源
}
private void OnSettingChanged(SettingChangedEvent evt)
{
if (evt.Key.StartsWith("MyPlugin."))
{
// 响应插件设置变更
}
}
}
```
**开发者注意事项**:
- ✅ 组件更新应快速完成
- ✅ 使用缓存避免重复计算
- ✅ 异步操作使用 async/await
- ✅ 妥善处理网络错误
- ❌ 不要在 UpdateAsync 中执行超过 1 秒的操作
### 6. 关闭阶段 (Shutdown)
**时机**:
- 宿主应用退出
- 插件被禁用
- 插件热重载
**职责**: 清理资源和保存状态
**插件代码示例**:
```csharp
public class MyPlugin : IPlugin
{
private IDisposable? _eventSubscription;
private HttpClient? _httpClient;
private CancellationTokenSource? _cts;
public async Task ShutdownAsync()
{
try
{
_logger.LogInformation($"{Name} is shutting down...");
// 1. 取消正在进行的操作
_cts?.Cancel();
// 2. 取消事件订阅
_eventSubscription?.Dispose();
// 3. 保存状态
await SaveStateAsync();
// 4. 释放资源
_httpClient?.Dispose();
// 5. 停止后台任务
await StopBackgroundServicesAsync();
_logger.LogInformation($"{Name} shutdown completed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during plugin shutdown");
// 不要抛出异常,避免影响其他插件
}
}
private async Task SaveStateAsync()
{
// 保存插件状态到设置
_context?.Settings.SetValue("LastUpdateTime", DateTime.Now);
await Task.CompletedTask;
}
private async Task StopBackgroundServicesAsync()
{
// 停止定时任务等
await Task.CompletedTask;
}
}
```
**关闭最佳实践**:
```csharp
public async Task ShutdownAsync()
{
try
{
// ✅ 尽快完成(< 3 秒)
// ✅ 使用 try-catch 避免异常
// ✅ 按相反顺序清理资源
// ✅ 保存关键状态
// ❌ 不要抛出异常
// ❌ 不要执行耗时操作
// 取消异步操作
_cancellationTokenSource?.Cancel();
// 取消订阅(防止内存泄漏)
UnsubscribeEvents();
// 释放托管资源
DisposeResources();
// 保存状态(快速)
SaveCriticalState();
}
catch (Exception ex)
{
// 记录但不抛出
_logger?.LogError(ex, "Shutdown error");
}
}
```
**开发者注意事项**:
- ✅ ShutdownAsync 必须快速完成(< 3 秒)
- ✅ 取消所有异步操作
- ✅ 取消事件订阅(防止内存泄漏)
- ✅ 释放所有 IDisposable 资源
- ✅ 保存关键状态
- ❌ 不要抛出异常
## 生命周期事件
插件可以监听宿主的生命周期事件:
```csharp
public class MyPlugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
var hostLifecycle = context.Services
.GetService<IHostLifecycleService>();
if (hostLifecycle != null)
{
hostLifecycle.Starting += OnHostStarting;
hostLifecycle.Started += OnHostStarted;
hostLifecycle.Stopping += OnHostStopping;
hostLifecycle.Stopped += OnHostStopped;
}
}
private void OnHostStarting(object? sender, EventArgs e)
{
// 宿主正在启动
}
private void OnHostStarted(object? sender, EventArgs e)
{
// 宿主已启动完成
}
private void OnHostStopping(object? sender, EventArgs e)
{
// 宿主即将关闭
}
private void OnHostStopped(object? sender, EventArgs e)
{
// 宿主已关闭
}
public async Task ShutdownAsync()
{
// 取消订阅
var hostLifecycle = _context?.Services
.GetService<IHostLifecycleService>();
if (hostLifecycle != null)
{
hostLifecycle.Starting -= OnHostStarting;
hostLifecycle.Started -= OnHostStarted;
hostLifecycle.Stopping -= OnHostStopping;
hostLifecycle.Stopped -= OnHostStopped;
}
}
}
```
## 错误处理
### 初始化失败
如果插件初始化失败,宿主会:
1. 记录错误日志
2. 标记插件为"加载失败"
3. 继续加载其他插件
4. 在 UI 中显示失败状态
### 运行时异常
组件代码中的未捕获异常:
1. 被宿主捕获并记录
2. 组件标记为"错误"状态
3. 组件停止更新
4. 不影响其他组件
### 关闭超时
如果 ShutdownAsync 超过 5 秒:
1. 宿主强制终止
2. 记录超时警告
3. 继续关闭其他插件
## 插件热重载
宿主支持插件热重载(开发中功能):
```
1. 用户触发重载
2. 调用 ShutdownAsync()
3. 卸载程序集
4. 重新加载程序集
5. 创建新实例
6. 调用 InitializeAsync()
7. 恢复组件状态
```
## 小结
插件生命周期的关键点:
-**发现**: 确保 plugin.json 正确
-**加载**: 管理好依赖关系
-**初始化**: 快速注册,耗时操作后台执行
-**运行**: 高效更新,异步处理
-**关闭**: 及时清理,避免异常
## 下一步
- [组件系统详解](02-组件系统.md) - 学习组件开发
- [设置系统](03-设置系统.md) - 管理插件配置
- [插件通信](05-插件通信.md) - 插件间协作
- [IPlugin 接口](../03-API参考/01-IPlugin接口.md) - API 详细文档

View File

@@ -0,0 +1,789 @@
# 组件系统详解
本文档详细介绍阑山桌面的桌面组件Widget系统包括组件架构、生命周期、渲染机制和最佳实践。
## 什么是桌面组件?
**桌面组件Widget** 是显示在桌面上的可视化模块,提供信息展示和快捷操作功能。
### 组件特点
- 🎨 **固定在桌面** - 显示在桌面图层,不会被普通窗口遮挡
- 🔄 **实时更新** - 定时刷新数据,保持信息最新
- ⚙️ **可配置** - 用户可以自定义组件行为和外观
- 🖱️ **可交互** - 支持点击、拖拽等用户操作
- 📐 **可布局** - 用户可以自由调整位置和大小
### 典型组件示例
| 组件类型 | 功能 | 更新频率 |
|---------|------|---------|
| **时钟组件** | 显示当前时间和日期 | 1秒 |
| **天气组件** | 显示天气信息 | 5-15分钟 |
| **日历组件** | 显示日程和待办 | 1小时 |
| **系统监控** | CPU、内存使用率 | 2秒 |
| **倒计时** | 重要日期倒计时 | 1秒 |
## 组件架构
### 组件三层结构
```
┌────────────────────────────────────────┐
│ Component (组件模型) │
│ ┌──────────────────────────────────┐ │
│ │ 业务逻辑 │ │
│ │ - 数据获取 │ │
│ │ - 状态管理 │ │
│ │ - 设置持久化 │ │
│ └──────────────────────────────────┘ │
└────────────────┬───────────────────────┘
│ 数据绑定
┌────────────────▼───────────────────────┐
│ ViewModel (视图模型) │
│ ┌──────────────────────────────────┐ │
│ │ 展示逻辑 │ │
│ │ - 属性通知 │ │
│ │ - 命令处理 │ │
│ │ - 数据格式化 │ │
│ └──────────────────────────────────┘ │
└────────────────┬───────────────────────┘
│ UI 绑定
┌────────────────▼───────────────────────┐
│ View (视图) │
│ ┌──────────────────────────────────┐ │
│ │ UI 渲染 │ │
│ │ - Avalonia AXAML │ │
│ │ - 样式和主题 │ │
│ │ - 用户交互 │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
```
### 组件基类层次
```
object
ObservableObject (MVVM Toolkit)
ComponentBase (Plugin SDK)
YourComponent (你的组件)
```
## 创建组件
### 步骤 1: 定义组件类
```csharp
using LanMountainDesktop.PluginSdk.Components;
using LanMountainDesktop.Shared.Contracts.Components;
using System.ComponentModel;
namespace MyPlugin.Components;
/// <summary>
/// 天气组件 - 显示当前天气信息
/// </summary>
[Component(
Id = "com.example.myplugin.weather",
Name = "天气",
Description = "显示当前天气和温度",
Category = "信息",
Icon = "avares://MyPlugin/Assets/weather-icon.png",
DefaultWidth = 200,
DefaultHeight = 150
)]
public class WeatherComponent : ComponentBase
{
// 组件唯一标识
public override string Id => "com.example.myplugin.weather";
// 组件显示名称
public override string Name => "天气";
// === 数据属性 ===
private string _location = "北京";
private double _temperature = 0;
private string _condition = "晴";
private string _icon = "☀️";
/// <summary>
/// 位置
/// </summary>
public string Location
{
get => _location;
set => SetProperty(ref _location, value);
}
/// <summary>
/// 温度(摄氏度)
/// </summary>
public double Temperature
{
get => _temperature;
set => SetProperty(ref _temperature, value);
}
/// <summary>
/// 天气状况
/// </summary>
public string Condition
{
get => _condition;
set => SetProperty(ref _condition, value);
}
/// <summary>
/// 天气图标
/// </summary>
public string Icon
{
get => _icon;
set => SetProperty(ref _icon, value);
}
// === 配置属性 ===
private bool _useFahrenheit = false;
/// <summary>
/// 是否使用华氏度
/// </summary>
public bool UseFahrenheit
{
get => _useFahrenheit;
set
{
if (SetProperty(ref _useFahrenheit, value))
{
// 保存到设置
Settings.SetValue("UseFahrenheit", value);
// 触发更新
OnPropertyChanged(nameof(DisplayTemperature));
}
}
}
/// <summary>
/// 显示温度(根据单位)
/// </summary>
public string DisplayTemperature
{
get
{
if (UseFahrenheit)
{
var fahrenheit = Temperature * 9 / 5 + 32;
return $"{fahrenheit:F1}°F";
}
return $"{Temperature:F1}°C";
}
}
// === 生命周期方法 ===
/// <summary>
/// 组件初始化
/// </summary>
public override async Task InitializeAsync()
{
// 从设置加载配置
Location = Settings.GetValue("Location", "北京");
UseFahrenheit = Settings.GetValue("UseFahrenheit", false);
// 首次加载数据
await FetchWeatherDataAsync();
Logger.LogInformation($"WeatherComponent initialized for {Location}");
}
/// <summary>
/// 组件定时更新
/// </summary>
public override async Task UpdateAsync()
{
// 每 10 分钟更新一次天气数据
var lastUpdate = Settings.GetValue<DateTime>("LastUpdate", DateTime.MinValue);
if (DateTime.Now - lastUpdate > TimeSpan.FromMinutes(10))
{
await FetchWeatherDataAsync();
}
}
/// <summary>
/// 组件销毁
/// </summary>
public override void Dispose()
{
// 清理资源
base.Dispose();
}
// === 业务逻辑 ===
private HttpClient? _httpClient;
private async Task FetchWeatherDataAsync()
{
try
{
_httpClient ??= new HttpClient();
// 调用天气 API
var url = $"https://api.weather.com/data?city={Location}";
var response = await _httpClient.GetStringAsync(url);
// 解析数据
var weatherData = ParseWeatherData(response);
// 更新属性
Temperature = weatherData.Temperature;
Condition = weatherData.Condition;
Icon = GetWeatherIcon(weatherData.Condition);
// 记录更新时间
Settings.SetValue("LastUpdate", DateTime.Now);
Logger.LogInformation($"Weather data updated for {Location}");
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to fetch weather data");
Condition = "加载失败";
}
}
private WeatherData ParseWeatherData(string json)
{
// 解析 JSON 数据
// 实际项目中使用 System.Text.Json 或 Newtonsoft.Json
return new WeatherData
{
Temperature = 25.5,
Condition = "晴"
};
}
private string GetWeatherIcon(string condition)
{
return condition switch
{
"晴" => "☀️",
"多云" => "⛅",
"阴" => "☁️",
"雨" => "🌧️",
"雪" => "❄️",
_ => "🌤️"
};
}
private class WeatherData
{
public double Temperature { get; set; }
public string Condition { get; set; } = "";
}
}
```
### 步骤 2: 创建视图
创建 `Views/WeatherComponentView.axaml`
```xml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyPlugin.ViewModels"
x:Class="MyPlugin.Views.WeatherComponentView"
x:DataType="vm:WeatherComponentViewModel">
<!-- 组件容器 -->
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="16"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1"
BoxShadow="0 2 8 0 #20000000">
<Grid RowDefinitions="Auto,*,Auto">
<!-- 标题栏 -->
<StackPanel Grid.Row="0"
Orientation="Horizontal"
Spacing="8"
Margin="0,0,0,12">
<TextBlock Text="📍" FontSize="16" />
<TextBlock Text="{Binding Component.Location}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
<!-- 主要内容 -->
<StackPanel Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="8">
<!-- 天气图标 -->
<TextBlock Text="{Binding Component.Icon}"
FontSize="48"
HorizontalAlignment="Center" />
<!-- 温度 -->
<TextBlock Text="{Binding Component.DisplayTemperature}"
FontSize="32"
FontWeight="Bold"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<!-- 天气状况 -->
<TextBlock Text="{Binding Component.Condition}"
FontSize="16"
HorizontalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<!-- 底部操作 -->
<StackPanel Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="8"
Margin="0,12,0,0">
<!-- 刷新按钮 -->
<Button Command="{Binding RefreshCommand}"
Padding="8,4"
ToolTip.Tip="刷新">
<TextBlock Text="🔄" FontSize="14" />
</Button>
<!-- 设置按钮 -->
<Button Command="{Binding SettingsCommand}"
Padding="8,4"
ToolTip.Tip="设置">
<TextBlock Text="⚙️" FontSize="14" />
</Button>
</StackPanel>
</Grid>
</Border>
</UserControl>
```
代码后台 `WeatherComponentView.axaml.cs`
```csharp
using Avalonia.Controls;
namespace MyPlugin.Views;
public partial class WeatherComponentView : UserControl
{
public WeatherComponentView()
{
InitializeComponent();
}
}
```
### 步骤 3: 创建视图模型
创建 `ViewModels/WeatherComponentViewModel.cs`
```csharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MyPlugin.Components;
namespace MyPlugin.ViewModels;
/// <summary>
/// 天气组件视图模型
/// </summary>
public partial class WeatherComponentViewModel : ObservableObject
{
[ObservableProperty]
private WeatherComponent _component;
public WeatherComponentViewModel(WeatherComponent component)
{
_component = component;
}
/// <summary>
/// 刷新命令
/// </summary>
[RelayCommand]
private async Task RefreshAsync()
{
// 强制刷新天气数据
await Component.UpdateAsync();
}
/// <summary>
/// 设置命令
/// </summary>
[RelayCommand]
private void Settings()
{
// 打开组件设置对话框
// 实际实现需要调用宿主的对话框服务
Component.Logger.LogInformation("Settings clicked");
}
}
```
### 步骤 4: 注册组件
在插件入口注册组件:
```csharp
public class Plugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
var componentRegistry = context.Services
.GetService<IComponentRegistry>();
if (componentRegistry != null)
{
// 注册天气组件
componentRegistry.RegisterComponent<WeatherComponent>(
componentFactory: () => new WeatherComponent(),
viewFactory: (component) => new WeatherComponentView
{
DataContext = new WeatherComponentViewModel(
(WeatherComponent)component
)
}
);
context.Logger.LogInformation("WeatherComponent registered");
}
}
}
```
## ComponentBase API
### 核心属性
```csharp
public abstract class ComponentBase : ObservableObject, IComponent
{
// === 标识属性 ===
/// <summary>
/// 组件唯一标识符
/// </summary>
public abstract string Id { get; }
/// <summary>
/// 组件显示名称
/// </summary>
public abstract string Name { get; }
// === 服务访问 ===
/// <summary>
/// 日志记录器
/// </summary>
protected ILogger Logger { get; }
/// <summary>
/// 设置服务
/// </summary>
protected IComponentSettings Settings { get; }
/// <summary>
/// 服务提供者
/// </summary>
protected IServiceProvider Services { get; }
// === 生命周期方法 ===
/// <summary>
/// 组件初始化(创建时调用一次)
/// </summary>
public virtual Task InitializeAsync() => Task.CompletedTask;
/// <summary>
/// 组件更新定时调用默认1秒
/// </summary>
public virtual Task UpdateAsync() => Task.CompletedTask;
/// <summary>
/// 组件销毁(清理资源)
/// </summary>
public virtual void Dispose() { }
}
```
### 辅助方法
```csharp
/// <summary>
/// 设置属性值并触发通知
/// </summary>
protected bool SetProperty<T>(
ref T field,
T value,
[CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
/// <summary>
/// 触发属性变更通知
/// </summary>
protected void OnPropertyChanged(
[CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName)
);
}
```
## 组件生命周期
### 完整生命周期
```
1. 用户添加组件
2. ComponentRegistry.CreateInstance()
├─ 调用 componentFactory()
├─ 创建组件实例
└─ 注入依赖Logger, Settings, Services
3. 调用 InitializeAsync()
├─ 加载设置
├─ 初始化数据
└─ 订阅事件
4. ComponentRegistry.CreateView()
├─ 调用 viewFactory()
├─ 创建视图
└─ 设置 DataContext
5. 添加到桌面
├─ 包装到 DesktopWidgetWindow
├─ 设置位置和大小
└─ 显示窗口
6. 定时更新循环
├─ 每 1 秒(可配置)
├─ 调用 UpdateAsync()
└─ UI 自动刷新(数据绑定)
7. 用户移除组件 / 应用关闭
8. 调用 Dispose()
├─ 取消订阅
├─ 保存状态
└─ 释放资源
9. 从桌面移除
└─ 关闭窗口
```
### 更新频率控制
```csharp
public class MyComponent : ComponentBase
{
private DateTime _lastUpdate;
private readonly TimeSpan _updateInterval = TimeSpan.FromMinutes(5);
public override async Task UpdateAsync()
{
// 控制更新频率
if (DateTime.Now - _lastUpdate < _updateInterval)
return;
await FetchDataAsync();
_lastUpdate = DateTime.Now;
}
}
```
## 组件设置
### 使用设置服务
```csharp
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
// 读取设置(带默认值)
var city = Settings.GetValue("City", "北京");
var refreshRate = Settings.GetValue("RefreshRate", 10);
var enabled = Settings.GetValue("Enabled", true);
// 读取复杂对象
var config = Settings.GetValue<MyConfig>("Config", new MyConfig());
return Task.CompletedTask;
}
public void SaveCity(string city)
{
// 保存设置
Settings.SetValue("City", city);
}
}
```
### 监听设置变更
```csharp
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
// 监听设置变更
Settings.SettingChanged += OnSettingChanged;
return Task.CompletedTask;
}
private void OnSettingChanged(object? sender, SettingChangedEventArgs e)
{
if (e.Key == "City")
{
var newCity = e.NewValue as string;
// 响应城市变更
_ = FetchWeatherForCity(newCity);
}
}
public override void Dispose()
{
// 取消订阅
Settings.SettingChanged -= OnSettingChanged;
base.Dispose();
}
}
```
## 最佳实践
### ✅ 性能优化
```csharp
// ✅ 好:使用缓存
private string? _cachedData;
private DateTime _cacheTime;
public async Task<string> GetDataAsync()
{
if (_cachedData != null &&
DateTime.Now - _cacheTime < TimeSpan.FromMinutes(5))
{
return _cachedData;
}
_cachedData = await FetchDataAsync();
_cacheTime = DateTime.Now;
return _cachedData;
}
// ❌ 差:每次都重新获取
public async Task<string> GetDataAsync()
{
return await FetchDataAsync(); // 浪费资源
}
```
### ✅ 异步编程
```csharp
// ✅ 好:使用 async/await
public override async Task UpdateAsync()
{
await FetchDataAsync();
}
// ❌ 差:阻塞线程
public override Task UpdateAsync()
{
FetchDataAsync().Wait(); // 阻塞!
return Task.CompletedTask;
}
```
### ✅ 错误处理
```csharp
// ✅ 好:捕获并记录异常
public override async Task UpdateAsync()
{
try
{
await FetchDataAsync();
}
catch (HttpRequestException ex)
{
Logger.LogError(ex, "Network error");
DisplayError("网络错误");
}
catch (Exception ex)
{
Logger.LogError(ex, "Unexpected error");
DisplayError("未知错误");
}
}
// ❌ 差:忽略异常
public override async Task UpdateAsync()
{
await FetchDataAsync(); // 异常会传播到宿主
}
```
### ✅ 资源管理
```csharp
// ✅ 好:正确释放资源
public class MyComponent : ComponentBase
{
private HttpClient? _httpClient;
private CancellationTokenSource? _cts;
public override void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
_httpClient?.Dispose();
base.Dispose();
}
}
// ❌ 差:不释放资源
public class MyComponent : ComponentBase
{
private HttpClient _httpClient = new(); // 内存泄漏
}
```
## 下一步
- [设置系统](03-设置系统.md) - 管理组件配置
- [主题与外观](04-主题外观.md) - 适配主题
- [ComponentBase API](../03-API参考/03-组件API.md) - API 详细文档
- [天气组件案例](../04-实战案例/01-天气组件.md) - 完整实战

View File

@@ -0,0 +1,858 @@
# 设置系统
本文档介绍阑山桌面的设置系统,包括配置管理、持久化、设置页面和最佳实践。
## 设置系统概览
阑山桌面提供了统一的设置系统,用于管理应用、插件和组件的配置数据。
### 核心特性
- 💾 **自动持久化** - 设置自动保存到本地
- 🔔 **变更通知** - 监听设置变更事件
- 📁 **分域管理** - 按命名空间组织设置
- 🔒 **类型安全** - 泛型 API 保证类型安全
- 🎨 **UI 集成** - 轻松创建设置页面
### 设置存储位置
```
%LOCALAPPDATA%\LanMountainDesktop\
└── settings\
├── app.json # 应用设置
├── appearance.json # 外观设置
├── plugins\
│ ├── com.example.plugin1.json
│ └── com.example.plugin2.json
└── components\
└── com.example.plugin1.component1.json
```
## 使用设置服务
### 在插件中使用
```csharp
public class MyPlugin : IPlugin
{
private IPluginContext? _context;
public async Task InitializeAsync(IPluginContext context)
{
_context = context;
// 通过 context 访问设置
var settings = context.Settings;
// 读取设置
var apiKey = settings.GetValue("ApiKey", "");
var refreshRate = settings.GetValue("RefreshRate", 60);
var enableNotifications = settings.GetValue("EnableNotifications", true);
// 保存设置
settings.SetValue("LastStartTime", DateTime.Now);
}
}
```
### 在组件中使用
```csharp
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
// 组件有自己的设置域
// 自动命名空间:{PluginId}.{ComponentId}
// 读取设置
var location = Settings.GetValue("Location", "北京");
var useFahrenheit = Settings.GetValue("UseFahrenheit", false);
// 读取复杂对象
var config = Settings.GetValue<ComponentConfig>("Config", new ComponentConfig());
return Task.CompletedTask;
}
public void UpdateLocation(string location)
{
Location = location;
// 保存设置
Settings.SetValue("Location", location);
}
}
```
## 设置 API
### ISettingsService 接口
```csharp
public interface ISettingsService
{
/// <summary>
/// 获取设置值
/// </summary>
T GetValue<T>(string key, T defaultValue);
/// <summary>
/// 设置值
/// </summary>
void SetValue<T>(string key, T value);
/// <summary>
/// 删除设置
/// </summary>
void Remove(string key);
/// <summary>
/// 检查设置是否存在
/// </summary>
bool Contains(string key);
/// <summary>
/// 获取所有键
/// </summary>
IEnumerable<string> GetAllKeys();
/// <summary>
/// 清空所有设置
/// </summary>
void Clear();
/// <summary>
/// 设置变更事件
/// </summary>
event EventHandler<SettingChangedEventArgs>? SettingChanged;
}
```
### 基本用法
```csharp
// 读取设置
var value = settings.GetValue<string>("Key", "DefaultValue");
// 保存设置
settings.SetValue("Key", "NewValue");
// 删除设置
settings.Remove("Key");
// 检查是否存在
if (settings.Contains("Key"))
{
// ...
}
// 获取所有键
var keys = settings.GetAllKeys();
// 清空所有设置
settings.Clear();
```
## 支持的数据类型
### 基本类型
```csharp
// 字符串
settings.SetValue("Name", "张三");
var name = settings.GetValue("Name", "");
// 数字
settings.SetValue("Age", 25);
var age = settings.GetValue("Age", 0);
settings.SetValue("Price", 99.99);
var price = settings.GetValue("Price", 0.0);
// 布尔值
settings.SetValue("Enabled", true);
var enabled = settings.GetValue("Enabled", false);
// 日期时间
settings.SetValue("LastUpdate", DateTime.Now);
var lastUpdate = settings.GetValue("LastUpdate", DateTime.MinValue);
// 枚举
settings.SetValue("Theme", AppTheme.Dark);
var theme = settings.GetValue("Theme", AppTheme.Light);
```
### 复杂对象
```csharp
// 定义配置类
public class WeatherConfig
{
public string City { get; set; } = "北京";
public string Unit { get; set; } = "Celsius";
public int RefreshInterval { get; set; } = 10;
public List<string> FavoriteCities { get; set; } = new();
}
// 保存对象
var config = new WeatherConfig
{
City = "上海",
Unit = "Celsius",
RefreshInterval = 15,
FavoriteCities = new List<string> { "北京", "上海", "广州" }
};
settings.SetValue("WeatherConfig", config);
// 读取对象
var savedConfig = settings.GetValue<WeatherConfig>(
"WeatherConfig",
new WeatherConfig()
);
```
### 集合类型
```csharp
// 列表
var favoriteColors = new List<string> { "红色", "蓝色", "绿色" };
settings.SetValue("FavoriteColors", favoriteColors);
var colors = settings.GetValue<List<string>>("FavoriteColors", new List<string>());
// 字典
var preferences = new Dictionary<string, string>
{
["Language"] = "zh-CN",
["Timezone"] = "Asia/Shanghai"
};
settings.SetValue("Preferences", preferences);
var prefs = settings.GetValue<Dictionary<string, string>>(
"Preferences",
new Dictionary<string, string>()
);
```
## 监听设置变更
### 订阅变更事件
```csharp
public class MyPlugin : IPlugin
{
private ISettingsService? _settings;
public async Task InitializeAsync(IPluginContext context)
{
_settings = context.Settings;
// 订阅设置变更事件
_settings.SettingChanged += OnSettingChanged;
}
private void OnSettingChanged(object? sender, SettingChangedEventArgs e)
{
// e.Key - 变更的设置键
// e.OldValue - 旧值
// e.NewValue - 新值
if (e.Key == "ApiKey")
{
var newApiKey = e.NewValue as string;
_logger.LogInformation($"API Key changed to: {newApiKey}");
// 重新初始化服务
ReinitializeService(newApiKey);
}
}
public async Task ShutdownAsync()
{
// 取消订阅(防止内存泄漏)
if (_settings != null)
{
_settings.SettingChanged -= OnSettingChanged;
}
}
}
```
### 在组件中监听
```csharp
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
// 监听设置变更
Settings.SettingChanged += OnSettingChanged;
return Task.CompletedTask;
}
private void OnSettingChanged(object? sender, SettingChangedEventArgs e)
{
switch (e.Key)
{
case "Location":
Location = e.NewValue as string ?? "北京";
_ = RefreshWeatherAsync();
break;
case "UseFahrenheit":
UseFahrenheit = (bool)(e.NewValue ?? false);
OnPropertyChanged(nameof(DisplayTemperature));
break;
}
}
public override void Dispose()
{
// 取消订阅
Settings.SettingChanged -= OnSettingChanged;
base.Dispose();
}
}
```
## 创建设置页面
### 步骤 1: 创建设置页视图
创建 `Settings/MyPluginSettingsPage.axaml`
```xml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:MyPlugin.ViewModels"
x:Class="MyPlugin.Settings.MyPluginSettingsPage"
x:DataType="vm:MyPluginSettingsViewModel">
<ScrollViewer>
<StackPanel Spacing="16" Margin="24">
<!-- 页面标题 -->
<TextBlock Text="天气插件设置"
FontSize="24"
FontWeight="Bold"
Margin="0,0,0,8" />
<!-- 基本设置 -->
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="16"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1">
<StackPanel Spacing="12">
<!-- 分组标题 -->
<TextBlock Text="基本设置"
FontSize="16"
FontWeight="SemiBold" />
<!-- 城市设置 -->
<StackPanel Spacing="8">
<TextBlock Text="城市:" />
<TextBox Text="{Binding Location, Mode=TwoWay}"
Watermark="输入城市名称"
Width="300"
HorizontalAlignment="Left" />
</StackPanel>
<!-- API Key -->
<StackPanel Spacing="8">
<TextBlock Text="API Key:" />
<TextBox Text="{Binding ApiKey, Mode=TwoWay}"
Watermark="输入 API Key"
PasswordChar="●"
Width="300"
HorizontalAlignment="Left" />
<TextBlock Text="从 https://api.weather.com 获取"
FontSize="12"
Foreground="{DynamicResource TextFillColorTertiaryBrush}" />
</StackPanel>
</StackPanel>
</Border>
<!-- 显示设置 -->
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="16"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1">
<StackPanel Spacing="12">
<TextBlock Text="显示设置"
FontSize="16"
FontWeight="SemiBold" />
<!-- 温度单位 -->
<StackPanel Spacing="8">
<TextBlock Text="温度单位:" />
<ComboBox SelectedIndex="{Binding TemperatureUnitIndex, Mode=TwoWay}"
Width="200"
HorizontalAlignment="Left">
<ComboBoxItem Content="摄氏度 (°C)" />
<ComboBoxItem Content="华氏度 (°F)" />
</ComboBox>
</StackPanel>
<!-- 刷新间隔 -->
<StackPanel Spacing="8">
<TextBlock Text="刷新间隔 (分钟):" />
<NumericUpDown Value="{Binding RefreshInterval, Mode=TwoWay}"
Minimum="5"
Maximum="60"
Increment="5"
Width="200"
HorizontalAlignment="Left" />
</StackPanel>
<!-- 开关选项 -->
<CheckBox IsChecked="{Binding ShowIcon, Mode=TwoWay}"
Content="显示天气图标" />
<CheckBox IsChecked="{Binding EnableNotifications, Mode=TwoWay}"
Content="启用天气预警通知" />
</StackPanel>
</Border>
<!-- 高级设置 -->
<Border Background="{DynamicResource CardBackgroundBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusComponent}"
Padding="16"
BorderBrush="{DynamicResource CardBorderBrush}"
BorderThickness="1">
<StackPanel Spacing="12">
<TextBlock Text="高级设置"
FontSize="16"
FontWeight="SemiBold" />
<!-- 收藏城市 -->
<StackPanel Spacing="8">
<TextBlock Text="收藏城市:" />
<ListBox ItemsSource="{Binding FavoriteCities}"
Height="150"
Width="300"
HorizontalAlignment="Left" />
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBox x:Name="NewCityTextBox"
Watermark="添加城市"
Width="200" />
<Button Content="添加"
Command="{Binding AddCityCommand}"
CommandParameter="{Binding #NewCityTextBox.Text}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Border>
<!-- 操作按钮 -->
<StackPanel Orientation="Horizontal" Spacing="12">
<Button Content="保存"
Command="{Binding SaveCommand}"
IsDefault="True" />
<Button Content="重置"
Command="{Binding ResetCommand}" />
<Button Content="测试连接"
Command="{Binding TestConnectionCommand}" />
</StackPanel>
<!-- 状态提示 -->
<TextBlock Text="{Binding StatusMessage}"
Foreground="{Binding StatusColor}"
IsVisible="{Binding !!StatusMessage}" />
</StackPanel>
</ScrollViewer>
</UserControl>
```
### 步骤 2: 创建设置页视图模型
创建 `ViewModels/MyPluginSettingsViewModel.cs`
```csharp
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace MyPlugin.ViewModels;
public partial class MyPluginSettingsViewModel : ObservableObject
{
private readonly ISettingsService _settings;
private readonly ILogger _logger;
public MyPluginSettingsViewModel(
ISettingsService settings,
ILogger logger)
{
_settings = settings;
_logger = logger;
// 加载设置
LoadSettings();
}
// === 属性 ===
[ObservableProperty]
private string _location = "北京";
[ObservableProperty]
private string _apiKey = "";
[ObservableProperty]
private int _temperatureUnitIndex = 0;
[ObservableProperty]
private int _refreshInterval = 10;
[ObservableProperty]
private bool _showIcon = true;
[ObservableProperty]
private bool _enableNotifications = true;
[ObservableProperty]
private ObservableCollection<string> _favoriteCities = new();
[ObservableProperty]
private string? _statusMessage;
[ObservableProperty]
private string _statusColor = "Green";
// === 命令 ===
/// <summary>
/// 保存命令
/// </summary>
[RelayCommand]
private void Save()
{
try
{
// 保存所有设置
_settings.SetValue("Location", Location);
_settings.SetValue("ApiKey", ApiKey);
_settings.SetValue("UseFahrenheit", TemperatureUnitIndex == 1);
_settings.SetValue("RefreshInterval", RefreshInterval);
_settings.SetValue("ShowIcon", ShowIcon);
_settings.SetValue("EnableNotifications", EnableNotifications);
_settings.SetValue("FavoriteCities", FavoriteCities.ToList());
ShowStatus("设置已保存", "Green");
_logger.LogInformation("Settings saved successfully");
}
catch (Exception ex)
{
ShowStatus($"保存失败: {ex.Message}", "Red");
_logger.LogError(ex, "Failed to save settings");
}
}
/// <summary>
/// 重置命令
/// </summary>
[RelayCommand]
private void Reset()
{
// 重新加载设置
LoadSettings();
ShowStatus("已重置到上次保存的值", "Orange");
}
/// <summary>
/// 添加城市命令
/// </summary>
[RelayCommand]
private void AddCity(string? city)
{
if (string.IsNullOrWhiteSpace(city))
return;
if (!FavoriteCities.Contains(city))
{
FavoriteCities.Add(city);
ShowStatus($"已添加城市: {city}", "Green");
}
else
{
ShowStatus("城市已存在", "Orange");
}
}
/// <summary>
/// 测试连接命令
/// </summary>
[RelayCommand]
private async Task TestConnectionAsync()
{
ShowStatus("正在测试连接...", "Blue");
try
{
// 测试 API 连接
var result = await TestWeatherApiAsync(ApiKey, Location);
if (result)
{
ShowStatus("连接成功!", "Green");
}
else
{
ShowStatus("连接失败,请检查 API Key 和城市名称", "Red");
}
}
catch (Exception ex)
{
ShowStatus($"测试失败: {ex.Message}", "Red");
_logger.LogError(ex, "Connection test failed");
}
}
// === 辅助方法 ===
private void LoadSettings()
{
Location = _settings.GetValue("Location", "北京");
ApiKey = _settings.GetValue("ApiKey", "");
var useFahrenheit = _settings.GetValue("UseFahrenheit", false);
TemperatureUnitIndex = useFahrenheit ? 1 : 0;
RefreshInterval = _settings.GetValue("RefreshInterval", 10);
ShowIcon = _settings.GetValue("ShowIcon", true);
EnableNotifications = _settings.GetValue("EnableNotifications", true);
var cities = _settings.GetValue<List<string>>("FavoriteCities", new List<string>());
FavoriteCities = new ObservableCollection<string>(cities);
}
private void ShowStatus(string message, string color)
{
StatusMessage = message;
StatusColor = color;
// 3 秒后清除状态
Task.Delay(3000).ContinueWith(_ =>
{
StatusMessage = null;
});
}
private async Task<bool> TestWeatherApiAsync(string apiKey, string location)
{
// 实际实现中测试 API 连接
await Task.Delay(1000);
return !string.IsNullOrEmpty(apiKey);
}
}
```
### 步骤 3: 注册设置页
在插件入口注册:
```csharp
public class MyPlugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
var settingsRegistry = context.Services
.GetService<ISettingsPageRegistry>();
if (settingsRegistry != null)
{
// 注册设置页
settingsRegistry.RegisterPage(
title: "天气插件",
category: "插件",
icon: "avares://MyPlugin/Assets/settings-icon.png",
pageFactory: () =>
{
var viewModel = new MyPluginSettingsViewModel(
context.Settings,
context.Logger
);
return new MyPluginSettingsPage
{
DataContext = viewModel
};
}
);
context.Logger.LogInformation("Settings page registered");
}
}
}
```
## 设置最佳实践
### ✅ 提供默认值
```csharp
// ✅ 好:提供合理的默认值
var timeout = settings.GetValue("Timeout", 30);
var apiUrl = settings.GetValue("ApiUrl", "https://api.example.com");
// ❌ 差:不提供默认值
var timeout = settings.GetValue<int>("Timeout", 0); // 0 可能不合理
```
### ✅ 验证设置值
```csharp
// ✅ 好:验证设置值
public void SetRefreshInterval(int minutes)
{
if (minutes < 1 || minutes > 60)
{
throw new ArgumentOutOfRangeException(
nameof(minutes),
"刷新间隔必须在 1-60 分钟之间"
);
}
RefreshInterval = minutes;
Settings.SetValue("RefreshInterval", minutes);
}
// ❌ 差:不验证
public void SetRefreshInterval(int minutes)
{
Settings.SetValue("RefreshInterval", minutes); // 可能是非法值
}
```
### ✅ 使用类型化配置
```csharp
// ✅ 好:使用强类型配置类
public class PluginConfig
{
public string ApiKey { get; set; } = "";
public string Location { get; set; } = "北京";
public int RefreshInterval { get; set; } = 10;
public bool EnableNotifications { get; set; } = true;
public void Validate()
{
if (string.IsNullOrEmpty(ApiKey))
throw new InvalidOperationException("API Key is required");
if (RefreshInterval < 1 || RefreshInterval > 60)
throw new ArgumentOutOfRangeException(nameof(RefreshInterval));
}
}
// 使用
var config = settings.GetValue<PluginConfig>("Config", new PluginConfig());
config.Validate();
// ❌ 差:分散的设置键
var apiKey = settings.GetValue<string>("ApiKey", "");
var location = settings.GetValue<string>("Location", "");
var interval = settings.GetValue<int>("RefreshInterval", 10);
```
### ✅ 取消事件订阅
```csharp
// ✅ 好:在 Dispose 中取消订阅
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
Settings.SettingChanged += OnSettingChanged;
return Task.CompletedTask;
}
public override void Dispose()
{
Settings.SettingChanged -= OnSettingChanged;
base.Dispose();
}
}
// ❌ 差:忘记取消订阅(内存泄漏)
public class MyComponent : ComponentBase
{
public override Task InitializeAsync()
{
Settings.SettingChanged += OnSettingChanged;
return Task.CompletedTask;
}
// 没有 Dispose导致内存泄漏
}
```
## 设置迁移
### 版本升级时的设置迁移
```csharp
public class MyPlugin : IPlugin
{
public async Task InitializeAsync(IPluginContext context)
{
var settings = context.Settings;
// 检查设置版本
var settingsVersion = settings.GetValue("SettingsVersion", 1);
if (settingsVersion < 2)
{
// 迁移到版本 2
MigrateToV2(settings);
settings.SetValue("SettingsVersion", 2);
}
if (settingsVersion < 3)
{
// 迁移到版本 3
MigrateToV3(settings);
settings.SetValue("SettingsVersion", 3);
}
}
private void MigrateToV2(ISettingsService settings)
{
// 例如:重命名设置键
if (settings.Contains("OldKey"))
{
var value = settings.GetValue<string>("OldKey", "");
settings.SetValue("NewKey", value);
settings.Remove("OldKey");
}
}
private void MigrateToV3(ISettingsService settings)
{
// 例如:更改数据格式
var oldFormat = settings.GetValue<string>("Location", "");
var newFormat = new LocationConfig
{
City = oldFormat,
Country = "中国"
};
settings.SetValue("LocationConfig", newFormat);
settings.Remove("Location");
}
}
```
## 下一步
- [主题与外观](04-主题外观.md) - 适配主题系统
- [插件通信](05-插件通信.md) - 插件间协作
- [设置 API 详解](../03-API参考/04-设置API.md) - API 参考文档
- [创建设置页](../04-实战案例/04-开发设置页.md) - 实战案例