Files

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。

📚 延伸阅读


打造美观现代的用户界面! 🎨