313 lines
11 KiB
Markdown
313 lines
11 KiB
Markdown
# 06 - 主题系统示例
|
||
|
||
演示如何使用 Sharp.Wpf 的主题系统实现浅色/深色主题切换,包括自定义颜色、跟随系统主题等功能。
|
||
|
||
## 🎯 学习目标
|
||
|
||
- 使用 `IThemeService` 管理应用主题
|
||
- 实现浅色/深色主题切换
|
||
- 自定义主题颜色
|
||
- 跟随系统主题设置
|
||
- 动态主题切换(无需重启)
|
||
|
||
## 📦 使用的模块
|
||
|
||
- **Sharp.Wpf** - IThemeService, ThemeService
|
||
|
||
## 🚀 快速运行
|
||
|
||
```bash
|
||
cd examples/06-Theming
|
||
dotnet run
|
||
```
|
||
|
||
## 📝 核心代码讲解
|
||
|
||
### 1. App.xaml.cs - 注册主题服务
|
||
|
||
```csharp
|
||
using Wpf.Theming;
|
||
|
||
protected override void OnStartup(StartupEventArgs e)
|
||
{
|
||
var services = new ServiceCollection();
|
||
|
||
services.AddSharpWpf(); // 自动注册 IThemeService
|
||
|
||
services.AddViewModel<MainViewModel>();
|
||
|
||
var provider = services.BuildServiceProvider();
|
||
|
||
// 应用初始主题
|
||
var themeService = provider.GetRequiredService<IThemeService>();
|
||
themeService.ApplyTheme(Theme.Light); // 或 Theme.System
|
||
|
||
var mainWindow = provider.GetRequiredService<MainWindow>();
|
||
mainWindow.Show();
|
||
}
|
||
```
|
||
|
||
### 2. MainViewModel.cs - 主题切换逻辑
|
||
|
||
```csharp
|
||
using Wpf.Theming;
|
||
|
||
public class MainViewModel : ViewModelBase
|
||
{
|
||
private readonly IThemeService _themeService;
|
||
private Theme _currentTheme;
|
||
|
||
public Theme CurrentTheme
|
||
{
|
||
get => _currentTheme;
|
||
set
|
||
{
|
||
SetProperty(ref _currentTheme, value);
|
||
_themeService.ApplyTheme(value);
|
||
}
|
||
}
|
||
|
||
public ICommand SwitchToLightCommand { get; }
|
||
public ICommand SwitchToDarkCommand { get; }
|
||
public ICommand SwitchToSystemCommand { get; }
|
||
|
||
public MainViewModel(IThemeService themeService)
|
||
{
|
||
_themeService = themeService;
|
||
_currentTheme = themeService.CurrentTheme;
|
||
|
||
// 监听主题变化
|
||
_themeService.ThemeChanged += (s, theme) =>
|
||
{
|
||
OnPropertyChanged(nameof(CurrentTheme));
|
||
};
|
||
|
||
SwitchToLightCommand = new RelayCommand(() =>
|
||
{
|
||
CurrentTheme = Theme.Light;
|
||
});
|
||
|
||
SwitchToDarkCommand = new RelayCommand(() =>
|
||
{
|
||
CurrentTheme = Theme.Dark;
|
||
});
|
||
|
||
SwitchToSystemCommand = new RelayCommand(() =>
|
||
{
|
||
CurrentTheme = Theme.System;
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. MainWindow.xaml - 使用主题颜色
|
||
|
||
```xml
|
||
<Window x:Class="ThemingExample.MainWindow"
|
||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
Title="主题系统示例" Height="500" Width="600"
|
||
Background="{DynamicResource BackgroundColor}"> <!-- 使用主题颜色 -->
|
||
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- 主题切换栏 -->
|
||
<StackPanel Grid.Row="0" Orientation="Horizontal"
|
||
Background="{DynamicResource PrimaryColor}" Padding="15">
|
||
<TextBlock Text="选择主题:"
|
||
Foreground="White" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||
<Button Content="☀️ 浅色" Command="{Binding SwitchToLightCommand}"
|
||
Margin="0,0,10,0" Padding="15,5"/>
|
||
<Button Content="🌙 深色" Command="{Binding SwitchToDarkCommand}"
|
||
Margin="0,0,10,0" Padding="15,5"/>
|
||
<Button Content="🖥️ 跟随系统" Command="{Binding SwitchToSystemCommand}"
|
||
Padding="15,5"/>
|
||
</StackPanel>
|
||
|
||
<!-- 内容区域 -->
|
||
<ScrollViewer Grid.Row="1" Padding="20">
|
||
<StackPanel>
|
||
<TextBlock Text="主题颜色预览" FontSize="24" FontWeight="Bold"
|
||
Foreground="{DynamicResource ForegroundColor}" Margin="0,0,0,20"/>
|
||
|
||
<!-- 基础颜色 -->
|
||
<GroupBox Header="基础颜色" Margin="0,0,0,15">
|
||
<StackPanel>
|
||
<Border Background="{DynamicResource PrimaryColor}"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="PrimaryColor" Foreground="White"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
<Border Background="{DynamicResource BackgroundColor}"
|
||
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="BackgroundColor"
|
||
Foreground="{DynamicResource ForegroundColor}"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
|
||
<!-- 状态颜色 -->
|
||
<GroupBox Header="状态颜色" Margin="0,0,0,15">
|
||
<StackPanel>
|
||
<Border Background="{DynamicResource SuccessColor}"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="✓ Success" Foreground="White"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
<Border Background="{DynamicResource WarningColor}"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="⚠ Warning" Foreground="White"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
<Border Background="{DynamicResource ErrorColor}"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="✗ Error" Foreground="White"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
<Border Background="{DynamicResource InfoColor}"
|
||
Height="40" Margin="0,5">
|
||
<TextBlock Text="ℹ Info" Foreground="White"
|
||
VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||
</Border>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
|
||
<!-- 控件预览 -->
|
||
<GroupBox Header="控件预览" Margin="0,0,0,15">
|
||
<StackPanel>
|
||
<!-- 按钮 -->
|
||
<Button Content="普通按钮" Margin="0,5" Padding="10,5"
|
||
Background="{DynamicResource ButtonBackgroundColor}"
|
||
Foreground="{DynamicResource ForegroundColor}"/>
|
||
|
||
<!-- 输入框 -->
|
||
<TextBox Text="输入框示例" Margin="0,5" Padding="5"
|
||
Background="{DynamicResource InputBackgroundColor}"
|
||
BorderBrush="{DynamicResource InputBorderColor}"/>
|
||
|
||
<!-- 复选框 -->
|
||
<CheckBox Content="复选框示例" Margin="0,5"
|
||
Foreground="{DynamicResource ForegroundColor}"/>
|
||
|
||
<!-- 单选按钮 -->
|
||
<RadioButton Content="单选按钮示例" Margin="0,5"
|
||
Foreground="{DynamicResource ForegroundColor}"/>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
|
||
<!-- 文本示例 -->
|
||
<GroupBox Header="文本示例">
|
||
<StackPanel>
|
||
<TextBlock Text="这是标题文本" FontSize="20" FontWeight="Bold"
|
||
Foreground="{DynamicResource ForegroundColor}" Margin="0,5"/>
|
||
<TextBlock Text="这是正文文本。主题切换后,所有使用 DynamicResource 的颜色都会自动更新,无需重启应用。"
|
||
TextWrapping="Wrap" Foreground="{DynamicResource ForegroundColor}"
|
||
Margin="0,5"/>
|
||
<TextBlock Text="这是次要文本" FontSize="12" FontStyle="Italic"
|
||
Foreground="Gray" Margin="0,5"/>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
</Grid>
|
||
</Window>
|
||
```
|
||
|
||
### 4. 自定义主题颜色
|
||
|
||
```csharp
|
||
public class CustomThemeViewModel : ViewModelBase
|
||
{
|
||
private readonly IThemeService _themeService;
|
||
|
||
public ICommand ChangePrimaryColorCommand { get; }
|
||
|
||
public CustomThemeViewModel(IThemeService themeService)
|
||
{
|
||
_themeService = themeService;
|
||
|
||
ChangePrimaryColorCommand = new RelayCommand(() =>
|
||
{
|
||
// 打开颜色选择器(可以使用第三方库或自定义)
|
||
var color = Colors.DeepSkyBlue; // 示例:选择的颜色
|
||
|
||
// 更新主题颜色
|
||
_themeService.SetColor("PrimaryColor", color);
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5. 主题资源字典(可选 - 高级用法)
|
||
|
||
可以创建独立的资源字典文件:
|
||
|
||
**Themes/LightTheme.xaml**:
|
||
```xml
|
||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||
<SolidColorBrush x:Key="PrimaryColor" Color="#0078D7"/>
|
||
<SolidColorBrush x:Key="BackgroundColor" Color="White"/>
|
||
<SolidColorBrush x:Key="ForegroundColor" Color="Black"/>
|
||
<!-- ... 其他颜色 -->
|
||
</ResourceDictionary>
|
||
```
|
||
|
||
**Themes/DarkTheme.xaml**:
|
||
```xml
|
||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||
<SolidColorBrush x:Key="PrimaryColor" Color="#0078D7"/>
|
||
<SolidColorBrush x:Key="BackgroundColor" Color="#202020"/>
|
||
<SolidColorBrush x:Key="ForegroundColor" Color="White"/>
|
||
<!-- ... 其他颜色 -->
|
||
</ResourceDictionary>
|
||
```
|
||
|
||
## 🎓 练习建议
|
||
|
||
### 练习1:添加更多预设主题
|
||
添加"高对比度"主题,适合视力不佳的用户。
|
||
|
||
### 练习2:主题动画过渡
|
||
在切换主题时添加颜色渐变动画,而非瞬间切换。
|
||
|
||
### 练习3:保存主题偏好
|
||
将用户选择的主题保存到配置文件,下次启动时自动应用。
|
||
|
||
### 练习4:自定义主题编辑器
|
||
创建一个界面,允许用户自定义每种颜色,并导出/导入主题配置。
|
||
|
||
## 💡 常见问题
|
||
|
||
**Q: StaticResource 和 DynamicResource 的区别?**
|
||
A:
|
||
- `StaticResource`: 编译时绑定,主题切换后不会更新
|
||
- `DynamicResource`: 运行时绑定,主题切换后自动更新(推荐用于主题)
|
||
|
||
**Q: 主题切换后部分控件没变化?**
|
||
A: 确保使用了 `{DynamicResource}` 而非 `{StaticResource}`。
|
||
|
||
**Q: 如何跟随 Windows 系统主题?**
|
||
A: 使用 `Theme.System`,Sharp.Wpf 会自动检测系统主题设置。
|
||
|
||
**Q: 能否为不同的窗口设置不同主题?**
|
||
A: 可以,但不推荐。可以为每个 Window 设置独立的 ResourceDictionary。
|
||
|
||
## 📚 延伸阅读
|
||
|
||
- [WPF 资源](https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/systems/xaml-resources-overview)
|
||
- [WPF 样式和模板](https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/styles-templates-overview)
|
||
- [Windows 主题检测](https://learn.microsoft.com/zh-cn/windows/apps/desktop/modernize/apply-windows-themes)
|
||
- 综合示例:[11-FullStackDemo](../11-FullStackDemo/README.md)
|
||
|
||
---
|
||
|
||
**打造美观现代的用户界面!** 🎨
|