mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 18:04:26 +08:00
790 lines
20 KiB
Markdown
790 lines
20 KiB
Markdown
|
|
# 组件系统详解
|
|||
|
|
|
|||
|
|
本文档详细介绍阑山桌面的桌面组件(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) - 完整实战
|