浅析Family Show 2.0的动态换肤实现
作者:Tony Qu
前两天Family Show 2.0终于发布了(有关Family Show的项目信息大家可以看这里)。出于兴趣,这两天对这个项目中用到的一些技术作了一些研究。首先吸引我的就是这个动态换肤功能,请看下面两张图:
这张是Black Skin的效果
这张是Silver Skin的效果
我们会发现整个窗口风格都变了,很酷吧。。。
我先来介绍一下ResourceDictionary。ResourceDictionary中文译做资源字典,顾名思义,就是一个用来存放资源的集合。ResourceDictionary元素中可以直接嵌入资源,如下所示:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="MainBackgroundBrush" Color="#FF202020"/>
<LinearGradientBrush x:Key="PanelGradientBrush" EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FF555555" Offset="0"/>
<GradientStop Color="#FF1C1C1C" Offset="1"/>
</LinearGradientBrush>
</ResourceDictionary>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="MainBackgroundBrush" Color="#FF202020"/>
<LinearGradientBrush x:Key="PanelGradientBrush" EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FF555555" Offset="0"/>
<GradientStop Color="#FF1C1C1C" Offset="1"/>
</LinearGradientBrush>
</ResourceDictionary>
也可以切入另一个ResourceDictionary,如下所示:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\Style1.xaml"/>
<ResourceDictionary Source="Resources\Style2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources\Style1.xaml"/>
<ResourceDictionary Source="Resources\Style2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
这里的MergedDicionaries是关键,它会把嵌入的资源文件合并起来,就像在一个资源中一样,但要注意一点——嵌入的资源文件必须把ResourceDictionary作为根元素。
接下来的一个重点就是动态资源引用。在WPF中资源引用分为动态和静态两种,所谓动态资源引用就是一旦资源内容改变就会影响相应的内容,例如Window的背景动态引用了一个SolidColorBrush资源,一旦这个SolidColorBrush改变,那么背景同时也会改变;而静态资源引用则是仅在资源第一次使用时加载,所以如果使用的静态资源引用,即使之后资源的内容变化了,也不会对应用了资源的元素或属性产生任何影响。由于Family Show要实现的是动态换肤,静态资源引用恐怕是无能为力了,所以得用动态资源,例如:
<Border x:Name="Header" Background="{DynamicResource BackgroundBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1,1,1,0" CornerRadius="5,5,0,0">
<TextBlock Text="Add a family member" TextWrapping="Wrap" Margin="15,5,10,5" Foreground="{DynamicResource HeaderFontColor}" FontSize="18" VerticalAlignment="Center" FontWeight="Bold" x:Name="HeaderTextBlock"/>
</Border>
<TextBlock Text="Add a family member" TextWrapping="Wrap" Margin="15,5,10,5" Foreground="{DynamicResource HeaderFontColor}" FontSize="18" VerticalAlignment="Center" FontWeight="Bold" x:Name="HeaderTextBlock"/>
</Border>
这里的Border.Background动态引用了BackgroundBrush资源Border.BorderBrush则引用了BorderBrush资源,TextBlock.Foreground则引用了HeaderFontColor资源。那么这些资源分别保存在哪里呢?原来它们都保存在每个Skin的BrushResources.xaml中,如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- The Background Brush is used as the background for the Headers and Footers -->
<SolidColorBrush x:Key="BackgroundBrush" Color="#FF202020"/>
<!-- The Border Brush is used as the color for most borders -->
<SolidColorBrush x:Key="BorderBrush" Color="#FF747474"/>
<SolidColorBrush x:Key="HeaderFontColor" Color="#FFE6E6E6"/>
</ResourceDictionary>
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- The Background Brush is used as the background for the Headers and Footers -->
<SolidColorBrush x:Key="BackgroundBrush" Color="#FF202020"/>
<!-- The Border Brush is used as the color for most borders -->
<SolidColorBrush x:Key="BorderBrush" Color="#FF747474"/>
<SolidColorBrush x:Key="HeaderFontColor" Color="#FFE6E6E6"/>
</ResourceDictionary>
所以,如果大家为找不到这些资源而发愁,这就是答案。
另外,之所以可以在Family Show中直接用{DynamicResource XXX}这样的形式直接引用资源,还有一个原因,那就是这些资源都被作为了应用程序级资源,让我们看看App.xaml中的代码:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Microsoft.FamilyShow.App"
StartupUri="MainWindow.xaml"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Use the Black skin by default -->
<ResourceDictionary Source="Skins\Black\BlackResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Microsoft.FamilyShow.App"
StartupUri="MainWindow.xaml"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Use the Black skin by default -->
<ResourceDictionary Source="Skins\Black\BlackResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
我们会发现原来默认情况下会把Skins\Black\BlackResources.xaml作为应用程序级资源引入,这也是为什么我们默认看到的是Black Skin,而不是Silver Skin。这里也顺便提一下,WPF的资源都是有作用范围的,有应用程序级的资源,也有Window级的资源,还有控件级的资源,所以在使用一个资源之前一定要先确认是否在它的作用范围之内。
但这里只解决了默认的Skin加载,如何在程序运行时实现资源替换呢?答案就是下面这段代码:
/// <summary>
/// Command handler for ChangeSkinCommand
/// </summary>
private void ChangeSkin(object sender, ExecutedRoutedEventArgs e)
{
ResourceDictionary rd = new ResourceDictionary();
rd.MergedDictionaries.Add(Application.LoadComponent(new Uri(e.Parameter as string, UriKind.Relative)) as ResourceDictionary);
Application.Current.Resources = rd;
}
/// Command handler for ChangeSkinCommand
/// </summary>
private void ChangeSkin(object sender, ExecutedRoutedEventArgs e)
{
ResourceDictionary rd = new ResourceDictionary();
rd.MergedDictionaries.Add(Application.LoadComponent(new Uri(e.Parameter as string, UriKind.Relative)) as ResourceDictionary);
Application.Current.Resources = rd;
}
ChangeSkin其实是一个事件处理程序,对应于ChangeSkin路由事件。(关于路由事件的知识由于超出了这篇文章的范畴,就不做展开了,我可能会在另一篇文章中单独讲解,这里你只要把它当作一个普通事件来理解就可以了。)这个事件处理程序是在点击Skin菜单中的菜单项时触发的。其实代码还是比较简单的——首先创建一个ResourceDictionary,然后把新的资源字典加载进来放入MergedDictionary中,最后把这个资源字典作为当前应用程序的资源。
到此,我想你对Family Show 2.0动态换肤的实现已经理解得差不多了。当然这只是WPF中换肤的基础而已,其实WPF换肤不仅仅可以换某个控件的颜色,还可以换布局、样式等,这可以让你的应用程序产生夺人眼球的效果,但由于布局和样式在WPF中是两个很大的主题,在本文中无法一一道来,建议大家可以去看看WPF Unleashed的第8章“资源”和第10章“样式、模板、皮肤和主题”,相信会对理解资源和高级换肤有很大帮助。
版权声明:本文由作者Tony Qu原创, 未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。