WPF的资源有两种,一种称为“程序集资源”( assembly resources)或者“二进制资源”(binary resources),在MSDN中将其称为“应用程序数据文件”(application data files);另外一种称为资源或者对象资源(object resources)、“逻辑资源”(logic resources),甚至“声明式资源”(declarative resources)。统称前者为程序集资源,后者为逻辑资源。
一、程序集资源
应用程序中常常依赖一些XAML、图片、音频和视频等文件,可以将其作为程序集资源组织起来。程序集资源可以以如下三种方式打包:
1.资源文件(Resource File):直接嵌入到程序集中。
资源文件是被直接打包到程序集当中的,先看下面一个示例。我们在解决方案资源管理器中的目录上点击右键,然后选中添加|新建文件夹,然后在这个新建的文件夹中添加一些图片。如下图,我们在图片文件的属性里面看到它的Build Action选项均为Resource。
下面我们在程序中使用这些图片文件 ,代码如下:
<Window x:Class="AssemblyResources.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<GroupBox Header="Pack URIs">
<StackPanel>
<Image Source="pack://application:,,,/Image/1348648395834.jpg"/>
</StackPanel>
</GroupBox>
</StackPanel>
</Grid>
</Window>
在图片文件的属性页中有一个Build Action成员,这个成员有很多可选值。其中有一个Embedded Resource(嵌入式资源)和Resource,这两者都会在应用程序中嵌入一个程序集资源,前者用于在Winform程序中嵌入程序集资源,后者用于WPF。在上述代码中,有一句引用资源的XAML代码:pack://application:,,,/Image/1348648395834.jpg ,这种字符串是按照URI的规范来写的,我们也可以使用代码来访问这个图片资源,如下:
Uri uri = new Uri("/Image/1348648395834.jpg",UriKind.Relative);
StreamResourceInfo info = Application.GetResourceStream(uri);
BitmapImage bitmapimg = new BitmapImage();
//注意为BitmapImage赋值需要调用BeginInit和EndInit两个函数
bitmapimg.BeginInit();
bitmap.StreamSource = info.Stream;
bitmapimg.EndInit();
img.Source = bitmapimg;
也可以直接指定BitmapImage的Uri,如下:
img.Source = new BitmapImage(new Uri("/Image/1348648395834.jpg",UriKind.Relative));
这些资源文件都会被编译到一个名为“项目名.g.resources”文件中。2.内容文件(Content File):该文件的相关信息都会编译到程序集中,如文件的相对位置等。
内容文件并不打包到程序集中,但是内容文件还算是在应用程序的掌控之中。因为内容文件的相关信息会被编译到应用程序中。在上述项目里面添加一个Sound文件夹,然后在文件夹中添加一个声音文件。查看属性我们可以看到该文件的Build Action选项设置为Content属性,我们需要将另一个属性Copy To Output Directory设置为Copy always或者Copy If newer,方可将其复制到bin\dibug目录下。只不过前者每次编译均复制,后者在文件更新时复制。这个声音文件主要用来当触发按钮单击事件时播放声音,代码如下:
<Button Content="Play chimes.wav">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<SoundPlayerAction Source="\Sound\chimes.wav"/>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
我们也可以使用代码来访问Content文件,在上面的项目中添加一个名为ContentFile.xaml的Page文件,将其Build Action属性设置为Content,Copy To Output Directory 设置为Copy always,代码如下所示:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBlock>在应用程序中的内容文件</TextBlock>
</Page>
注意这个xmal文件里面不能有x:Class属性,否则程序会报错。我们在MainWindow里面使用GetContentStream方法来将Page的内容添加到MainWindow中去。代码如下:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//引用资源文件
//引用内容文件
Uri uri = new Uri("/ContentFile.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetContentStream(uri);
XamlReader reader = new XamlReader();
Page page = (Page)reader.LoadAsync(info.Stream);
this.contentfileframe.Content = page;
}
也可以直接使用如下代码:
this.contentfileframe.Source = new Uri("/ContentFile.xaml",UriKind.Relative);
其中contentfileframe为一个Frame标签的名称。
这个ContentFile也可以使用XAML引用,如下:
<Frame x:Name="contentfileframe" Source="pack://application:,,,/ContentFile.xaml"></Frame>
3.Site of Origin文件:不参加编译,应用程序不知道该文件是否存在。
Site of Origin文件根本就不参加编译,换句话说,应用程序在编译时根本不知道Site of Origin文件的存在,只有运行时知道,如下:
<Frame Source = "http://www.cnblogs.com/helloj2ee/"/>
<Image Source = "file:///C:/DataFile.bmp"/>
配置Site of Origin文件需要把Build Action属性设置为None,Copy to Output Directory设置为Copy always或者是Copy If newer。通过代码访问则需要使用GetRemoteStream方法。
Uri uri = new Uri("/siteoforiginfile.xaml", UriKind.Relative);
StreamResourceInfo info = Application.GetRemoteStream(uri);
XamlReader reader = new XamlReader();
Page page = (Page)reader.LoadAsync(info.Stream);
this.contentfileframe.Content = page;
二、URI语法
一个URI一般由如下3个部分组成:
1.协议,也称为“服务方式”。
2.该资源的主机IP地址,有时也包括端口号。
3.主机资源的地址,如目录和文件名等。
如http://www.cnblogs.com/helloj2ee/表示一个可通过HTTP协议访问的资源,位于主机www.cnblogs.com,通过路径/helloj2ee/访问。
在WPF中通过URI来标识和访问资源,包括如下情况:
1.当应用程序启动时设置需要显示的用户界面。
2.装载图像
3.页面之间的导航。
4.装载资源、内容和Site of Origin文件
URI可以通过当前程序集、被引用的程序集、相对程序集的一个位置和任意位置标识访问资源。WPF中的URI同样由3个部分组成,第1个部分的协议是pack;第2个部分称为“authority”,有两种值:一是application:///,表示编译时知道的文件,主要指资源和内容文件;二是siteoforigin:///,表示Site of Origin文件;第3个部分是路径,如果是引用程序集中的资源,情况会稍微复杂一些。路径必须包含引用的程序集名称和一个Component标识,表示引用的不是本地程序集的资源,有时还需要加上版本信息。WPF中的路径分为相对路径和绝对路径。如下代码,前者是一个绝对路径,后者是一个相对路径。WPF中的默认URI设置是pach://application:,,,,因此引用Site of Origin文件必须要用绝对路径。
pack://application:,,,ResourceFile.xaml
/ResourceFile.xaml
三、逻辑资源
逻辑资源是WPF中的特有概念,它是一些保存在元素Resources属性中的.Net对象,通常需要共享给多个子元素。我们先看一个关于使用逻辑资源的示例:
<Window x:Class="LogicResourceDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ImageBrush x:Key="TileBrush" TileMode="Tile" ViewportUnits="Absolute"
Viewport="0 0 32 32" ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
</Window.Resources>
<Grid>
<Button Background="{StaticResource ResourceKey=TileBrush}" Padding="5"
FontWeight="Bold" FontSize="14" Margin="5">A Tiled Button</Button>
</Grid>
</Window>
在WPF中,Application、FrameworkElement和FrameworkContentElement等基类均包含Resources属性,因此大部分WPF类都有这个属性。该属性实际上是一个资源集合的容器,每个资源需要用x:Key关键字来唯一标识。定义在窗口中的资源可以在整个窗口内使用,定义在应用程序内的资源可以在整个应用程序中使用。我们仍然可以通过标记扩展的方式来引用资源,如上例中的Background="{StaticResources TileBrush}"。在C#代码中,我们可以使用如下代码来引用资源:
ImageBrush brush = (ImageBrush)this.Resources["TileBrush"];
WPF提供两种访问逻辑资源的方式:一是静态资源,通过StaticResources标记扩展来实现,前面引用资源的方式均为这种方式;二是动态资源,通过DynamicResource标记扩展来实现。这两种方式的主要区别在于静态资源只从资源字典中查找一次数据,动态资源在应用程序需要时查找资源。
静态资源和动态资源的总结如下:
1.静态资源只有一次机会从资源字典中查找资源,一般是加载窗口或者页面时;动态资源标记扩展在应用程序每次需要时查找资源。
2.由于需要跟踪变化,动态资源需要占用更多的资源,因此过多地使用会影响程序的性能;另一方面,使用动态资源可以改善加载时间,因其仅在需要时加载。
3.静态资源不支持向前引用(forward reference),即任何资源都必须声明后使用;动态资源则没有这种限制。
我们一般大多数情况下都是使用动态资源,只有使用系统资源时才使用动态资源。
动态资源主要用在系统资源,这是因为当系统环境设置发生变化时应用程序也需要随之变化,那么只有动态资源才能满足这样的需求。系统资源主要是指SystemColors、SystemFonts和SystemParameters类。在System.Drawing和System.Windows命名空间均有这3个类。使用System.Color有两种方式,一种是直接使用,另一种是直接使用该属性。
默认情况下,当有一个资源被应用到多处时使用的都是一个对象实例,这就是共享资源。如果希望应用程序在应用资源的每处都有一个不同的对象实例,可以在资源中标记x:Shared = "False"。
当资源越来越多的时候,会引发一个问题,就是文件越来越长。WPF中提供一种ResourceDictionary(资源字典)类型的XAML文件用于组织资源。