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. Batch_Size 详解

    Batch_Size(批尺寸)是机器学习中一个重要参数,涉及诸多矛盾,下面逐一展开. 首先,为什么需要有 Batch_Size 这个参数? Batch 的选择,首先决定的是下降的方向.如果数据集比较小 ...

  2. 包建强的培训课程(17):Java代码敏捷之道

    第1讲 千言万语聊注释 按图索骥 奇葩注释“赏析” Git提交的学问 第2讲 RxJava:函数式编程 从一只猫的故事说起 背压 第3讲 代码瘦身 抽象相同逻辑的代码 查找相似代码 AOP一瞥 第4讲 ...

  3. 新鲜出炉的一套Java面试题

    作者:孤独烟 由于近期是互联网寒冬,然而烟哥的好友还是顶着重重压力出去面试,最终斩获无数offer.在烟哥的沟通下,终于套得其中一套题目,故在此分享! 公司:国内三巨头其中的一家!面试时间约在1月份左 ...

  4. Javascript高级编程学习笔记(32)—— 客户端检测(1)能力检测

    能力检测 浏览器厂商虽然在实现公共接口方面投入了大量的精力 但是每种浏览器仍旧存在许多差异 为了让网页能跨浏览器的运行,对浏览器差异做的兼容处理自然无法避免 其中最常用的也就是我们现在所说的能力检测 ...

  5. Android开发技术周报182学习记录

    Android开发技术周报182学习记录 教程 App安全二三事 记录 为什么要安全 App的移动安全主要包括下面几种: 密钥破解,导致本地加密数据被盗取. 通信密钥破解,导致接口数据被盗取. 伪造接 ...

  6. 前端页面使用emoji

    概述 最近在工作中遇到了移动端emoji的显示问题,于是查找了一些相关资料,试验了一番,把心得记录下来,供以后开发时参考,相信对其他人也有用. 参考资料: 为网站添加emoji表情的支持 思路 在页面 ...

  7. Jquery Ajax Realize whether the user is registered

    XMLHttpRequest对象可以在不向服务器提交整个页面的情况下,实现局部更新网页.当页面全部加载完毕后,客户端通过该对象向服务器请求数据,服务器端接受数据并处理后,向客户端反馈数据. XMLHt ...

  8. 玩转Python图片处理 (OpenCV-Python )

    OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux.Windows.Android和Mac OS操作系统上.它轻量级而且高效——由一系列 C 函数和少量 C++ 类 ...

  9. axios的秘密

    vue自2.0开始,vue-resource不再作为官方推荐的ajax方案,转而推荐使用axios. 按照作者的原话来说: “Ajax 本身跟 Vue 并没有什么需要特别整合的地方,使用 fetch ...

  10. 浅谈《think in java》:二 一切都是对象

    清晨坐在图书馆,今天聊聊早晨的“果实”. 清单1. 引用操作:对象创建:存储地方:基本类型:数组 “reference”:引用[操作对象的标识符] 例子:创建String类型的引用,并不是对象. St ...