您的位置:首页 > 其它

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

2012-01-02 17:56 507 查看

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



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

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

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

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

XAML:

<Window.Resources>
<SolidColorBrush x:Key="bgBrush">Yellow</SolidColorBrush>
<SolidColorBrush x:Key="btnBgBrush">Green</SolidColorBrush>
<SolidColorBrush x:Key="borderBrush">Red</SolidColorBrush>
</Window.Resources>
<Window.Background>
<StaticResource ResourceKey="bgBrush"/>
</Window.Background>
<Grid>
<Button Background="{StaticResource btnBgBrush}" BorderBrush="{StaticResource borderBrush}" Width=" 66" Height="36" >
按钮!
</Button>
</Grid>


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



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

可以将扩展标记作为属性元素(如代码中Window的Background的设置)。

可以使用定位参数方式的标记扩展语法来设置,如Button的Background与BorderBrush的设置。

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

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


新的效果如下:



资源查找:

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

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

静态资源与动态资源

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

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

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

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

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

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

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

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

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


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

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<Image x:Key="logo" Source="Logo.png" />
</Window.Resources>
<StaticResource ResourceKey="logo" />
</Window>


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


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



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

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


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

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


资源XAML文件的组合

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

<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="file1.xaml" />
<ResourceDictionary Source="file2.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>


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

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

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


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

资源的共享

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

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

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

使用C#定义资源:

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


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

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

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


等价的C#代码为:

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


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

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

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


同样的等价C#代码:

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


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


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

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

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


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



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

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

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

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


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

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


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

程序集A中提供的属性:

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


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

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


访问系统资源

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

XAML:

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


C#:

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


XAML:

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


C#:

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


XAML:

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


C#:

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


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

本文完

参考:

《WPF揭秘》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐