• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Tony Qu
我的软件工作室
博客园    首页    新随笔    联系   管理     

浅析Family Show 2.0的动态换肤实现

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>

也可以切入另一个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>

这里的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>

这里的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>

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

另外,之所以可以在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>

我们会发现原来默认情况下会把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;
          
        }

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

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

版权声明:本文由作者Tony Qu原创, 未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
posted @ 2007-07-30 09:50  找事的狐狸  阅读(4007)  评论(7)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3