您的位置:首页 > 产品设计 > UI/UE

WPF Modern UI 主题更换原理

2018-08-25 20:11 344 查看

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>

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

三 、总结

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

ComboBox 绑定 SettingsAppearanceViewModel 类中的 ThemesSelectedTheme 两个字段。

SettingsAppearanceViewModel.SelectedThemeset 的时候更改 AppearanceManager.Current.ThemeSource的值。

AppearanceManager.Current.ThemeSource 被更改时进行主题资源的置换以及其他一系列操作,最后触发 AppearanceManager.Current.PropertyChanged 事件

ModernWindow 中绑定到 AppearanceManager.Current.PropertyChanged 事件的函数 OnAppearanceManagerPropertyChanged 被触发,打开 backgroundAnimation 动画。

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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: