- A+
WPF不但支持程序级的传统资源,同时还推出了独具特色的对象级资源,每个界面元素都可以携带自己的资源并可被自己的子级元素共享。
WPF对象资源的定义和查找
每个WPF界面元素都有一个名为Resource的属性,其类型为ResourceDictionary(继承至FrameworkElement类)。
ResourceDictionary能够以键值对的形式存储资源,当要使用到某个资源的时候,使用键值对的形式获取资源对象。
在保存资源时,ResourceDictionary视资源对象为Object类型,使用资源时先要对资源对象进行类型转换,XAML编译器能够根据Attribute自动识别资源类型(类型不对就会抛出异常),在C#中需要手动对资源对象进行类型转换。
ResourceDictionary可以存储任意类型的对象,在XAML代码中向Resource添加资源时需要把正确的命名空间引入到XAML代码中,如下所示:
<Window x:Class="WpfApp.MainWindow" 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="MainWindow" Height="100" Width="300"> <Window.Resources> <ResourceDictionary> <sys:String x:Key="str">沉舟侧畔千帆过,病树前头万木春。</sys:String> <sys:Double x:Key="db">3.1415926</sys:Double> </ResourceDictionary> </Window.Resources> <StackPanel> <TextBlock Text="{ StaticResource ResourceKey=str}"/> <!--<TextBlock Text="{StaticResource ResourceKey=db}"/> 类型错误--> </StackPanel> </Window>
将System命名空间引入XAML代码中并映射为sys名称空间,然后在Windows.Resource里面添加了两个资源条目,一个是string类型,一个是double类型。
用两个textBlock来消费这两个资源(被注释掉的代码因为数据类型不匹配而抛出异常),程序运行效果如下图:
在XAML代码里面可以对集合类容及标签扩展进行简写,上面代码的常见书写格式如下:
<TextBlock Text="{ StaticResource str}"/>
在查找资源时,先查找控件自己的Resource属性,如果没有这个资源程序会沿着逻辑树向上一级进行查找,如果连最顶端容器都没有这个资源,程序就会查找Application.Resource(也就是程序的顶级资源)。如果还没有找到,那么就只能抛出异常了。
在C#代码里面使用XAML代码里面定义的资源,格式如下:
private void Window_Loaded(object sender, RoutedEventArgs e) { string text = (string)this.FindResource("str"); textBox.Text = text; }
明确知道资源放在那个资源字典里,可以这样检索资源:
private void Window_Loaded(object sender, RoutedEventArgs e) { string text = (string)this.Resources["str"]; textBox.Text = text; }
WPF的资源可以做到像CSS或者JS一样放在独立的文件夹里,使用时成套引用、重用时便于分发,只要把包含资源定义的文件路径赋值给ResourceDictionary的Source属性即可:
<Window.Resources> <ResourceDictionary Source="ShinyRed.xaml"/> </Window.Resources>
动态、静态使用资源
当资源被存储进资源词典之后,可以使用两种方式来使用这些资源-----静态方式和动态方式。
对于资源的使用,Static指的是程序的非执行状态而Dynamic指的是程序的运行状态。
静态资源使用StackResource指的是程序载入内存时对资源的一次性使用(之后不在访问),动态资源(DynamicResource)使用指的是在程序运行过程中仍然会去访问资源。
在Windows资源字典里放置了两个TextBlock类型资源,并分别以StaticResource和DynamicResource方式使用之:
<Window.Resources> <ResourceDictionary> <TextBlock x:Key="res1" Text="海上生明月"/> <TextBlock x:Key="res2" Text="海上生明月"/> </ResourceDictionary> </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>
界面上的第三个按钮负责在程序运行过程中对资源词典里面的两个资源进行改变:
private void Button_Click(object sender, RoutedEventArgs e) { this.Resources["res1"] = new TextBlock() { Text = "天涯共此时" }; this.Resources["res2"] = new TextBlock() { Text = "天涯共此时" }; }
第一个按钮是以静态方式使用资源,尽管资源已经更新它也不知道。
运行程序,单击第三个按钮,效果如下图:
向程序添加二进制资源
常见的应用程序资源有图标、图片、文本、音频、视频等,各种编程语言的编译器或者资源编译器都有能力把这些文件编译进目标文件(最终的.exe文件或者.dll文件)。
资源文件在目标文件里以二进制数据形式存在、形成目标文件的资源段(Resource Section),使用时数据会被提取出来。
为了区分,称呼资源词典里面的资源为“WPF资源”或“对象资源”,称呼应用程序内嵌资源为“程序集资源”或者“二进制资源”。WPF中写在<Application.Resource>...</Application.Resource>标签内的资源仍然是WPF资源而非二进制资源。
字符串资源
如果要添加的资源是字符串而非文件,可以使用应用程序名称空间下的Resources.resx资源文件。打开资源文件的方法是项目管理器中展开Properties文件夹,并双击下面的Resources.resx资源文件。
Resources.resx文件内容的组织形式是“键-值”对,编译后Resources.resx会形成Properties名称空间中的Resource类,使用这个类的方法或属性就能获取资源。
在资源文件的字符串组里添加两个条目,然后分别在XAML代码和C#代码中访问它们。一定要在资源文件编辑器里把Resources.resx的访问级别由Internal改为public,否则运行会报错。
在XAML代码中使用Resources.resx中的资源,需要把程序的Properties名称映射为XAML名称空间,然后使用x:Static标签扩展来访问资源:
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prop="clr-namespace:WpfApp.Properties" Title="MainWindow" Height="130" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="23" /> <RowDefinition Height="4" /> <RowDefinition Height="23" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="4" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Text="{x:Static prop:Resources.UserName}"/> <TextBlock x:Name="textBlockPassword" Grid.Row="2"/> <TextBox BorderBrush="Black" Grid.Column="2"/> <TextBox BorderBrush="Black" Grid.Row="2" Grid.Column="2"/> </Grid> </Window>
C#中访问Resources.resx的资源如下:
this.textBlockPassword.Text = Properties.Resources.Password;
效果如下:
使用Resources.resx最大的好处就是便于程序国际化,本地化。
非字符串资源
如果要添加的资源不是字符串,而是图标、图片、音频或者视屏,就不能使用Resources.resx了,WPF不支持这么做。在WPF使用外部文件作为资源,仅需要将其简单的放入项目即可。
方法是在项目管理器上右击项目名称,在弹出的菜单里选择“添加”-->“新建文件夹”,按需要新建几层文件夹来存放资源,然后在恰当的文件夹上右击,在弹出的菜单里选择“添加”--->“现有项”,在文件对话框里选择文件后单击“添加”按钮,文件就以资源的形式加入项目中了。
如果想让外部文件编译进二进制资源,必须在属性窗口把文件的“生成操作”属性值设为“Resource”。并不是每种文件都会自动设置为“Resource”,比如图片文件会,MP3文件就不会,一般情况下,如果“生成操作”的值设为“Resource”,则“复制到输出目录”属性设置为“不复制”;如果不希望以资源的形式使用外部文件,可以把“生成操作”属性设置为“无”,而把“复制到输出目录”设置为“始终复制”。另外,“生成操作”属性的下拉列表里面有一个颇具迷惑性的值“嵌入的资源”,不要选择这个值。
使用Pack URI路径访问二进制资源
WPF对二进制资源的访问有自己的一套方法,称为Pack URI路径。WPF的Pack URI路径只需要记住以下格式:
pack://application,,,[/程序集名称;][可选版本号;][文件夹名称/][文件名称]
实际上pack://applicationi,,,可以省略、程序集名称和版本号常使用省略值,剩下的就只有以下内容:
[文件夹名称/][文件名称]
访问Resource/Images/Rafale.gif,用这个图片填充一个<Image/>元素并把<image/>元素作为窗体的背景。
XAML代码如下:
<Image Source="Resource/Images/Rafale.gif" x:Name="ImageBg" Stretch="Fill"/> <!--或--> <Image Source="pack://application:,,,/Resource/Images/Rafale.gif" x:Name="ImageBg" Stretch="Fill"/>
等价的C#代码如下:
Uri imgUri = new Uri(@"Resource/Images/Rafale.gif",UriKind.Relative); //或 //Uri imgUri = new Uri(@"pack://application:,,,/Resource/Images/Rafale.gif", UriKind.Absolute); this.ImageBg.Source = new BitmapImage(imgUri);
在使用Pack URI路径时有以下几点需要注意:
- Pack URI使用的是从右向左的正斜线(/)表示路径。
- 使用所略写意味着相对路径,C#代码中的UriKind必须为Relative而且代表根目录的/可以省略。
- 使用完整写法时是绝对路径,C#代码中的UriKind必须为Absolute并且代表根目录的/不能省略。
- 使用相对路径可以借助类似DOS的语法进行导航,比如./代表同级目录,../代表父级目录。