作者:Tony Qu 

前两天Family Show 2.0终于发布了(有关Family Show的项目信息大家可以看这里)。出于兴趣,这两天对这个项目中用到的一些技术作了一些研究。首先吸引我的就是这个动态换肤功能,请看下面两张图:
浅析Family Show 2.0的动态换肤实现
这张是Black Skin的效果

浅析Family Show 2.0的动态换肤实现
这张是Silver Skin的效果

我们会发现整个窗口风格都变了,很酷吧。。。

我先来介绍一下ResourceDictionary。ResourceDictionary中文译做资源字典,顾名思义,就是一个用来存放资源的集合。ResourceDictionary元素中可以直接嵌入资源,如下所示:

浅析Family Show 2.0的动态换肤实现<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
浅析Family Show 2.0的动态换肤实现    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现  
<SolidColorBrush x:Key="MainBackgroundBrush" Color="#FF202020"/>
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现  
<LinearGradientBrush x:Key="PanelGradientBrush" EndPoint="1,0.5" StartPoint="0,0.5">
浅析Family Show 2.0的动态换肤实现    
<GradientStop Color="#FF555555" Offset="0"/>
浅析Family Show 2.0的动态换肤实现    
<GradientStop Color="#FF1C1C1C" Offset="1"/>
浅析Family Show 2.0的动态换肤实现  
</LinearGradientBrush>
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现
</ResourceDictionary>

也可以切入另一个ResourceDictionary,如下所示:

浅析Family Show 2.0的动态换肤实现<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
浅析Family Show 2.0的动态换肤实现   xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
浅析Family Show 2.0的动态换肤实现  
<ResourceDictionary.MergedDictionaries>
浅析Family Show 2.0的动态换肤实现    
<ResourceDictionary Source="Resources\Style1.xaml"/>
浅析Family Show 2.0的动态换肤实现    <ResourceDictionary Source="Resources\Style2.xaml"/>
浅析Family Show 2.0的动态换肤实现  
</ResourceDictionary.MergedDictionaries>
浅析Family Show 2.0的动态换肤实现
</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">
浅析Family Show 2.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>

这里的Border.Background动态引用了BackgroundBrush资源Border.BorderBrush则引用了BorderBrush资源,TextBlock.Foreground则引用了HeaderFontColor资源。那么这些资源分别保存在哪里呢?原来它们都保存在每个Skin的BrushResources.xaml中,如下:

浅析Family Show 2.0的动态换肤实现<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
浅析Family Show 2.0的动态换肤实现    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现  
<!-- The Background Brush is used as the background for the Headers and Footers -->
浅析Family Show 2.0的动态换肤实现  
<SolidColorBrush x:Key="BackgroundBrush" Color="#FF202020"/>
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现  
<!-- The Border Brush is used as the color for most borders -->
浅析Family Show 2.0的动态换肤实现  
<SolidColorBrush x:Key="BorderBrush" Color="#FF747474"/>
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现  
<SolidColorBrush x:Key="HeaderFontColor" Color="#FFE6E6E6"/>
浅析Family Show 2.0的动态换肤实现
浅析Family Show 2.0的动态换肤实现
</ResourceDictionary>

所以,如果大家为找不到这些资源而发愁,这就是答案。

另外,之所以可以在Family Show中直接用{DynamicResource XXX}这样的形式直接引用资源,还有一个原因,那就是这些资源都被作为了应用程序级资源,让我们看看App.xaml中的代码:

浅析Family Show 2.0的动态换肤实现<Application
浅析Family Show 2.0的动态换肤实现    
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
浅析Family Show 2.0的动态换肤实现    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
浅析Family Show 2.0的动态换肤实现    x:Class
="Microsoft.FamilyShow.App"
浅析Family Show 2.0的动态换肤实现    StartupUri
="MainWindow.xaml"
浅析Family Show 2.0的动态换肤实现    
>
浅析Family Show 2.0的动态换肤实现  
<Application.Resources>
浅析Family Show 2.0的动态换肤实现    
<ResourceDictionary>
浅析Family Show 2.0的动态换肤实现      
<ResourceDictionary.MergedDictionaries>
浅析Family Show 2.0的动态换肤实现        
<!-- Use the Black skin by default -->
浅析Family Show 2.0的动态换肤实现        
<ResourceDictionary Source="Skins\Black\BlackResources.xaml"/>
浅析Family Show 2.0的动态换肤实现      
</ResourceDictionary.MergedDictionaries>
浅析Family Show 2.0的动态换肤实现    
</ResourceDictionary>
浅析Family Show 2.0的动态换肤实现  
</Application.Resources>
浅析Family Show 2.0的动态换肤实现
</Application>

我们会发现原来默认情况下会把Skins\Black\BlackResources.xaml作为应用程序级资源引入,这也是为什么我们默认看到的是Black Skin,而不是Silver Skin。这里也顺便提一下,WPF的资源都是有作用范围的,有应用程序级的资源,也有Window级的资源,还有控件级的资源,所以在使用一个资源之前一定要先确认是否在它的作用范围之内。

但这里只解决了默认的Skin加载,如何在程序运行时实现资源替换呢?答案就是下面这段代码:
        }

ChangeSkin其实是一个事件处理程序,对应于ChangeSkin路由事件。(关于路由事件的知识由于超出了这篇文章的范畴,就不做展开了,我可能会在另一篇文章中单独讲解,这里你只要把它当作一个普通事件来理解就可以了。)这个事件处理程序是在点击Skin菜单中的菜单项时触发的。其实代码还是比较简单的——首先创建一个ResourceDictionary,然后把新的资源字典加载进来放入MergedDictionary中,最后把这个资源字典作为当前应用程序的资源。

到此,我想你对Family Show 2.0动态换肤的实现已经理解得差不多了。当然这只是WPF中换肤的基础而已,其实WPF换肤不仅仅可以换某个控件的颜色,还可以换布局、样式等,这可以让你的应用程序产生夺人眼球的效果,但由于布局和样式在WPF中是两个很大的主题,在本文中无法一一道来,建议大家可以去看看WPF Unleashed的第8章“资源”和第10章“样式、模板、皮肤和主题”,相信会对理解资源和高级换肤有很大帮助。
 

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-09-10
  • 2021-12-22
  • 2022-12-23
猜你喜欢
  • 2021-07-10
  • 2022-01-29
  • 2022-02-10
  • 2022-12-23
  • 2022-12-23
  • 2022-03-08
相关资源
相似解决方案