# 组件系统详解 本文档详细介绍阑山桌面的桌面组件(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; /// /// 天气组件 - 显示当前天气信息 /// [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 = "☀️"; /// /// 位置 /// public string Location { get => _location; set => SetProperty(ref _location, value); } /// /// 温度(摄氏度) /// public double Temperature { get => _temperature; set => SetProperty(ref _temperature, value); } /// /// 天气状况 /// public string Condition { get => _condition; set => SetProperty(ref _condition, value); } /// /// 天气图标 /// public string Icon { get => _icon; set => SetProperty(ref _icon, value); } // === 配置属性 === private bool _useFahrenheit = false; /// /// 是否使用华氏度 /// public bool UseFahrenheit { get => _useFahrenheit; set { if (SetProperty(ref _useFahrenheit, value)) { // 保存到设置 Settings.SetValue("UseFahrenheit", value); // 触发更新 OnPropertyChanged(nameof(DisplayTemperature)); } } } /// /// 显示温度(根据单位) /// public string DisplayTemperature { get { if (UseFahrenheit) { var fahrenheit = Temperature * 9 / 5 + 32; return $"{fahrenheit:F1}°F"; } return $"{Temperature:F1}°C"; } } // === 生命周期方法 === /// /// 组件初始化 /// public override async Task InitializeAsync() { // 从设置加载配置 Location = Settings.GetValue("Location", "北京"); UseFahrenheit = Settings.GetValue("UseFahrenheit", false); // 首次加载数据 await FetchWeatherDataAsync(); Logger.LogInformation($"WeatherComponent initialized for {Location}"); } /// /// 组件定时更新 /// public override async Task UpdateAsync() { // 每 10 分钟更新一次天气数据 var lastUpdate = Settings.GetValue("LastUpdate", DateTime.MinValue); if (DateTime.Now - lastUpdate > TimeSpan.FromMinutes(10)) { await FetchWeatherDataAsync(); } } /// /// 组件销毁 /// 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 ``` 代码后台 `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; /// /// 天气组件视图模型 /// public partial class WeatherComponentViewModel : ObservableObject { [ObservableProperty] private WeatherComponent _component; public WeatherComponentViewModel(WeatherComponent component) { _component = component; } /// /// 刷新命令 /// [RelayCommand] private async Task RefreshAsync() { // 强制刷新天气数据 await Component.UpdateAsync(); } /// /// 设置命令 /// [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(); if (componentRegistry != null) { // 注册天气组件 componentRegistry.RegisterComponent( 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 { // === 标识属性 === /// /// 组件唯一标识符 /// public abstract string Id { get; } /// /// 组件显示名称 /// public abstract string Name { get; } // === 服务访问 === /// /// 日志记录器 /// protected ILogger Logger { get; } /// /// 设置服务 /// protected IComponentSettings Settings { get; } /// /// 服务提供者 /// protected IServiceProvider Services { get; } // === 生命周期方法 === /// /// 组件初始化(创建时调用一次) /// public virtual Task InitializeAsync() => Task.CompletedTask; /// /// 组件更新(定时调用,默认1秒) /// public virtual Task UpdateAsync() => Task.CompletedTask; /// /// 组件销毁(清理资源) /// public virtual void Dispose() { } } ``` ### 辅助方法 ```csharp /// /// 设置属性值并触发通知 /// protected bool SetProperty( ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } /// /// 触发属性变更通知 /// 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("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 GetDataAsync() { if (_cachedData != null && DateTime.Now - _cacheTime < TimeSpan.FromMinutes(5)) { return _cachedData; } _cachedData = await FetchDataAsync(); _cacheTime = DateTime.Now; return _cachedData; } // ❌ 差:每次都重新获取 public async Task 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) - 完整实战