WPF应用程序中的程序集资源与其余.NET应用程序中的程序集资源在本质上是相同的。基本概念是为项目添加文件,从而Visual studio可将其嵌入到编译过的应用程序的EXE或DLL文件中。WPF程序集资源与其余应用程序中的程序集资源之间的重要区别是引用他们的寻址系统不一样。编程
在前面章节已讨论过程序集资源的工做原理。由于每次编译应用程序时,项目中的每一个XAML文件都转换为解析效率更高的BAML文件。这些BAML文件做为独立资源嵌入到程序集中。添加本身的资源一样很容易。浏览器
1、添加资源安全
可经过向项目添加文件,并在Properties窗口中将其Build Action属性设置为Resource来添加本身的资源。这是须要完成的所有工做——这确实是好消息。app
为更加合理地组织资源,可在项目中建立子文件夹(在Solution Explorer中右击项目名称,而后选择Add|New Folder菜单项),而后使用这些子文件夹组织不一样类型的资源。工具
以这种方式添加的资源易于更新。只须要替换文件并从新编译应用程序便可。例如,可在Windows浏览器中将全部新文件复制到指定文件夹中。只要替换在项目中包含的文件的内容,就没必要在Visual Studio中再采起任何其余特殊步骤(除了实际编译应用程序外)。测试
为成功地使用程序集资源,务必注意如下两点:ui
不能将Build Action属性错误地设置为Embedded Resource。尽管全部程序集资源都被定义为嵌入的资源,但Embedded Resource生成操做会在另外一个更难访问的位置放置二进制数据。在WPF应用程序中,假定老是使用Resource生成类型。this
不要将Project Properties窗口中使用Resource选项卡。WPF不支持这种类型的资源URI。spa
好奇的编程人员天然但愿了解嵌入到程序集中的资源到底发生了什么变化。WPF将他们和其余BAML资源合并到单独的流中。单独的资源流使用如下格式命名AssemblyName.g.resources。.net
若是想要实际查看在编译过的程序集中嵌入的资源,可以使用反编译工具。例如,使用Reflector(http://reflector.net)的更出色工具来深刻挖掘资源。
除全部图像和音频文件外,还可看到用于应用程序中窗口的BAML资源。在WPF中,文件中的空格不会引发问题,由于Visual Studio足够智能,它可以正确地略过他们。当应用程序被编译过以后,你可能还会注意到文件名变成了小写形式。
2、检索资源
显然,添加资源很是容易,但到底如何使用他们呢?能够采用多种方法来使用资源。
低级方法是检索封装数据的StreamResourceInfo对象,而后决定如何使用该对象。可经过代码,使用静态方法Application.GetResourceStream()完成该工做。例如,下面的代码为winter.jpg图像获取StreamResourceInfo对象:
StreamResourceInfo sri=Application.GetResourceStream(new Uri("image/winter.jpg",UriKind.Relative));
一旦获得StreamResourceInfo对象,就能够获得两部分信息。ContentType属性返回一个描述数据类型的字符串——在该例中是image/jpg。Stream属性返回一个UnmanagedMemoryStream对象,可以使用该对象读取数据,一次读取一个字节。
GetResourceStream()的确是一个颇有用的辅助方法,它封装了ResourceManager类和ResourceSet类。这些类是.NET Framework资源体系的核心,自从.NET 1.0开始就提供了这些类。若是不使用GetResourceStream()方法,就须要具体访问AssemblyName.g.resources资源流(这是存储全部WPF资源的地方),并查找所需的对象。下面是完成这一操做的很是简单的代码:
Assembly assembly=Assembly.GetAssembly(this.GetType()); string resourceName=assembly.GetName().Name+".g"; ResourceManager rm=new ResourceManager(resourceName,assembly); using(ResourceSet set=rm.GetResourceSet(CultureInfo.CurrentCulture,tur,true)) { UnmanagedMemoryStream s; s=(UnmanagedMemoryStream)set.GetOjbect("images/winter.jpg",true); }
经过ResourceManager类和ResourceSet类还可完成其余一些Application类自身不能完成的工做。例如,下面的代码片断会向你现实在AssemblyName.g.resources资源流中全部嵌入资源的名称:
Assembly assembly=Assembly.GetAssembly(this.GetType()); string resourceName=assembly.GetName().Name+".g"; ResourceManager rm=new ResourceManager(resourceName,assembly); using(ResourceSet set=rm.GetResourceSet(CultureInfo.CurrentCulture,true,ture)) { foreach(DictionaryEntry res in set) { MessageBox.Show(res.Key.ToSting()); } }
虽然GetResourceStream()方法可提供帮助,但直接检索资源还可能会遇到麻烦,问题是使用该方法获得的相对低级的UnmanagedMemoryStream对象,该对象自己没有什么用处,须要将它转换成一些更有意义的数据,例如具备属性和方法的高级对象。
WPF提供了几个专门使用资源的类。这些类不要求提取资源(这很是混乱且不是类型安全的),他们使用资源的名称访问资源。例如,若是但愿在WPF的Image元素中显示Blue.jpg图像,可以使用下面的标记:
<Image Source="Images/Blue.jpg"></Image>
注意反斜杠变成了正斜杠,由于这是WPF做用URI的约定(实际上这两种方式均可行,但为了连贯起见,建议使用正斜杠)。
可以使用代码完成相同的工做。对于Image元素,只须要将Source属性设置为BitmapImage对象,该对象使用URI肯定但愿显示的图像的位置,能够像下面这样指定彻底限定的文件路径:
img.Source = new BitmapImage(new Uri("d:\images\winter.jpg",));
但若是使用相对URI,就可从程序集中提取不一样资源,并将他们传递给图像,并且不须要使用UnmanagedMemoryStream对象:
img.Source = new BitmapImage(new Uri("images/winter.jpg", UriKind.Relative));
该技术经过在基本应用程序URI的末尾处加上images/winter.jpg构造了URI。大多数状况下不须要考虑URI语法——只要遵循相对URI,剩下的工做就由程序集负责了。然而有些状况下,更详细理解URI系统的很是重要的,当但愿访问嵌入到另外一个程序集中额资源时更是如此。
3、pack URI
WPF使用pack URI语法寻址编译过的资源(好比用于页面的BAML)。上一节的Image对象和标签使用相对URI来引用资源,以下所示:
images/winter.jpg
这与下面更繁琐的绝对URI是等效的:
pack://application:,,,/images/winter.jpg
当为一幅图像设置源时可以使用这种绝对URI,尽管这种方法没有任何优势:
img.Source = new BitmapImage(new Uri("pack://application:,,,/images/winter.jpg"));
pack URI语法来自XPS(XML Paper Specification,XML页面规范)标准。它看起来很是奇怪,由于它在一个URI中嵌入了另外一个URI。三个逗号实际上时三个转义的斜杠。换句话说,上面显示的包含应用成功需URI的pack URI是以application:///开头的。
位于其余程序集中的资源
使用pack URI还可检索嵌入到另外一个库中的资源(换句话说,在应用程序中使用的DLL程序集中的资源)。这种状况下须要使用以下语言:
pack://application:,,,/AssemblyName;component/ResourceName
例如,若是图像呗嵌入到引用的名为ImageLibrary的程序集中,将须要使用以下URI:
img.Source=new BitmapImage(new Uri("pack://application:,,,/ImageLibrary;component/images/winter.jpg"));
或从更实用的角度看,可以使用等价的相对URI:
img.Source=new BitmapImage(new Uri("ImageLibrary;component/images/winter.jpg",UriKind.Relative));
若是使用强命名的程序集,可以使用包含版本和/或公钥标记的限定程序集引用代替程序集的名称。使用分号隔离每段信息,并在版本号数字以前添加字符v.下面是一个使用版本号的示例:
image.Source=new BitmapImage(new Uri("ImageLibrary;v1.25;component/images/winter.jpg",UriKind.Relative));
下面的示例同时使用了版本号和公钥标记:
image.Source=new BitmapImage(new Uri("ImageLibrary;v1.25;dc642a7f5bd64912;component/images/winter.jpg",UriKind.Relative));
4、内容文件
当嵌入文件做为资源时,会将文件放到编译过的程序集中,而且能够确保文件老是可用的。对于部署而言这是理想选择,而且可避免可能存在的问题。然而在有些状况下,使用这种方法并不方便:
显然,可事业可以应用程序部署文件,并为应用程序添加代码,进而从硬盘驱动器中读取这些文件来解决该问题。然而,WPF还有更方便的选择,使这一过程更加容易管理。可将这些未编译的文件专门标记为内容文件。
不能将内容文件嵌入到程序集中。然而,WPF为程序集添加了AssemblyAssociatedContentFile特性,公告每一个内容文件的存在。该特性还记录了每一个内容文件相对可执行文件的位置(指示内容文件是否和可执行文件位于同一文件夹中,或者位于某个子文件夹中)。最方便的是,当为可以理解资源的元素(如Image类)使用内容文件时,可以使用相同的URI系统。
为测试该技术,为项目添加声音文件,在Solution Exporer中选择该文件,并在Properties窗口中将Build Action属性改成Content,确保将Copy to Output Directory属性设置为Copy Always,以确保当生产项目时将声音文件复制到输出目录中。
如今可以使用相对URI,将MediaElement元素指向内容文件:
<MediaElement Name="Sound" Source="Sounds/start.wav" LoadedBehavior="Manual"></MediaElement>