06 - 主题系统示例
演示如何使用 Sharp.Wpf 的主题系统实现浅色/深色主题切换,包括自定义颜色、跟随系统主题等功能。
🎯 学习目标
- 使用
IThemeService管理应用主题 - 实现浅色/深色主题切换
- 自定义主题颜色
- 跟随系统主题设置
- 动态主题切换(无需重启)
📦 使用的模块
- Sharp.Wpf - IThemeService, ThemeService
🚀 快速运行
cd examples/06-Theming
dotnet run
📝 核心代码讲解
1. App.xaml.cs - 注册主题服务
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 - 主题切换逻辑
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 - 使用主题颜色
<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. 自定义主题颜色
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:
<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:
<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。
📚 延伸阅读
打造美观现代的用户界面! 🎨