WPF使用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

      WPF使用WPFDevelopers.Minimal实现更换主题色

  • 使用 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"
   });
  • 重启应用效果如下;
WPF使用WPFDevelopers.Minimal实现更换主题色
WPF使用WPFDevelopers.Minimal实现更换主题色

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={RelativeSour