WPF Modern UI 主题更换原理

2021-07-14 12:16

阅读:599

标签:www.   ems   元素   erp   NPU   资源文件   img   字段   initial   

WPF Modern UI 主题更换原理

一 . 如何更换主题?

技术分享图片

技术分享图片

二 . 代码分析

代码路径 : FirstFloor.ModernUI.App / Content / SettingsAppearance.xaml

1.关键 XAML 代码

形如 Property = "{ Binding fieldname }" 这种格式的绑定,都是将控件属性绑定到当前 用户控件 DataContext 属性的对象中的。

那我们再打开 SettingsAppearance.cs 文件看一看后台代码:

public partial class SettingsAppearance : UserControl
{
    public SettingsAppearance()
    {
        InitializeComponent();
        // a simple view model for appearance configuration
        this.DataContext = new SettingsAppearanceViewModel();
    }
}

很显然,控件的 DataContext 属性引用到了 new SettingsAppearanceViewModel()

那再F12进去分析一下 SettingsAppearanceViewModel 这个类有哪些相关的东西:

2. Themes

首先看一下 Themes 这个属性 ,它是一个 LinkCollection 对象,并返回了 themes 字段:

public LinkCollection Themes
{
    get { return this.themes; }
}

LinkCollection 继承自 ObservableCollection ,百度一下 ObservableCollection这个类,就知道它其实也是用来实现数据绑定的,当集合内的元素发生变化时,集合就会通知外部调用者,此处不多赘述。

此外,SettingsAppearance 类的构造函数中向 themes 添加了一些主题,这里就不贴代码了。

3. SelectedTheme

public Link SelectedTheme
{
    get { return this.selectedTheme; }
    set
    {
        if (this.selectedTheme != value) {
            this.selectedTheme = value;
            OnPropertyChanged("SelectedTheme");
            // and update the actual theme
            AppearanceManager.Current.ThemeSource = value.Source;
        }
    }
}

SelectedTheme 更改时,set 方法会更改当前的主题源。

ThemeSource 这个属性一直F12下去,会找到一个方法

SetThemeSource

private void SetThemeSource(Uri source, bool useThemeAccentColor)
{
    if (source == null) {
        throw new ArgumentNullException("source");
    }

    var oldThemeDict = GetThemeDictionary();
    var dictionaries = Application.Current.Resources.MergedDictionaries;
    var themeDict = new ResourceDictionary { Source = source };

    // if theme defines an accent color, use it
    var accentColor = themeDict[KeyAccentColor] as Color?;
    if (accentColor.HasValue) {
        // remove from the theme dictionary and apply globally if useThemeAccentColor is true
        themeDict.Remove(KeyAccentColor);

        if (useThemeAccentColor) {
            ApplyAccentColor(accentColor.Value);
        }
    }

    // add new before removing old theme to avoid dynamicresource not found warnings
    dictionaries.Add(themeDict);

    // remove old theme
    if (oldThemeDict != null) {
        dictionaries.Remove(oldThemeDict);
    }

    OnPropertyChanged("ThemeSource");
}

看一下第一个函数 GetThemeDictionary:

private ResourceDictionary GetThemeDictionary()
{
    // determine the current theme by looking at the app resources and return the first dictionary having the resource key ‘WindowBackground‘ defined.
    return (from dict in Application.Current.Resources.MergedDictionaries
            where dict.Contains("WindowBackground")
            select dict).FirstOrDefault();
}

这个函数使用 LINQApp.xaml 定义的 MergedDictionaries 中搜索主题资源

看一下 App.xaml 里面的代码:


    
        

这里面预定义了两个资源,去相应的地方找到这两个资源文件,其中 ModernUI.Light.xaml 内有大量包含 "WindowBackground" 字符串的 Key ,那这个显然就是主题资源文件了。

接下来的逻辑就比较好理解了:调整 AccentColor ,加入新的主题,移除旧的主题,最后通知属性更改。

4. 动画

那主题更改的渐变动画效果是在哪里触发的呢?

看一下 MainWindon 的基类 MorderWindow , 这里面监听了 AppearanceManager.Current.PropertyChanged 事件:

/// 
/// Initializes a new instance of the  class.
/// 
public ModernWindow()
{
    // 其他代码
    ...
    // listen for theme changes
    AppearanceManager.Current.PropertyChanged += OnAppearanceManagerPropertyChanged;
}

...
    
private void OnAppearanceManagerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // start background animation if theme has changed
    if (e.PropertyName == "ThemeSource" && this.backgroundAnimation != null) {
        this.backgroundAnimation.Begin();
    }
}

再找一下 backgroundAnimation 赋值的地方

/// 
/// When overridden in a derived class, is invoked whenever application code or internal processes call System.Windows.FrameworkElement.ApplyTemplate().
/// 
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
            
    // retrieve BackgroundAnimation storyboard
    var border = GetTemplateChild("WindowBorder") as Border;
    if (border != null) {
        this.backgroundAnimation = border.Resources["BackgroundAnimation"] as Storyboard;

        if (this.backgroundAnimation != null) {
            this.backgroundAnimation.Begin();
        }
    }
}

backgroundAnimation 其实是从资源文件中加载的,找到 ModernWindow.xaml 文件,相关代码:

到这里整个主题更换的流程就很明朗了。

三 、总结

总结一下主题更换的简要流程:

  1. ComboBox 绑定 SettingsAppearanceViewModel 类中的 ThemesSelectedTheme 两个字段。
  2. SettingsAppearanceViewModel.SelectedThemeset 的时候更改 AppearanceManager.Current.ThemeSource的值。
  3. AppearanceManager.Current.ThemeSource 被更改时进行主题资源的置换以及其他一系列操作,最后触发 AppearanceManager.Current.PropertyChanged 事件
  4. ModernWindow 中绑定到 AppearanceManager.Current.PropertyChanged 事件的函数 OnAppearanceManagerPropertyChanged 被触发,打开 backgroundAnimation 动画。

四、下一篇参考本流程来自己实现一个简单的主题更换功能

WPF Modern UI 主题更换原理

标签:www.   ems   元素   erp   NPU   资源文件   img   字段   initial   

原文地址:https://www.cnblogs.com/ArthurRen/p/WPF_ModernUI.html


评论


亲,登录后才可以留言!