WPF,Silverlight与XAML读书笔记第十七 资源之逻辑资源

说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。

 

    逻辑资源是相对于二进制文件而言,其定义于XAML,但同样可在过程代码中创建与使用逻辑资源。

    本质上逻辑资源是存储于元素的Resource属性(System.Windows.ResourceDictionary类型)的.NET对象。这个属性定义于FrameworkElement与FrameworkContentElement,所以大部分WPF元素中都可以找到这个属性。逻辑资源通常是样式或数据提供程序。

在介绍逻辑资源的使用方法与优势之前,首先回顾下ASP.NET模型中样式的定义方式。在ASP.NET中,我们将服务器控件的样式定义于一个Skin文件中,并需要设置这个样式的控件中通过SinkID引用这个样式。

WPF逻辑资源的工作方式类似,其将一系列可以被应用于多个元素的资源(如样式)保存于这些元素的容器,如Window的Resource属性,或者如果逻辑资源将在整个应用程序中可用时,可以将其保存于Application的Resource属性。先通过一个示例纵览一下逻辑资源(以样式为例)的设置与使用。

XAML:

 1 <Window.Resources> 
 2 <SolidColorBrush x:Key="bgBrush">Yellow</SolidColorBrush> 
 3 <SolidColorBrush x:Key="btnBgBrush">Green</SolidColorBrush> 
 4 <SolidColorBrush x:Key="borderBrush">Red</SolidColorBrush> 
 5 </Window.Resources> 
 6 <Window.Background> 
 7 <StaticResource ResourceKey="bgBrush"/> 
 8 </Window.Background> 
 9 <Grid> 
10 <Button Background="{StaticResource btnBgBrush}" BorderBrush="{StaticResource borderBrush}" Width=" 66" Height="36" > 
11 按钮!
12 </Button> 
13 </Grid>

代码中斜体为定义逻辑资源(样式)的语法,粗体为使用逻辑资源(样式)的语法。效果图如下:

上述代码中,你可以看出应用逻辑资源需要使用StaticResource这个标记扩展(System.Window.StaticResource)。使用标记扩展有两种方式:

  1. 可以将扩展标记作为属性元素(如代码中Window的Background的设置)。
  2. 可以使用定位参数方式的标记扩展语法来设置,如Button的Background与BorderBrush的设置。

使用资源的一个好处是,可以在任意时刻统一替换掉资源,如将上述例子中的bgBrush重新定义如下:

1 <LinearGradientBrush x:Key="bgBrush" StartPoint="0,0" EndPoint="1,1"> 
2 <GradientStop Color="Blue" Offset="0"/> 
3 <GradientStop Color="White" Offset="0.5"/> 
4 <GradientStop Color="Red" Offset="1"/> 
5 </LinearGradientBrush>

新的效果如下:

资源查找:

    StaticResource标记扩展接受的参数是资源字典(ResourceDictionary的对象),这个资源字典可以是位于当前元素,也可以是位于父元素,甚至是应用程序级的Resource属性中。

    StaticResource标记扩展类,可以向上遍历逻辑树查找资源,当找不到时会抛出InvalidOperationException异常。另外每个独立的ResourceDictionary中的键名不能重复,但多个不同的ResourceDictionary中的键名可以相同,由于采用向上遍历的策略,同名资源中最近的将被采用。

静态资源与动态资源

    静态访问资源的方式使用前文所示的StaticResource这个标记扩展来提供。这种资源仅在第一次需要加载资源时加载。

    动态加载资源的方式由DynamicResource这个标记扩展来实现,这个标记扩展与StaticResource在使用方式上(包括其遍历元素树的方式上)都相同。最大的不同是DynamicResource会跟踪资源的变换,并在资源变化后重新加载资源。

    可以将动态资源与静态资源结合使用,即给一个资源固定一个名称,如果需要使用静态方式引用就使用StaticResource调用,反之就使用DynamicResource来调用这个资源对象。

下面具体分析下两者的区别:

最主要的区别就是,动态资源可以在资源变化后自动更新,而使用静态资源时,如果资源发生变化则需要手工编码来更新。而这就导致了一些的不同。

    首先由于动态资源需要跟踪变化,动态资源需要占用更多的资源。

另一方面动态资源可以改善加载时间,因为这种引用到实际使用时才会发生。而对静态资源的引用总是发生在Window或Page加载之后。

另外,动态资源只能用于设置依赖属性,而静态资源可以用于任何地方(不单是设置属性)。例如:静态资源可以当作元素来使用(见下面示例)

1 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
2  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
3 <Image Height="20" Width="36" Source="Logo.png" /> 
4 </Window>

如果将以上代码用静态资源的方式实现代码如下:

1 <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
2  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
3 <Window.Resources> 
4 <Image x:Key="logo" Source="Logo.png" /> 
5 </Window.Resources> 
6 <StaticResource ResourceKey="logo" /> 
7 </Window>

如上代码中的粗体部分就是作为元素的静态资源。

注意:不能把一个对象用于多个资源中。

    静态资源访问不同于动态资源访问的另一个方面是StaticResource不支持向前引用,也就是说任何资源必须在XAML中声明后才可以使用。所以,当资源与资源访问定义于同一个元素中时,不能使用StaticResource这个标记扩展来引用资源。其原因是当这两者共存于一个元素中时,作为属性元素方式定义的资源需要放在后面,(而StaticResource这个标记会在前面),见下面这段代码(其是错误的!错误提示即为:未找到 StaticResource 引用"bgBtn"):

1 <Button x:Name="Test" Background="{StaticResource bgBtn}"> 
2 <Button.Resources> 
3 <SolidColorBrush x:Key="bgBtn">Yellow</SolidColorBrush> 
4 </Button.Resources> 
5 </Button>

而DynamicResource这个标记扩展不存在这个问题,所以正确的代码如下(注意粗体部分):

1 <Button x:Name="Test" Background="{DynamicResource bgBtn}"> 
2 <Button.Resources> 
3 <SolidColorBrush x:Key="bgBtn">Yellow</SolidColorBrush> 
4 </Button.Resources> 
5 </Button>

资源XAML文件的组合

    逻辑资源可以很方便的嵌入普通的XAML文件中,但是为了更好地组织与管理逻辑资源,可以将它们组织到各个独立的XAML文件中。这样就需要通过一定的方式在一般XAML中引用这些资源XAML 文件,方法就是使用ResourceDictionary类的MergedDictionaries属性来完成。下列代码很好的阐述了这个问题:

1 <Window.Resources> 
2 <ResourceDictionary> 
3 <ResourceDictionary.MergedDictionaries> 
4 <ResourceDictionary Source="file1.xaml" /> 
5 <ResourceDictionary Source="file2.xaml" /> 
6 </ResourceDictionary.MergedDictionaries> 
7 </ResourceDictionary> 
8 </Window.Resources>

这样就可以将file1.xaml与file2.xaml文件中的资源引用添加到当前xaml文件中。如果当这两个文件中资源的key重复时,后添加的资源会覆盖先添加的资源。

而这个单独的file1.xaml文件格式也有要求,其根元素必须为ResourceDictionary,一个模板可以参见下方:

1 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
2  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
3 <Image x:Key="logo" Source="logo.jpg" /> 
4 </ResourceDictionary>

另一种将XAML放入多个文件的方式是使用自定义控件

资源的共享

    当一个资源被应用到多个地方时,默认使用这个资源对象的同一个实例。将ResourceDictionary中项的x:Shared属性设置为"False"时,在每个使用这个资源的地方都会使用一个不同的资源对象实例,而且可以独立修改每个资源对象。

使用程序代码定义与应用资源

    前文所述的资源的定义与使用都是使用XAML来完成的。下面来说明一下如何使用C#代码完成同样的工作。

使用C#定义资源:

1 mainwindow.Resources.Add("bgBrush", new SolidColorBrush(Colors.Yellow)); 
2 mainwindow.Resources.Add("borderBrush", new SolidColorBrush(Colors.Red));

而对于资源的使用,C#代码就与XAML有较大不同,因为XAML使用了标记扩展而C#没有这种特性。

对于静态资源访问方式,需要通过调用相应元素(需要是从FrameworkElement或FrameworkContentElement继承而来的,因为下面介绍的方法继承自这些基类)的FindResource方法的返回值来取得资源。所以对于下面的XAML代码:

1 <Button Background="{StaticResource bgBrush}" BorderBrush="{StaticResource borderBrush}" />

等价的C#代码为:

1 Button button = new Button(); 
2 button.Background = (Brush)button.FindResource("bgBrush"); 
3 button.BorderBrush = (Brush)button.FindResource("borderBrush");

在FindResource找不到资源时会抛出一个异常,另外有一个TryFindResource方法,在找不到资源时将返回null而不是抛出异常。

对于动态资源访问方式,需要通过相应元素(需要是从FrameworkElement或FrameworkContentElement继承而来的,因为下面介绍的方法继承自这些基类)的SetResourceReference方法来设置资源到相应元素的属性。对于下面的XAML代码:

1 <Button Background="{DynamicResource bgBrush}" BorderBrush="{DynamicResource borderBrush}" />

同样的等价C#代码:

1 Button button = new Button(); 
2 button.SetResourceReference(Button.BackgroundProperty, "bgBrush"); 
3 button.SetResourceReference(Button.BorderBrushProperty, "borderBrush");

前面介绍的StaticResource不能向前引用的规则在程序代码里同样适用。即把资源添加到一个合适的资源字典(ResourceDictionary)之前,调用FindResource或TryFindResource会失败。而调用SetResourceReference可以正常执行。

提示:在代码中直接访问资源

由于资源是一个字典类的集合,所以也可以直接使用字典的方式来访问资源对象。如下代码示例:

1 Button button = new Button(); 
2 button.Background = (Brush)mainwindow.Resources["bgBrush"]; 
3 button.BorderBrush = (Brush)mainwindow.Resources["borderBrush"];

这种直接访问资源字典的方式,可以在一定程度上提高程序的性能,虽然可能会带来一定的负面影响。

访问另一个程序集的逻辑资源

    在上篇文章中我们知道可以访问另一个程序集中的二进制资源,同样也可以访问另一个程序集中的逻辑资源。访问的方式是通过名为ComponentResourceKey的标记扩展。要使用ComponentResourceKey,每个资源都必须有一个键名,这个键名是ComponentResourceKey的实例。下面给出一个示例:

以下代码是定义于程序集A的资源:

1 <SolidColorBrush 
2  x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:MyClass}, 
3  ResourceId=MyClassBrush}">Yellow</SolidColorBrush>

以下是在程序集B中使用这个资源:

1 <Button Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=otherAssembly:MyClass, ResourceId=MyClassBrush}}" />

    往往在定义资源的程序集中(上例中为程序集A),会通过一个属性将ComponentResourceKey暴露给使用者(即程序集B)。继续上面的例子:

程序集A中提供的属性:

1 public object MyClassBrushKey 
2 {
3 get { return new ComponentResourceKey(this.GetType(), "MyClassBrush"); } 
4 }

这样在程序集B中可以这样使用:

1 <Button Background="{DynamicResource {x:Static otherAssembly:MyClass.MyClassBrushKey}}" />
 

访问系统资源

System.Windows命名空间中有3个封装了系统设置的类:SystemColors、SystemFonts和SystemParameters。这三个类非常适合使用DynamicResource来引用,因为用户可以非常方便的通过更改控制面板来影响运行中的程序。使用动态资源,程序就可以随之发生变化。使用这个系统资源有三种方式:

XAML:

1 <Button Background="{x:Static SystemColors.WindowBrush}" />

C#:

1 Button b = new Button(); 
2 b.Background = SystemColors.WindowBrush;

XAML:

1 <Button Background="{StaticResource {x:Static SystemColors.WindowBrush}}" />

C#:

1 Button b = new Button(); 
2 b.Background = (Brush)FindResource(SystemColors.WindowBrush);

XAML:

1 <Button Background="{DynamicResource {x:Static SystemColors.WindowBrush}}" />

C#:

1 Button b = new Button();
2 SetResourceReference(Button.BackgroundProperty, SystemColors.WindowBrush);

其中最后一种动态引用方式程序可以随系统设置变化而改变,前两种不会改变。

本文完

参考:

《WPF揭秘》

原文地址:https://www.cnblogs.com/lsxqw2004/p/4616738.html