WPF Modern UI 主题更换原理

一 . 如何更换主题?

二 . 代码分析

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

1.关键 XAML 代码

<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Themes}" SelectedItem="{Binding SelectedTheme, Mode=TwoWay}" DisplayMemberPath="DisplayName" VerticalAlignment="Center" Margin="0,0,0,4" />

形如 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<Link> ,百度一下 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 里面的代码:

<Application x:Class="FirstFloor.ModernUI.App.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
                <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Light.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

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

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

4. 动画

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

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

/// <summary>
/// Initializes a new instance of the <see cref="ModernWindow"/> class.
/// </summary>
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 赋值的地方

/// <summary>
/// When overridden in a derived class, is invoked whenever application code or internal processes call System.Windows.FrameworkElement.ApplyTemplate().
/// </summary>
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 文件,相关代码:

<Border.Resources>
    <Storyboard x:Key="BackgroundAnimation">
        <ColorAnimation Storyboard.TargetName="WindowBorderBackground" Storyboard.TargetProperty="Color" To="{DynamicResource WindowBackgroundColor}" Duration="0:0:.6" />
    </Storyboard>
</Border.Resources>

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

三 、总结

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

  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 主题更换原理的更多相关文章

  1. (转)基于 WPF + Modern UI 的 公司OA小助手 开发总结

    原文地址:http://www.cnblogs.com/rainlam163/p/3365181.html 前言: 距离上一篇博客,整整一个月的时间了.人不能懒下来,必须有个阶段性的总结,算是对我这个 ...

  2. 基于 WPF + Modern UI 的 公司OA小助手 开发总结

    前言: 距离上一篇博客,整整一个月的时间了.人不能懒下来,必须有个阶段性的总结,算是对我这个阶段的一个反思.人只有在总结的过程中才会发现自己的不足. 公司每天都要在OA系统上上班点击签到,下班点击签退 ...

  3. WPF实现主题更换的简单DEMO

    WPF实现主题更换的简单DEMO 实现主题更换功能主要是三个知识点: 动态资源 ( DynamicResource ) INotifyPropertyChanged 接口 界面元素与数据模型的绑定 ( ...

  4. WPF动态改变主题颜色

    原文:WPF动态改变主题颜色 国内的WPF技术先行者周银辉曾介绍过如何动态改变应用程序的主题样式,今天我们来介绍一种轻量级的改变界面风格的方式--动态改变主题色. 程序允许用户根据自己的喜好来对界面进 ...

  5. WPF的UI虚拟化

    许多时候,我们的界面上会呈现大量的数据,如包含数千条记录的表格或包含数百张照片的相册.由于呈现UI是一件开销比较大的动作,一次性呈现数百张照片就目前的电脑性能来说是需要占用大量内存和时间的.因此需要对 ...

  6. WPF相关UI库

    免费控件库: 1.Extended WPF Toolkit 官方拓展控件 http://wpftoolkit.codeplex.com/ 2.avalondock 可停靠布局(wpf toolkit包 ...

  7. 使用AsyncTask异步更新UI界面及原理分析

    概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...

  8. WPF多线程UI更新——两种方法

    WPF多线程UI更新——两种方法 前言 在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对 ...

  9. 2D UI和3D UI的工作原理

    2D UI的工作原理 UI控件的位置在UI Root 的红框(视窗)上,也就是UI控件的z轴,相机的z轴,UI Root的z轴都是0,因为2D UI都是纯粹的2D图片按层次显示,不会不出现三维立体效果 ...

随机推荐

  1. 解析.NET对象的跨应用程序域访问(上篇)

    在目前的项目开发中,分布式开发已经逐渐成为主流.一个项目要是没有采用分布式架构,都不好意思跟别人说这是一个完整的项目.这句话虽然有些过激,但是随着人们对效率的要求在提高,以及产品需要提升用户体验.只有 ...

  2. Java定时任务解决方案

    很多项目中都会遇到需要定时任务的情况,本篇文章就结合了Spring中以及SpringBoot.SpringCloud中定时任务的解决方案. 在Spring中使用定时器 用SpringBoot比较多的同 ...

  3. 在Ubuntu 18.04中安装pyenv(Python多版本管理工具)

    最近正在重头梳理Python的基础知识,为了更好地使用Python进行开发,防止发生版本混乱(不同的第三方库有可能因为Python版本不兼容而报错),所以需要使用pyenv进行版本管理. *** 通常 ...

  4. 线性整流函数(ReLU)

    线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元, 是一种人工神经网络中常用的激活函数(activation function),通常指代以斜坡函数及其变种为代 ...

  5. vue 项目实战 (生命周期钩子)

    开篇先来一张图 下图是官方展示的生命周期图 Vue实例的生命周期钩子函数(8个)        1. beforeCreate             刚 new了一个组件,无法访问到数据和真实的do ...

  6. C++版 - 剑指offer 面试题63:二叉搜索树的第k个结点(二叉树中序遍历的应用) 题解

    面试题 63:二叉搜索树的第k个结点 题目:给定一颗二叉搜索树,请找出其中的第k大的结点.例如, 5 / \ 3 7 /\ /\ 2 4 6 8 (见下面的图1) 中,按结点数值大小顺序第三个结点的值 ...

  7. HashMap在JDK1.7中可能出现的并发问题

    在JDK1.7及以前中,如果在并发环境中使用HashMap保存数据,有可能会产生死循环的问题,造成cpu的使用率飙升.之所以会发生该问题,实际上就是因为HashMap中的扩容问题. HashMap的实 ...

  8. Singly linked list algorithm implemented by Java

    Jeff Lee blog:   http://www.cnblogs.com/Alandre/  (泥沙砖瓦浆木匠),retain the url when reproduced ! Thanks ...

  9. 【EF6学习笔记】(五)数据库迁移及部署

    原文地址:Code First Migrations and Deployment 原文主要讲两部分:开发环境下数据库迁移到其他数据库服务器:以及在Azure上如何部署应用: 迁移数据库 原文前面讲一 ...

  10. vue-06-过度和动画

    1, css过度与动画 需要使用 v-if, v-show 来进行 1), 过度类名 v-enter: 进入时触发 v-enter-active: 执行过程中 v-enter-to: 停止时进行 v- ...