WPF 实现更换主题色

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF 实现更换主题色相关的知识,希望对你有一定的参考价值。

 WPF 实现更换主题色

WPF 使用 WPFDevelopers.Minimal 如何更换主题色

作者:WPFDevelopersOrg

原文链接:    https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal

  • 框架使用大于等于.NET40

  • Visual Studio 2022;

  • 项目使用 MIT 开源许可协议;

  • Nuget Install-Package WPFDevelopers.Minimal 3.2.6-preview

  • 定义一个公共颜色资源文件,所有控件都动态资源引用DynamicResource颜色的Key,然后这样修改一个资源文件的Key  SolidColorBrushColor颜色值就可以实现更换主题色;

  • 此篇主要是定义了多个公共颜色资源文件如下,选择不同的资源文件进行更换App.xaml的资源字典实现更换主题色;

    • Application.Current.Resources.MergedDictionaries.Remove(Blue) 字典寻找Blue,然后移除Application.Current.Resources.MergedDictionaries.Add(Green); 就完成更换主题色;

    • 创建 Light.Blue

    • 创建 Light.Green

    • 创建 Light.Orange

    • 创建 Light.Pink

    • 创建 Light.Purple

    • 创建 Light.Purple

  • 使用 WPFDevelopers.Minimal 如何更换主题色Nuget包大于等于3.2.3如下;

  • 新增主题色如下;

    • 新建资源文件Light.Carmine.xaml如下,自己更换颜色值即可;

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" po:Freeze="True">
   
    <!--Default颜色-->
    <Color x:Key="DefaultBorderBrushColor" po:Freeze="True">#CD7474</Color>
    <SolidColorBrush x:Key="DefaultBorderBrushSolidColorBrush" Color="StaticResource DefaultBorderBrushColor" po:Freeze="True"></SolidColorBrush>
    <Color x:Key="DefaultBackgroundColor" po:Freeze="True">#CFA0A0</Color>
    <SolidColorBrush x:Key="DefaultBackgroundSolidColorBrush" Color="StaticResource DefaultBackgroundColor" po:Freeze="True"></SolidColorBrush>
    <Color x:Key="DefaultBackgroundPressedColor" po:Freeze="True">#B70404</Color>
    <SolidColorBrush x:Key="DefaultBackgroundPressedSolidColorBrush" Color="StaticResource DefaultBackgroundPressedColor" po:Freeze="True"></SolidColorBrush>
    <!--Primary颜色-->
    <Color x:Key="PrimaryNormalColor" po:Freeze="True">#B31B1B</Color>
    <SolidColorBrush x:Key="PrimaryNormalSolidColorBrush" Color="StaticResource PrimaryNormalColor" po:Freeze="True"></SolidColorBrush>
    <SolidColorBrush x:Key="WindowBorderBrushSolidColorBrush" Color="StaticResource PrimaryNormalColor" po:Freeze="True"></SolidColorBrush>

    <Color x:Key="PrimaryMouseOverColor" po:Freeze="True">#BB5F5F</Color>
    <SolidColorBrush x:Key="PrimaryMouseOverSolidColorBrush" Color="StaticResource PrimaryMouseOverColor" po:Freeze="True"></SolidColorBrush>

    <Color x:Key="PrimaryPressedColor" po:Freeze="True">#B70404</Color>
    <SolidColorBrush x:Key="PrimaryPressedSolidColorBrush" Color="StaticResource PrimaryPressedColor" po:Freeze="True"></SolidColorBrush>

</ResourceDictionary>
  • 1)把上面新增的资源添加App.xaml中即可;

<Application x:Class="WPFDevelopers.Minimal.Sample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ws="https://github.com/WPFDevelopersOrg.WPFDevelopers.Minimal"
             StartupUri="ExampleViews\\MainView.xaml" ShutdownMode="OnMainWindowClose">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/WPFDevelopers.Minimal.Sample.Net40;component/Light.Carmine.xaml"/>
                 <!--需要注意ws:Resources 必须再配色主题后,Theme="Dark" 为黑色皮肤 -->
                <ws:Resources Theme="Light"/>
                <ResourceDictionary Source="pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Theme.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
  • 2)或者把资源添加到ThemesCollection集合中;

if (ThemesCollection != null)
    ThemesCollection.Add(new ThemeModel
    
     Color = "#B31B1B",
     ResourcePath =
     "pack://application:,,,/WPFDevelopers.Minimal.Sample.Net40;component/Light.Carmine.xaml"
   );
  • 重启应用效果如下;

1)ThemeControl.cs代码如下;

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using WPFDevelopers.Minimal.Helpers;
using WPFDevelopers.Minimal.Models;

namespace WPFDevelopers.Minimal.Controls

    public class ThemeControl : Control
    
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<ThemeModel>), typeof(ThemeControl),
                new PropertyMetadata(null));

        static ThemeControl()
        
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ThemeControl),
                new FrameworkPropertyMetadata(typeof(ThemeControl)));
        

        public ObservableCollection<ThemeModel> ItemsSource
        
            get => (ObservableCollection<ThemeModel>)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        

        public override void OnApplyTemplate()
        
            base.OnApplyTemplate();

            ItemsSource = new ObservableCollection<ThemeModel>();
            ItemsSource.Add(new ThemeModel
            
                Color = "#409EFF",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Blue.xaml"
            );
            ItemsSource.Add(new ThemeModel
            
                Color = "#FF033E",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Red.xaml"
            );
            ItemsSource.Add(new ThemeModel
            
                Color = "#A21BFC",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Purple.xaml"
            );
            ItemsSource.Add(new ThemeModel
            
                Color = "#FE9426",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Orange.xaml"
            );
            ItemsSource.Add(new ThemeModel
            
                Color = "#00B050",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Green.xaml"
            );
            ItemsSource.Add(new ThemeModel
            
                Color = "#FF007F",
                ResourcePath = "pack://application:,,,/WPFDevelopers.Minimal;component/Themes/Light.Pink.xaml"
            );
            if (ThemeCache.ThemesDictCache.Count > 0)
                foreach (var item in ThemeCache.ThemesDictCache)
                    if (ItemsSource.Any(x => x.Color != item.Key))
                        ItemsSource.Add(item.Value);
            SelectChecked();
            ItemsSource.CollectionChanged += ItemsSource_CollectionChanged;
            foreach (var theme in ItemsSource)
                theme.PropertyChanged += Theme_PropertyChanged;
        

        private void SelectChecked()
        
            var existsTheme = ItemsSource.FirstOrDefault(y =>
                Application.Current.Resources.MergedDictionaries.ToList().Exists(j =>
                    j.Source != null && y.ResourcePath.Contains(j.Source.AbsoluteUri)));

            if (existsTheme != null)
                existsTheme.IsChecked = true;
        

        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        
            switch (e.Action)
            
                case NotifyCollectionChangedAction.Add:
                    foreach (ThemeModel item in e.NewItems)
                    
                        if (!ThemeCache.ThemesDictCache.ContainsKey(item.Color))
                            ThemeCache.ThemesDictCache.Add(item.Color, item);
                        item.PropertyChanged += Theme_PropertyChanged;
                        SelectChecked();
                        if (!item.IsChecked) return;
                        ReviseTheme(item);
                    

                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (ThemeModel item in e.NewItems)
                        if (ThemeCache.ThemesDictCache.ContainsKey(item.Color))
                            ThemeCache.ThemesDictCache.Remove(item.Color);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Reset:
                    break;
            
        


        private void Theme_PropertyChanged(object sender, PropertyChangedEventArgs e)
        
            if (e.PropertyName == nameof(ThemeModel.IsChecked))
            
                var theme = sender as ThemeModel;
                if (!theme.IsChecked) return;
                ReviseTheme(theme);
            
        

        private void ReviseTheme(ThemeModel theme)
        
            if (theme == null) return;

            var old = ItemsSource.FirstOrDefault(x => x.IsChecked && x.Color != theme.Color);
            if (old != null)
            
                ItemsSource.Where(y => !y.Color.Equals(theme.Color) && y.IsChecked).ToList()
                    .ForEach(h => h.IsChecked = false);
                var existingResourceDictionary =
                    Application.Current.Resources.MergedDictionaries.FirstOrDefault(x =>
                        x.Source != null && x.Source.Equals(old.ResourcePath));
                if (existingResourceDictionary != null)
                    Application.Current.Resources.MergedDictionaries.Remove(existingResourceDictionary);
                var newResource =
                    Application.Current.Resources.MergedDictionaries.FirstOrDefault(x =>
                        x.Source != null && x.Source.Equals(theme.ResourcePath));
                if (newResource != null) return;
                var newResourceDictionary = new ResourceDictionary  Source = new Uri(theme.ResourcePath) ;
                Application.Current.Resources.MergedDictionaries.Insert(0, newResourceDictionary);
                ControlHelper.ThemeRefresh();
            
        
    

    public class ThemeCache
    
        public static Dictionary<string, ThemeModel> ThemesDictCache = new Dictionary<string, ThemeModel>();
    

2)Styles.ThemeControl.xaml代码如下;

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                    xmlns:wpfsc="clr-namespace:WPFDevelopers.Minimal.Controls"
                    xmlns:model="clr-namespace:WPFDevelopers.Minimal.Models">
    
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="../Themes/Basic/ControlBasic.xaml"/>
        <ResourceDictionary Source="../Themes/Basic/Animations.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    
    <Style TargetType="x:Type wpfsc:ThemeControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="x:Type wpfsc:ThemeControl">
                    <ItemsControl ItemsSource="Binding ItemsSource,RelativeSource=RelativeSource AncestorType=wpfsc:ThemeControl">
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <RadioButton Height="40" Width="40" Margin="4,0"
                                         Cursor="Hand" 
                                         IsChecked="Binding IsChecked">
                                    <RadioButton.Style>
                                        <Style TargetType="x:Type RadioButton">
                                            <Setter Property="Template">
                                                <Setter.Value>
                                                    <ControlTemplate TargetType="x:Type RadioButton">
                                                        <Border x:Name="PART_Border"
                                                            Padding="2" 
                                                            BorderThickness="0"
                                                            BorderBrush="Binding Color">
                                                            <Grid Background="x:Null">
                                                                <Rectangle x:Name="PART_Rectangle" Fill="Binding Color"/>
                                                                <Path Data="StaticResource PathCheckMark"
                                                                      Stretch="Fill" Fill="DynamicResource BackgroundSolidColorBrush"
                                                                      VerticalAlignment="Bottom"
                                                                      HorizontalAlignment="Right"
                                                                      Height="10" Width="12"
                                                                      Margin="0,0,4,4"
                                                                      Visibility="Binding IsChecked,Converter=StaticResource bool2VisibilityConverter"/>
                                                            </Grid>
                                                        </Border>
                                                        <ControlTemplate.Triggers>
                                                            <Trigger Property="IsMouseOver" Value="True">
                                                                <Setter Property="Opacity" Value=".8" TargetName="PART_Rectangle"/>
                                                                <Setter Property="BorderThickness" Value="1" TargetName="PART_Border"/>
                                                            </Trigger>
                                                            <Trigger Property="IsChecked" Value="True">
                                                                <Setter Property="BorderThickness" Value="1" TargetName="PART_Border"/>
                                                            </Trigger>
                                                        </ControlTemplate.Triggers>
                                                    </ControlTemplate>
                                                </Setter.Value>
                                            </Setter>
                                        </Style>
                                    </RadioButton.Style>
                                </RadioButton>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <WrapPanel/>
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

3)ThemeControlExample.xaml代码如下;

<!--命名空间-->
  xmlns:ws="https://github.com/WPFDevelopersOrg.WPFDevelopers.Minimal"
  
 <TextBlock Text="Theme" FontSize="20" Margin="0,20,0,0"/>
 <ws:ThemeControl Margin="0,10" ItemsSource="Binding ThemesCollection,RelativeSource=RelativeSource AncestorType=local:MainView,Mode=OneWayToSource"/>

ThemeControl|Github[1]
ThemeControl|码云[2]
Styles.ThemeControl.xaml|Github[3]
Styles.ThemeControl.xaml|码云[4]

参考资料

[1]

ThemeControl|Github: https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal/blob/main/src/WPFDevelopers.Minimal/WPFDevelopers.Minimal.Shared/Controls/ThemeControl.cs

[2]

ThemeControl|码云: https://gitee.com/WPFDevelopersOrg/WPFDevelopers.Minimal/blob/main/src/WPFDevelopers.Minimal/WPFDevelopers.Minimal.Shared/Controls/ThemeControl.cs

[3]

Styles.ThemeControl.xaml|Github: https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal/blob/main/src/WPFDevelopers.Minimal/WPFDevelopers.Minimal.Shared/Styles/Styles.ThemeControl.xaml

[4]

Styles.ThemeControl.xaml|码云: https://gitee.com/WPFDevelopersOrg/WPFDevelopers.Minimal/blob/main/src/WPFDevelopers.Minimal/WPFDevelopers.Minimal.Shared/Styles/Styles.ThemeControl.xaml

以上是关于WPF 实现更换主题色的主要内容,如果未能解决你的问题,请参考以下文章

WPF实现主题更换的简单DEMO

Vue更换主题色的几种方法思路

WPF Modern UI 主题更换原理

element-plus 更换主题色 SassError: @forward rules must be written before any other rules

element-ui替换主题色介绍

elementUI——主题定制