每个界面都可以携带自己的资源,并且能够被子集元素所共享。比如各种模板,主题和程序样式就存在对象资源里。
WPF程序中资源分为四个等级存储:数据库里的数据相当于存放在仓库里,资源文件里的数据相当于放在行李箱里,WPF对象资源里的数据相当于放在随身携带的背包里,变量中的数据相当于拿在手里。
WPF对象级资源的定义与查找:
每个 WPF 的界面元素都具有一个名为 Resources 的属性,这个属性继承自 FrameworkElement 类,其类型为 ResourceDictionary。ResourceDictionary 能够以“键—值”对的形式存储资源,当需
要使用某个资源时,使用“键—值”对可以索引到资源对象。在保存资源时,ResourceDictionary 视资源对象为 object 类型,所以在使用资源时先要对资源对象进行类型转换,XAML 编译器能够
根据标签的 Attribute 自动识别资源类型,如果类型不对就会抛出异常,但在 C#代码里检索到资源
对象后,类型转换的事情就只能由我们自己来做了。
ResourceDictionary 可以存储任意类型的对象。在 XAML 代码中向 Resources 添加资源时需要
把正确的名称空间引入到 XAML 代码中。让我们看一个例子:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="Resource" FontSize="16">
<Window.Resources>
<ResourceDictionary>
<sys:String x:Key="str">
沉舟侧畔千帆过,病树前头万木春。
</sys:String>
<sys:Double x:Key="dbl">3.1415926</sys:Double>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<TextBlock Text="{StaticResource ResourceKey=str}" Margin="5" />
<!--<TextBlock Text="{StaticResource ResourceKey=dbl}" />-->
</StackPanel>
</Window>
首先将 System 名称空间引入 XAML 代码并映射为 sys 名称空间,然后在 Window.Resources
属性里添加了两个资源条目,一个是 string 类型实例、一个是 double 类型实例,最后我用两个 TextBlock 来消费这些资
源(被注销掉的代码会因数据类型不匹配而抛出异常)。
也可以简写:
<TextBlock Text="{StaticResource str}" Margin="5" />
在检索资源时,先查找控件自己的 Resources 属性,如果没有这个资源程序会沿着逻辑树向上
一级控件查找,如果连最顶层容器都没有这个资源,程序就会去找 Application.Resources(也就
是程序的顶级资源),如果还没找到,那就只好抛出异常了。这就好比每个界面元素都有自己的一
个背包,里面可能装着各种各样的资源,使用的时候打开找一找,如果没有找到还可以去翻看上一
层控件的背包,直至找到资源或报告没有这个资源为止。
如果想在 C#代码中使用定义在 XAML 代码里的资源,大概格式是这样:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string text = (string)this.FindResource("str");
this.textBlock1.Text = text;
}
或者你明确地知道资源放在哪的资源词典里,就可以这样检索资源:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string text1 = (string)this.Resources["str"];
this.textBlock1.Text = text1;
}
你可能会想:如果能把资源像 CSS 或 JavaScript 那样放在独立的文件中,使用时成套引用、重
用时便于分发岂不更好?WPF 的资源当然能够做到这一点,ResourceDictionary 具有一个名为
Source 的属性,只要把包含资源定义的文件路径赋值给这个属性就一切搞定!举个例子,
http://wpf.codeplex.com中包含了很多官方/半官方的 WPF 资源,其中包括 WPF 工具包和一组非常
漂亮的程序皮肤,这些皮肤以资源的形式放在 XAML 文件中,使用时仅需把相应的 XAML 文件添
加进项目并使用 Source 属性进行引用,你的程序立刻就变得光鲜照人:
<Window.Resources>
<ResourceDictionary Source="ShinyRed.xaml" />
</Window.Resources>
动态和静态使用资源:
当资源被存储进资源词典后,我们可以通过两种方式来使用这些资源——静态方式和动态方
式。Static 和 Dynamic 两个词是我们的老朋友了,当这对词一同出现的时候 Static 指的是程序的非执行状态而 Dynamic 指的是程序运行状态。对于资源的使用,Static 和 Dynamic 也是这个意思。静态资源使用(StaticResource)指的是在程序载入内存时对资源的一次性使用,之后就不再去访问这个资源了;动态资源使用(DynamicResource)使用指的是在程序运行过程中仍然会去访问资源。
显然,如果你确定某些资源只在程序初始化的时候使用一次、之后不会再改变,就应该使用
StaticResource,而程序运行过程中还有可能改变的资源应该以 DynamicResource 形式使用。拿程序的主题来举例,如果程序皮肤的颜色在运行中始终不变,以 StaticResource 方式来使用资源就可以了;如果程序运行过程中允许用户更改程序皮肤的配色方案则必须以 DynamicResource 方式来使用资源。
请看下面的例子。我在 Window 的资源词典里放置了两个 TextBlock 类型资源并分别以
StaticResource 和 DynamicResource 方式来使用之:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Static v.s. Dynamic" FontSize="16">
<Window.Resources>
<TextBlock x:Key="res1" Text="海上生明月" />
<TextBlock x:Key="res2" Text="海上生明月" />
</Window.Resources>
<StackPanel>
<Button Margin="5,5,5,0" Content="{StaticResource res1}" />
<Button Margin="5,5,5,0" Content="{DynamicResource res2}" />
<Button Margin="5,5,5,0" Content="Update" Click="Button_Click" />
</StackPanel>
</Window>
界面上的第三个按钮负责在程序运行过程当中对资源词典里的两个资源进行改变:
private void Button_Click(object sender, RoutedEventArgs e)
{
this.Resources["res1"] = new TextBlock() { Text = "天涯共此时" };
this.Resources["res2"] = new TextBlock() { Text = "天涯共此时" };
}
实际上,因为第一个按钮是以静态方式使用资源,所以尽管资源已经被更新它也不会知道。