Files
sharp-basic/examples/06-Theming/README.md
T

313 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
---
**打造美观现代的用户界面!** 🎨