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 SolidColorBrush的Color颜色值就可以实现更换主题色;
此篇主要是定义了多个公共颜色资源文件如下,选择不同的资源文件进行更换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