mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
23 KiB
23 KiB
设置系统
本文档介绍阑山桌面的设置系统,包括配置管理、持久化、设置页面和最佳实践。
设置系统概览
阑山桌面提供了统一的设置系统,用于管理应用、插件和组件的配置数据。
核心特性
- 💾 自动持久化 - 设置自动保存到本地
- 🔔 变更通知 - 监听设置变更事件
- 📁 分域管理 - 按命名空间组织设置
- 🔒 类型安全 - 泛型 API 保证类型安全
- 🎨 UI 集成 - 轻松创建设置页面
设置存储位置
%LOCALAPPDATA%\LanMountainDesktop\
└── settings\
├── app.json # 应用设置
├── appearance.json # 外观设置
├── plugins\
│ ├── com.example.plugin1.json
│ └── com.example.plugin2.json
└── components\
└── com.example.plugin1.component1.json
使用设置服务
在插件中使用
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);
}
}
在组件中使用
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 接口
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;
}
基本用法
// 读取设置
var value = settings.GetValue<string>("Key", "DefaultValue");
// 保存设置
settings.SetValue("Key", "NewValue");
// 删除设置
settings.Remove("Key");
// 检查是否存在
if (settings.Contains("Key"))
{
// ...
}
// 获取所有键
var keys = settings.GetAllKeys();
// 清空所有设置
settings.Clear();
支持的数据类型
基本类型
// 字符串
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);
复杂对象
// 定义配置类
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()
);
集合类型
// 列表
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>()
);
监听设置变更
订阅变更事件
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;
}
}
}
在组件中监听
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:
<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:
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: 注册设置页
在插件入口注册:
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");
}
}
}
设置最佳实践
✅ 提供默认值
// ✅ 好:提供合理的默认值
var timeout = settings.GetValue("Timeout", 30);
var apiUrl = settings.GetValue("ApiUrl", "https://api.example.com");
// ❌ 差:不提供默认值
var timeout = settings.GetValue<int>("Timeout", 0); // 0 可能不合理
✅ 验证设置值
// ✅ 好:验证设置值
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); // 可能是非法值
}
✅ 使用类型化配置
// ✅ 好:使用强类型配置类
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);
✅ 取消事件订阅
// ✅ 好:在 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,导致内存泄漏
}
设置迁移
版本升级时的设置迁移
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");
}
}