关于Resources(学习)

资源是保存在可执行文件中的一种不可执行数据。通过资源我们可以包含图像、字符串等等几乎是任意类型的数据。如此重要的功能,.NET Framework当然也是支持的,其中内建有资源创建、定位、打包和部署的工具。在.NET中可以创建.resx.resources文件。其中.resxXML项组成。.resx只是一种中间格式,不能被应用程序直接使用,它必须用工具转换为.resource格式。

WPF中,资源的含义和处理方式与传统的Win32Windows Forms资源有所区别。首先,不需要创建.resx文件,只需要在工程中指出资源即可,其它所有的工作都由WPF完成。其次,WPF中的资源不再像.NET中有资源IDXAML中引用资源需要使用Uri。最后,在WPF的资源中,几乎可以包含所有的任意CLR对象,只要对象有一个默认的构造函数和独立的属性。在WPF本身的对象中,可以声明如下四种对象:StyleBrushesTemplatesDataSource

在定义具体的资源之前,我们先考虑如下几个相关的问题:

1、资源的有效范围:

WPF中,所有的框架级元素(FrameworkElement或者FrameworkContentElement)都有一个Resource属性。也就是说。我们可以在所有这类元素的Resource子元素中定义属性。在实践中,最常用的三种就是三种根元素所对应的资源:Application、Page和Window。顾名思义,在Application根元素下定义的资源将在当前整个应用程序中可见,都可以访问。在PageWindow中定义的元素只能在对应的PageWindow中才能访问。

2、资源加载形式:

WPF提供了两种资源类型:Static资源和Dynamic资源

两种的区别主要有两点:A)、Static资源在编译时决议,而Dynamic资源则是在运行时决议。B)、Static资源在第一次编译后即确定了相应的对象或者值。此后不能对其进行修改,即使修改成功也是没有任何意义的,因为其它使用资源的对象不会得到通知。Dynamic资源不同,它只有在运行过程中真正需要时,才会在资源目标中查找。所以我们可以动态的修改Dynamic资源。显而易见,Static资源的运行效率高

3、不管是Static资源还是Dynamic资源,所有的资源都需要设置Key属性x:Key=”KeyName”。因为WPF中的资源没有资源ID,需要通过资源Key来标识以方便以后访问资源。范围资源时我们根据资源的类型使用StaticResource或者DynamicResource标记扩展。

好了,对WPF中的资源所有了解后,我们看一些简单的例子:

 1 <Window
 2 
 3      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4 
 5      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 6 
 7 <StackPanel>
 8 
 9      <StackPanel.Resources>
10 
11            <SolidColorBrush x:Key="MyBrush" Color="gold"/>
12 
13      </StackPanel.Resources>  //我们在StackPanel元素的Resource子元素中定义了一个SolidColorBrush资源
14 
15      <TextBlock Foreground="{StaticResource MyBrush}" Text="Text"/>
16 
17 </StackPanel>
18 
19 </Window>

在这个例子中,我们在StackPanel元素的Resource子元素中定义了一个SolidColorBrush资源。然后在后面通过StaticResouce标记扩展,利用前面的x:Key属性访问定义好的资源。

资源除了可以在XAML声明外,还可以通过代码进行访问控制。支持Resource属性的对象都可以通过FindResource、以及Resource.AddResource.Remove进行控制:

<Window

     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Window.Resouce>

     <SolidColorBrush x:Key="MyBrush" Color="gold"/>

</Window.Resouce>

</Window>

我们先在代码XAMLWindow.Resource中定义了一个MyBrush。在代码中可以如下对其进行访问:

SolidColorBrush brush = this.FindResource("MyBrush") as SolidColorBrush;

如果需要进一步修改或者删除资源时,可如下编码:

this.Resouce.Remove(“MyBrush”);      //删除MyBrush资源

this.Resouce.Add(“MyBrush”);         //重新动态添加资源

说明:以上三处的this引用都是特指我们定义MyBrush的元素Window。读者朋友可根据实际情况修改。

 

 

在本系列的之十三中简单介绍了WPF中资源的资源。但是,没有给出任何具体的实例,在这个Post中将给出一个动态资源的例子,也算是响应daxian110的请求。并适当的扩展在前一个Post当中没有涉及的知识。

我们先看一个例子程序:

 1 <Window x:Class="WindowsApplication1.Window1"
 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4     Title="WindowsApplication1" Height="150" Width="100" Loaded="OnLoaded"
 5     >
 6     <Canvas>
 7       <Button Click="OnClick" Canvas.Left="10" Canvas.Top="20"
 8               Width="80" Height="30" Content="{DynamicResource TestRes1}"/>
 9       <Button Canvas.Left="10" Canvas.Top="60" Width="80"
10               Height="30" Content="{DynamicResource TestRes2}"/>
11     </Canvas>
12 </Window>

程序很简单,在窗口中添加了两个按钮,我们需要关注的是这个ContentControl控件(实际就是Button)其中的Content属性。我们在Content属性中可以包含其它任何你想显示的内容。不止是字符串文本。这种抽象的处理使我们可以把所有的内容等同对待,减少了很多处理上的麻 烦。在本例子中,Content属性被和一个TestRes1和TestRes2关联起来。这个TestRes到底是什么呢?这就是动态资源的名称。具体的内容在显示按钮的时候决定。

注意上面Window中的Loaded属性,通过它我们可以设置一个函数名称,它将Window加载完成后被调用。下面就看看如何用代码控制TestRes:

private void OnLoaded(object sender, RoutedEventArgs e)
{
      string szText1 = "Res Text1";
      this.Resources.Add("TestRes1", szText1);

      string szText2 = "Res Text2";
      this.Resources.Add("TestRes2", szText2);
}

OnLoaded是Window1类中的一个成员函数,在这个函数里,我们需要添加资源,因为我们的XAML中需要使用TestRes1和TestRes2,运行时如果找不到对应资源,程序将失败。

现在,我们调用Add方法添加资源。第一个参数是资源的名称,第二个参数是添加的资源对象

程序的运行效果如图1:

                  
      图1                                                图2

接下来我们看看修改资源的方法。在上面XAML的第一个按钮的Click属性中我们指定了一个OnClick事件方法。它将在点击按钮时调用,现在我们通过这个事件来修改另一个按钮的Content资源:

private void OnClick(object sender, RoutedEventArgs e)
{
      string szText = "New Res Text";
      this.Resources.Remove("TestRes2");
      this.Resources.Add("TestRes2", szText);
}

OnLoaded实现同样的简单,先调用Remove方法删除已有的TestRes2资源,然后重新添加一个新的TestRes2资源对象。点击第一个按钮后,下面按钮的文本将自动修改为新的资源对象。运行效果如图2。 

XAML加载器在分析XAML文件时,发现StaticResource,将会在当前Element的资源中查找指定的Key,如果查找失败,将沿 着逻辑树向上查找,直到Root元素。如果还没有找到资源,再查找Application下定义的资源。在Application中定义的资源适用于整个 应用程序。类似于全局对象。注意:使用Static资源时,不能向前引用。即使偶尔程序运行成功,向前引用的效率将非常低,因为它需要查找所有的 ResourceDictionay。对于这种情况,使用DynamicResource将更适合。

另一方面,XAML加载器发现DynamicResource时,将根据当前的属性设置创建一个表达式,直到运行过程中资源需要,才根据表达式从资 源中查找相关内容进行计算,返回所需的对象。注意,DynamicResource的查找于StaticResource基本类似,除了在定义了 Style和Template时,会多一个查找目标。具体的细节可参数MSDN。

 

 


继续相同的话题:WPF中的资源。这次我将尝试从另外一个角度来分析WPF中的资源:资源编译行为,以及如何根据应用程序的需要选择适当的类型。

首先建立一个默认的WPF工程,然后向工程中添加一个ICON资源。在添加资源后,我们可以选择资源的类型,如下图所示:

 



从图中的下拉列表我们可以看到资源所支持的各种类型。主要支持的编译行为是ResourceContent。如果选择为Resource,再用文本方式打开C#工程文件(*.csproj文件),其中我们为发现如下的内容:
  • 如果选择为Resource,看到的资源项内容应该是:

<ItemGroup>

    <Resource Include="WTL2007.ico" />

</ItemGroup>

  • 如果选择为Content,看到的资源项内容应该是:

<ItemGroup>

    <Content Include="WTL2007.ico" />

</ItemGroup>

那么,这两者之间有什么区别呢?我们先看Resource类型。如果某个文件在工程文本中被标识为Resource这个文件将被嵌入到应用程序所在的Assembly

工程编译后,工程中的所有指定资源文件一起创建一个.resources文件。对于本地化应用程序,将加载对应的卫星Assembly

如果文件标识为Content,并且将CopyToOutputDirectory设置为Always或者PerserveNewest。这个文件被拷贝到编译输出目录与应用程序Assembly一起。

<Content Include="WTL2007.ico">

     <CopyToOutputDirectory>Always</CopyToOutputDirectory>

</Content>

编译时,标识为Content的文件都会被创建一个文件映射关系。运行时,根据指定的UriWPF的加载机制将根据实际情况加载资源。

不管我们所引用的类型是Resource还是Content,在代码中,我们可以通过简单的相关Uri来访问这些资源:

<Object Property=”Ico/WTL2007.ico”/>

下面有几个比较好的建议,可以帮助我们选择资源的编译Action。对于下面的这些需求,应该选择Resource

1、文件是本地化文件

2、应用程序部署后不再希望文件被修改

3如果不希望对文件进行单独的部署或者管理,移动应用程序时不必担心资源的位置

对于下面的一些资源文件需求,我们应该选择Content

1、文件不是本地化文件

2希望文件在部署后可以被替换

3、希望文件可以被下载更新等等(注意不能是包含在应用程序Assembly)。

4、某些不能被设置为Resource类型的文件,否则WPF不能识别。
 
 

前一个Post当中,我从资源编译行为的角度讨论了WPF中的资源。但是,不管是Resource还是Content都是在编译时声明资源。如果我们打破这个限制,不希望指定完全确认的资源地址。WPF提供了一种类似IE地址定位的抽象,它根据应用程序部署的位置决议。

WPF将应用程序的起源地点进行概念上的抽象。如果我们的应用程序位于http://yilinglai.cnblogs.com/testdir/test.application。我们应用程序的起源地点http://yilinglai.cnblogs.com/testdir/,那么我们就可以在应用程序中这样指定资源位置:

<Image Source=”pack://siteoforigin:,,,/Images/Test.JPG”/>

通过这种包装的Uri,使用资源的引用更加灵活。那么,这种类似Internet应用程序的资源包装Uri指定方式有什么优点呢?

1)、应用程序Assembly建立后,文件也可被替代。

2)、可以使文件只在需要使才被下载。

3)、编译应用程序时,我们不需要知道文件的内容(或者文件根本不存在)。

4)、某些文件如果被嵌入到应用程序的Assembly后,WPF将不能加载。比如Frame中的HTML内容,Media文件。

这里的pack://其实是一种URIUniform Resource Identifiers)语法格式。pack://<authority><absolute_path>,其中的authority部分是一个内嵌的URI。注意这个URI也是遵守RFC 2396文档声明的。由于它是被嵌入到URI当中,因此一些保留字符必须被忽略。在我们前面的例子中,斜线(”/”)被逗号(”,”)代替。其它的字符如”%””?”都必须忽略。

前面例子中的siteoforigin可以理解为一种authority的特例。WPF利用它抽象了部署应用程序的原始站点。比如我们的应用程序在C:\App,而在相同目录下有一个Test.JPG文件,访问这个文件我们可以用硬编码URI file:///c:/App/Test.JPG。另外一种方法就是这种抽象性:pack://siteoforigin:,,/Test.JPG。这种访问方法的便利是不言而喻的!在XPS文档规范中,对URI有更好的说明。有兴趣朋友可以在此下载

也许你看到现在对此的理解有些问题。不用太着急,随着你对WPF越来越熟悉,会有更多的体会。对于WPF的新手(我也是),对于此点不必过度纠缠。因为WPFApplication类中提供了一些有用的方法:

Application.GetResourceStream (Uri relativeUri);

Application.GetContentStream(Uri relativeUri);

Application.GetRemoteStream (Uri relativeUri);

通过使用这些函数,隐藏了URI定位的细节。从这些函数的名称我们可以看出,它们分别对应于我在前面介绍的三种类型:ContentResourceSiteofOrigin

最后,简单的说明一下另一种使用资源的方式,直接定义资源,不使用任何的属性,具体的用法看例子就明白了:

<StackPanel Name="sp1">
    <StackPanel.Resources>
      <Ellipse x:Key="It1" Fill="Red" Width="100" Height="50"/>
      <Ellipse x:Key="It2" Fill="Blue" Width="200" Height="100"/>
    </StackPanel.Resources>
    <StaticResource ResourceKey="It1" />
    <StaticResource ResourceKey="It2" />
</StackPanel>
posted @ 2012-10-02 04:01  若愚Shawn  阅读(767)  评论(0编辑  收藏  举报