欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Windows Phone自定义主题

程序员文章站 2022-03-08 16:04:18
我们知道Windows Phone默认的主题系统是由黑白两色为背景和一些强调色组成的,用户可以随意切换。通常来说,应用开发者无需关心这一部分,系统会去更新相关的资源,然后再体现在应...

我们知道Windows Phone默认的主题系统是由黑白两色为背景和一些强调色组成的,用户可以随意切换。通常来说,应用开发者无需关心这一部分,系统会去更新相关的资源,然后再体现在应用中。
但有一些时候,我们基于品牌等因素的考量,可能不想使用Windows Phone的默认主题。比如我开发的“豆芽”是豆瓣网的一个客户端,我希望尽可能贴近豆瓣网本身清新的风格,而不是给用户呈现一个和豆瓣网风格大相径庭的黑色背景的界面;再比如我想让应用使用Windows Phone的默认字体(等线),而不是SDK的默认字体(雅黑)。
这些都需要我们去自定义应用的主题。
在介绍如何创建自定义主题之前,先来简单的描述一下Windows Phone主题的原理。
在Windows Phone中,系统预定义了许多资源,这些资源包括了画笔、颜色、字体、粗细、字号、文本样式等等最基本的元素(详细的资源名称可以查看这里http://msdn.microsoft.com/zh-cn/library/ff769552%28v=vs.92%29.aspx)。此外,Windows Phone中的所有控件都会有自己的样式,样式中还包括了定义控件布局的模板,而模板又利用系统内置的资源定义了控件在各种状态下的外观(所以我们在XAML中随处可以见类似{StaticResource PhoneBackgroundBrush}这样的对内置资源的引用)。


所以我们可以想到,修改内置资源或者修改控件的样式都可以达到自定义主题的效果。
在早期的Windows Phone v7.0),我们可以使用前一种方式,只需要在应用中增加一个ResourceDictionary的XAML文件,里边添加若干和系统资源相同键名的资源,即可实现对系统资源的覆盖。
但这种方法在Mango (v7.1)中无效了,它被当作一个Bug修复了,所以我们只能另寻方法。代价最小的一种方法是在App初始化的时候动态的读取我们定义的ResourceDictionary,并替换系统内置资源。具体的步骤可以参考这里http://windowsphonegeek.com/articles/Windows-Phone-Mango-Custom-application-Theme-Step-by-Step  ,我就不赘述了。
此外,还可以利用Mango带来的另外一个变化,新的Silverlight 4带来的“隐式样式”(Implicit Style)。隐式样式是指只有TargetType却没有指定Key的Style,在Silverlight 4中,会将这个Style应用到所有匹配的TargetType对象上。
我们可以利用“隐式样式”来更改内置控件的样式,只需要将需要修改的控件的样式添加到应用的ResourceDictionary中,将其Key值去掉即可(当然不去掉也可以,这就需要手工设置所有匹配控件的Style属性)。
但一般情况下,既然我们想要更改应用级别的主题,基本上我们会修改整套配色方案,如果单纯用“隐式样式”来实现的话,我们就需要实现所有控件的隐式样式,看起来似乎也不是一件简单的事情。
本文来介绍另外一种方法,这种方法不仅实现了所有控件的样式,还一并接管了所有的内置资源,但它的实现过程却一点儿也不复杂。
首先
我们在项目中添加一个XAML文件,用来存放新的主题,这里将文件名定为“CustomTheme.xaml”,下面是它的内容:
<ResourceDictionary
 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
xmlns:System="clr-namespace:System;assembly=mscorlib">
 
</ResourceDictionary>

然后编辑App.xaml,添加相应的ResourceDictionary:
<Application.Resources>
 
<ResourceDictionary>
 
<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="CustomTheme.xaml"/>
 
</ResourceDictionary.MergedDictionaries>
 
</ResourceDictionary>
 

接着打开Windows Phone SDK附带的设计资源文件夹:C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Design。我们会看到若干文件夹,这些文件夹都对应于Windows Phone中的一个主题搭配,譬如LightGreen表示浅色背景色和绿色强调色的搭配。

  Windows Phone自定义主题


 

挑选一个最贴近我们想要的主题配色的文件夹打开,我们会看到两个XAML文件,其中ThemeResources.xaml中定义了系统资源,System.Windows.xaml中定义了大多数控件的样式。
将ThemeResources.xaml和System.Windows.xaml添加到项目中,由于System.Windows.xaml引用了ThemeResources.xaml中定义的资源,所以在System.Windows.xaml的根元素下添加一个ResourceDictionary来引用ThemeResources.xaml(否则在运行时会抛出找不到资源的异常):
<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="ThemeResources.xaml"/>
 
</ResourceDictionary.MergedDictionaries>

然后在我们的CustomTheme.xaml中也添加一个对System.Windows.xaml引用的ResourceDictionary:
<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="System.Windows.xaml"/>
 
</ResourceDictionary.MergedDictionaries>

现在这几个文件的关系是:App.xaml引用了CustomTheme.xaml,CustomTheme.xaml引用了System.Windows.xaml,System.Windows.xaml引用了ThemeResources.xaml。也就是说,在应用运行时,这几个XAML会被全部加载。
至此,我们的应用中已经包含了Windows Phone的几乎所有资源和样式,但无论我们怎么修改它们的值,都不会影响应用在运行时呈现的外观。因为我们所有的模版和页面元素依然引用了系统内置的资源,而这些资源是不可覆盖的。
这里有个奇怪的现象,如果我们修改了这些资源的值,是可以在设计器中看到效果的,但丝毫不会影响运行时的效果,我相信这也是一个迟早要修复的Bug。
回到正题,既然我们已经包含了Windows Phone的几乎所有的资源和样式,那还何必去覆盖系统预置的资源和模版呢?直接使用我们自己的不就好了吗?
没错,就是这样。
我们观察到Windows Phone内置的资源和样式的名称都以“Phone”开头,非常有规律,也就非常容易替换。所以接下来我们在整个项目(或解决方案,取决于你的实际情况)里搜索x:Key=”Phone这个字符串,将其替换为x:Key=”Custom,这样会将所有资源的名称修改为CustomXXX;然后在搜索{StaticResource Phone开头的字符串,将其替换为{StaticResource Custom,这样会把所有对系统资源的引用修改为对应用内资源的引用。
如下图所示:

 

Windows Phone自定义主题

2012-03-12 09:12 by Windie Chai, 129 visits, 收藏, 编辑

我们知道Windows Phone默认的主题系统是由黑白两色为背景和一些强调色组成的,用户可以随意切换。通常来说,应用开发者无需关心这一部分,系统会去更新相关的资源,然后再体现在应用中。

但有一些时候,我们基于品牌等因素的考量,可能不想使用Windows Phone的默认主题。比如我开发的“豆芽”是豆瓣网的一个客户端,我希望尽可能贴近豆瓣网本身清新的风格,而不是给用户呈现一个和豆瓣网风格大相径庭的黑色背景的界面;再比如我想让应用使用Windows Phone的默认字体(等线),而不是SDK的默认字体(雅黑)。

这些都需要我们去自定义应用的主题。

在介绍如何创建自定义主题之前,先来简单的描述一下Windows Phone主题的原理。

在Windows Phone中,系统预定义了许多资源,这些资源包括了画笔、颜色、字体、粗细、字号、文本样式等等最基本的元素(详细的资源名称可以查看这里)。此外,Windows Phone中的所有控件都会有自己的样式,样式中还包括了定义控件布局的模板,而模板又利用系统内置的资源定义了控件在各种状态下的外观(所以我们在XAML中随处可以见类似{StaticResource PhoneBackgroundBrush}这样的对内置资源的引用)。

所以我们可以想到,修改内置资源或者修改控件的样式都可以达到自定义主题的效果。

在早期的Windows Phone v7.0),我们可以使用前一种方式,只需要在应用中增加一个ResourceDictionary的XAML文件,里边添加若干和系统资源相同键名的资源,即可实现对系统资源的覆盖。

但这种方法在Mango (v7.1)中无效了,它被当作一个Bug修复了,所以我们只能另寻方法。代价最小的一种方法是在App初始化的时候动态的读取我们定义的ResourceDictionary,并替换系统内置资源。具体的步骤可以参考这里 ,我就不赘述了。

此外,还可以利用Mango带来的另外一个变化,新的Silverlight 4带来的“隐式样式”(Implicit Style)。隐式样式是指只有TargetType却没有指定Key的Style,在Silverlight 4中,会将这个Style应用到所有匹配的TargetType对象上。

我们可以利用“隐式样式”来更改内置控件的样式,只需要将需要修改的控件的样式添加到应用的ResourceDictionary中,将其Key值去掉即可(当然不去掉也可以,这就需要手工设置所有匹配控件的Style属性)。

但一般情况下,既然我们想要更改应用级别的主题,基本上我们会修改整套配色方案,如果单纯用“隐式样式”来实现的话,我们就需要实现所有控件的隐式样式,看起来似乎也不是一件简单的事情。

本文来介绍另外一种方法,这种方法不仅实现了所有控件的样式,还一并接管了所有的内置资源,但它的实现过程却一点儿也不复杂。

首先

我们在项目中添加一个XAML文件,用来存放新的主题,这里将文件名定为“CustomTheme.xaml”,下面是它的内容:

<ResourceDictionary
 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
xmlns:System="clr-namespace:System;assembly=mscorlib">
 
</ResourceDictionary>

复制代码

然后编辑App.xaml,添加相应的ResourceDictionary:

<Application.Resources>
 
<ResourceDictionary>
 
<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="CustomTheme.xaml"/>
 
</ResourceDictionary.MergedDictionaries>
 
</ResourceDictionary>
 
</Application.Resources>

复制代码

接着打开Windows Phone SDK附带的设计资源文件夹:C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Design。我们会看到若干文件夹,这些文件夹都对应于Windows Phone中的一个主题搭配,譬如LightGreen表示浅色背景色和绿色强调色的搭配。

Windows Phone自定义主题

挑选一个最贴近我们想要的主题配色的文件夹打开,我们会看到两个XAML文件,其中ThemeResources.xaml中定义了系统资源,System.Windows.xaml中定义了大多数控件的样式。

将ThemeResources.xaml和System.Windows.xaml添加到项目中,由于System.Windows.xaml引用了ThemeResources.xaml中定义的资源,所以在System.Windows.xaml的根元素下添加一个ResourceDictionary来引用ThemeResources.xaml(否则在运行时会抛出找不到资源的异常):

<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="ThemeResources.xaml"/>
 
</ResourceDictionary.MergedDictionaries>

复制代码

然后在我们的CustomTheme.xaml中也添加一个对System.Windows.xaml引用的ResourceDictionary:

<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="System.Windows.xaml"/>
 
</ResourceDictionary.MergedDictionaries>

复制代码

现在这几个文件的关系是:App.xaml引用了CustomTheme.xaml,CustomTheme.xaml引用了System.Windows.xaml,System.Windows.xaml引用了ThemeResources.xaml。也就是说,在应用运行时,这几个XAML会被全部加载。

至此,我们的应用中已经包含了Windows Phone的几乎所有资源和样式,但无论我们怎么修改它们的值,都不会影响应用在运行时呈现的外观。因为我们所有的模版和页面元素依然引用了系统内置的资源,而这些资源是不可覆盖的。

这里有个奇怪的现象,如果我们修改了这些资源的值,是可以在设计器中看到效果的,但丝毫不会影响运行时的效果,我相信这也是一个迟早要修复的Bug。

回到正题,既然我们已经包含了Windows Phone的几乎所有的资源和样式,那还何必去覆盖系统预置的资源和模版呢?直接使用我们自己的不就好了吗?

没错,就是这样。

我们观察到Windows Phone内置的资源和样式的名称都以“Phone”开头,非常有规律,也就非常容易替换。所以接下来我们在整个项目(或解决方案,取决于你的实际情况)里搜索x:Key=”Phone这个字符串,将其替换为x:Key=”Custom,这样会将所有资源的名称修改为CustomXXX;然后在搜索{StaticResource Phone开头的字符串,将其替换为{StaticResource Custom,这样会把所有对系统资源的引用修改为对应用内资源的引用。

如下图所示:

 Windows Phone自定义主题




完成这步操作之后,我们的应用就已经基本和Windows Phone内置主题说再见了,这时我们可以修改一些资源的值,运行一下看看效果:

  Windows Phone自定义主题


 

不仅仅是颜色,因为我们已经将系统内置的绝大多数样式都包含了进来了,所以还可以修改控件的布局。譬如我们想更改CheckBox的对钩的样式,没有问题,它在模版中使用一个名为CheckMark的Path来表示的,直接修改就好:

  Windows Phone自定义主题

 

前面我一直在强调我们只包含了“绝大多数模版”,是因为还有一些控件的模版并未在System.Windows.xaml中定义,譬如Panorama和Pivot,对于这两个控件的样式我们该如何自定义呢?

需要一些小手段,我们用.NET Reflector这个工具打开C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Libraries\Silverlight\Microsoft.Phone.Controls.dll,查看它的Resources,将MicrosoftPhone.Controls.g.resources中的themes/generic.xaml保存下来。

  Windows Phone自定义主题

 

这个文件定义了Panorama和Pivot控件的样式,同样将其添加到项目中。

然后在项目中添加对Microsoft.Phone.Controls.dll的引用并将generic.xaml文件根元素中定义的两个命名空间(local和localPrimitives)的值修改一下:

xmlns:local="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls" xmlns:localPrimitives="clr-namespace:Microsoft.Phone.Controls.Primitives;assembly=Microsoft.Phone.Controls"复制代码现在我们需要把generic.xaml插入到之前创建好的“资源引用链”中,先在generic.xaml文件的根元素下增加对System.Windows.xaml的引用:

<ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="System.Windows.xaml"/> </ResourceDictionary.MergedDictionaries>复制代码再将CustomTheme.xaml中对System.Windows.xaml的引用改为对generic.xaml的引用。

现在这几个文件的关系变成了:App.xaml应用了CustomTheme.xaml,CustomTheme.xaml引用了generic.xaml,generic.xaml引用了System.Windows.xaml,System.Windows.xaml引用了ThemeResources.xaml。

OK,现在再添加一个Panorama页面,修改一下布局(Title的尺寸),试试效果:
 


 Windows Phone自定义主题


方法介绍完了,简单归纳一下,就是找出系统内置的资源和样式,添加到应用的项目中,修改所有资源和样式的Key值,修改所有对系统内置资源和样式的引用,听起来似乎工程浩大,其实只是几步简单的复制粘贴和查找替换。

唯一比较麻烦的是要维护几个xaml文件之间的互相引用,还要保持一定的顺序,其实你也可以不必这么做,如果你愿意的话,完全可以把ThemeResources.xaml、System.Windows.xaml和generic.xaml文件根元素中的内容依次复制到CustomTheme.xaml文件中,这样只需要CustomTheme.xaml一个文件就可以了,但我个人认为把这几个文件混在一块并不利于维护。

当然,这种方法也并不是十分完美的,在我看来,它存在如下一些缺陷:

•每次添加一个新的页面,都要检查其中对系统内置资源的引用,将其修改为引用我们接管的资源
•假如微软在将来的版本中修改了内置样式的模板,我们也得做对应的修改,这就会比较棘手,我的方法是,在修改这些资源的值的时候,在其旁边加一个注释来表明这个值被修改过(譬如<!–Changed–>),将来微软升级了SDK,我们可以先备份一下现有XAML文件,然后重复之前的步骤接管系统内置资源和样式,再根据备份文件中的特定注释来逐一修改。
此外,你可能会担心最后在接管Panorama和Pivot样式的时候使用了一点Hack的手段,会不会被市场拒绝,对此我也不能给出确切的答案,我只能说,“豆芽”也使用了这种方法,目前还没有遇到这方面的问题。

轻触这里下载源码http://up.2cto.com/2012/0312/20120312101530611.zip

 


摘自  『听风且吟』