内容来源http://longer3436.blog.163.com/blog/static/12833062200987104249150/该博客
Silverlight 3 Theme研究
Silverlight 2009-09-07 22:42:49 阅读503 评论0 字号:大中小 订阅
Silverlight 3对于主题也有了很好的支持,在toolkit中已经提供了不少主题,当然也可能自己设计主题了,只要有一个xaml文件就够了,这样设计人员之间就能够共享主题。这里主要总结两个东西,一个是关于隐式主题,另一个当然就是显式的啦。
ImplicitStyle
什么是隐式样式?知道CSS的大概就知道是什么意思了。在SL中写一个Style的话,除了需要指明其TargetType,还需要指定一个Key,这样在控件中使用Style={StaticResource KeyName}的方式来加载Style。而隐式样式则不需要指定Key,也就是说如果指定了一个隐式样式的TargetType并且在项目或者某个Layout的控件中引用了这个样式,那么在项目或控件内部所有那种类型的控件都会应用那个样式,这样就不用一个一个控件地写Style=了,自然是方便不少。
正是因为所有同类型的控件都会加载同样的Style,可能在设计的时候并不希望这样,总是有一些是特别的。没有关系,因为隐式样式的优先级比较低,如果你设置了其他的样式的话自然会覆盖掉隐式样式的。
好了,现在来总结一下如何使用ImplictStyle。
使用隐式样式,首先要添加toolkit的System.Windows.Controls.Theming.Toolkit.dll程序集,当然在需要使用的xaml中也要添加相应的命名空间啦。现在添加一个新的User Control,就叫TestStyle吧。首先添加命名空间:
xmlns:theme="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.Toolkit"
然后添加一个StackPanel吧,在里面添加一个按钮,然后设定样式:
<Button Content="button" Width="100" Height="22" x:Name="button" ></Button>
当然了,这个按钮的样式现在还没有写,接下来的工作就是要添加这样一个样式了。文件放置的位置无所谓了,这里按照微软的习惯,先建立一个文件夹,名字为Assets,然后在里面添加一个xaml文件,叫Styles.xaml,样式就写在这个文件里
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:uc="clr-namespace:TestControl;assembly=TestControl"
xmlns:common="clr-namespace:Common;assembly=Common"
>
<Style TargetType="Button">
<Setter Property="Foreground" Value="Red"></Setter>
</Style>
/ResourceDictionary>
这个时候如果直接运行会发现,按钮的样式并没有改变。这个时候toolkit的ImplicitStyleManager就该出场了,可以在任意的Layout控件中使用,现在我们就在StackPanel中添加吧:
<StackPanel x:Name="LayoutRoot" theme:ImplicitStyleManager.ApplyMode="Auto" theme:ImplicitStyleManager.ResourceDictionaryUri="Assets/Styles.xaml" Background="White" >
上面的代码指定了两个东西,一个是隐式样式的添加模式,有三种:none、auto和onetime。none就跟你没有写ApplyMode的效果是一样的,而onetime就是只加载一次了,auto则允许多次加载。另一个就是ResourceDictionaryUri,它指定了隐式样式所在的文件。
那么好吧,现在直接运行一下?很不幸,程序可以编译通过,但是运行的时候出错了。这是因为ImplicitStyleManager无法找到相应的资源。那怎么办呢,很简单,Styles.xaml文件在新建的时候,其默认的“Build action”是page,这样隐式样式是无法加载的,我们需要把它改为Content。在Styles.xaml上右键,选择属性,将Build Action 改为Content。运行下程序,OK了,按钮的字体变成红色了:
并且我们可以看到,在StackPanel外面的Button是不受这个隐式样式的影响的,比如:
<StackPanel x:Name="LayoutRoot" Background="White" ><!---->
<Button Content="button without style" Width="200" Height="22"></Button>
<StackPanel theme:ImplicitStyleManager.ApplyMode="Auto" theme:ImplicitStyleManager.ResourceDictionaryUri="Assets/Styles.xaml" >
<Button Content="button" Width="200" Height="22" x:Name="button"></Button>
</StackPanel>
</StackPanel>
这样运行的结果将是:
这个结果正是我们希望的。同样,即使是在Layout控件内部的,如果已经定义了样式或对相应属性进行了设置,也是会覆盖隐式样式的。至于说隐式样式到底是怎么起作用的,这个就要涉及AttachProperty,比较麻烦了,这里暂时不讲,可以到网上搜一下。
还有一点要说的是,ResourceDictionaryUri是全局的,不仅可以在这里指定,在其他地方也可以指定,最后使用哪个路径就看哪个最后加载了。
在预编译的时候设定样式是很顺利了,那么在运行时又该如何改变呢?其实也非常简单,现在就来做个小测试吧。
首先添加一个按钮,其作用就是改变样式。然后依照刚才的方法,在Assets文件夹中添加一个新的xaml文件,将按钮的字体颜色设置为绿色。注意别忘了把Build Action改为了Content。这时需要对xaml文件作一些修改:
<Button Content="button without style" Width="200" Height="22"></Button>
<StackPanel x:Name="Container" theme:ImplicitStyleManager.ApplyMode="Auto" theme:ImplicitStyleManager.ResourceDictionaryUri="Assets/Styles.xaml" >
<Button Content="button" Width="200" Height="22" x:Name="button"></Button>
<Button Content="change style" Width="100" Height="22" Foreground="Black" Click="Button_Click" ></Button>
</StackPanel>
然后在后台添加代码:
private bool _change = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (_change)
{
ImplicitStyleManager.SetResourceDictionaryUri(this.Container, new Uri("Assets/GreenStyles.xaml", UriKind.RelativeOrAbsolute));
}
else
{
ImplicitStyleManager.SetResourceDictionaryUri(this.Container, new Uri("Assets/Styles.xaml", UriKind.RelativeOrAbsolute));
}
ImplicitStyleManager.SetApplyMode(this.Container, ImplicitStylesApplyMode.Auto);
ImplicitStyleManager.Apply(this.Container);
_change = !_change;
}
运行一下就可以看到了,添加了隐式样式的按钮,在点击之后按钮的样式就会改变,而没添加的按钮就不收影响。需要对上面的代码作一个简单的说明,调用SetResourceDictionaryUri方法来改变当前的样式文件的路径,这样可以调用不同的样式了,但是仅仅是这样是不会立刻改变的,还需要调用SetApplyMode方法设置模式,并调用Apply方法来提交改变,这样就能达到目的了。不过需要注意的是这几个方法传递的参数,第一个参数都是你想要Attach相应的样式的控件,比如我现在就是将样式Attach到StackPanel中,这样只有在里面的Button才会受影响。而如果我把“this.Container”改成“this”,那么整个UserControl都会Attach这个样式,其结果就是即使是在StackPanel外面的那个Button样式也会改变。
就跟显式样式会优于隐式样式一样,隐式样式自己也是有优先级的关系的。就像上面说的,如果在UserControl上Attach样式,但是如果在StackPanel上同样也Attach了,并且指定了ResourceDictionaryUri,那么在StackPanel里面的Button的样式就不会改变了。简单来说,就是父控件样式的范围会更广,而子控件样式的优先级会比较高。
最后还有一点想要说明的是,之前的测试都是在同一个工程里面的,如果在解决方案下有多个工程,那么在其他工程中该如何使用呢?其实是一样的,并不需要做什么改动。
好了,关于隐式样式的内容就是那么多了,再来总结一下显示样式吧。
Theme
我想写过Silverlight程序的人大多应该有这样的体会:写Style真是件让人很痛苦的事。因为随便一个什么样式都很可能会写上上百行甚至数百行或者更多。而以前全局的样式只能写在App.xaml里面,这样直接导致的一个结果就是这个文件会变得超大,大得让人无法忍受,除了打开会很慢很慢外,想要从中找出个什么东西来也是非常困难的一件事。现在就不一样了,允许将样式文件分放到不同的地方,这真是个很好的改进。现在来看看这个东西是怎么工作的吧。
为了简便起见还是用刚才定义的两个样式文件:Styles.xaml和GreenStyles.xaml。如果是显式样式的话,其Build Action似乎不是很重要(具体还没详细研究),无论是page还是Content都可以正常工作,因此就不改了。唯一做的改动就是给每个样式添加一个key,就叫ButtonStyle好了。
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Foreground" Value="Red"></Setter>
</Style>
接下来就是要改动app.xaml了:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TestControl.App"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries >
<ResourceDictionary Source="Assets/Styles.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
可以看到,我在Application.Resources里面添加了一个ResourceDictionary,同时又在其MergedDictionaries 里面再添加了一个ResourceDictionary ,在最后这个ResourceDictionary 里面指定其Source是事先写好的Style文件的路径,这样就可以全局调用这个资源了。调用的方法跟普通的样式是一样的,如下:
<Button Content="button" Width="200" Height="22" x:Name="button" Style="{StaticResource ButtonStyle}" ></Button>
这样Button就添加了样式了。
MergedDictionaries 是一个集合,有了这个东西,ResourceDictionary里面可以再添加ResourceDictionary,这样就能把样式分开到不同的文件了,是不是方便很多呢?最关键的还不是这里,有了这个东西就可以很方便的改变样式了哦,因此改变主题也就不是那么困难了。那么下面再来通过一个小例子说明下如果动态改变主题吧。
还是像刚才那样,点击一下按钮样式就改变,但是代码要做相应的变动:
App.Current.Resources.MergedDictionaries.Clear();
if (_change)
{
App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("/Assets/Styles.xaml", UriKind.RelativeOrAbsolute) });
}
else
{
App.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri("/Assets/GreenStyles.xaml", UriKind.RelativeOrAbsolute) });
}
his.button.Style = App.Current.Resources["ButtonStyle"] as Style;
上面的代码跟之前的隐式样式有些不同,首先是要将MergedDictionaries清除掉,然后添加新的资源,但是这样并不会马上改变控件的样式,还需要显示地设置其style才行,或者重新加载页面。这里有一个地方尤其需要注意的,请仔细观察两个方法设置xaml文件路径的异同,细心点可以发现,在显式样式的时候前面多了一个斜杠!这个是必须的,否则编译不通过,会抛出“对 COM 组件的调用返回了错误 HRESULT E_FAIL。”这么一个异常,原因是无法找到相应的资源(我以后哈,不是特别确定)。关于这点,就要涉及到Silverlight关于资源的引用的问题了,这里不想多说,引用同事Kevin Yang博客的一片文章中的一段话来简要说明一下吧:
- Resource —— 资源会被打包在程序集内部
- Content——资源会被打包在Xap包里面
- None——资源既不会被集成到程序集内,也不会打包到xap包中。不过我们可以通过设置CopyToOutputDirectory选项让其自动拷贝到xap包所在目录。
再来说引用的问题。
- 使用前置 / 引用资源时,SL会从当前Xap包中查找资源,找不到的话会到Xap包所在的目录查找
- 不使用前置 / 引用资源时,SL会从当前程序集内查找资源,如果找不着则会到Xap包所在目录查找其他选项你知道的话
- 你也可以使用/{程序集名};component/{图片资源路径}的方式来访问,这样就查找的路径就限定在程序集内部了,也就是那些标记为Resource的资源。
最后需要说的是,如果你的解决方案是有多个工程的,而且使用上面的路径还是抛出那个异常了,那么也不用太担心,基本上是路径的问题,可以尝试用“/{程序集名};component/{图片资源路径}”的方法来设置路径,比如我现在这个项目就可以这样设置“/TestControl;component/Assets/Styles.xaml”,这样一般不会有问题了,如果还有问题可能就是RP问题了。。。。