当前位置: 代码迷 >> 综合 >> 资源 - 《Windows Presentation Foundation 程序设计指南》 - 免费试读 - book.csdn.net
  详细解决方案

资源 - 《Windows Presentation Foundation 程序设计指南》 - 免费试读 - book.csdn.net

热度:50   发布时间:2024-01-18 17:33:40.0
第21章 资源
假设你正为一个窗口或对话框编写XAML,而且你决定你要为各种控件使用两个不同的font size。在窗口内的某些控件会有较大的font size,某些则会取得较小的font size。你大概知道哪个控件会取得哪种font size,但是你不太确定实际的font size会是多少。或许你想要在决定值之前先实验一下。
天真的做法是将FontSize的值硬编码在XAML内,像这样:
FontSize="14pt"
如果你稍后决定想要更大或更小的值,你可以用“查找并替代”的功能。虽然“查找并替代”的方法对于小项目可行,但是身为编程员,你一定知道这不是通用的好方法。假设你正处理复杂的渐变画刷,而不是简单的字体尺寸,你可能一开始会复制和粘贴渐变画刷,遍布整个程序。但是如果你需要改变此画刷,你需要改的地方可就相当多了。
如果你是在C# 中面对此问题,你不会用复制渐变画刷的方式,或者硬编码font size的方式。你会定义变量,或者(为了清楚地表达意图与提高效率)你可以在窗口类中定义一些常数字段:
const double fontsizeLarge = 14 / 0.75;
const double fontsizeSmall = 11 / 0.75;
你也可以改将它们定义成静态的只读值:
static readonly double fontsizeLarge = 14 / 0.75;
static readonly double fontsizeSmall = 11 / 0.75;
不同之处在于,常数是在编译期间计算的,并且在编译期间做值的替代,而静态变量是在运行期间计算的。
在编程语言中,此技巧实在太常用,也太有用,如果在XAML中也有类似的用法,会相当有价值。幸运的是,真的有。你可以先将对象定义成资源,然后就可以在XAML中复用它们。
本章所要讨论的“资源”(resource)和本书之前所提到的资源,差异相当大。我之前向你展示过如何使用Microsoft Visual Studio,来指示工程中某些文件要被编译成资源(Build Action设定为Resource)。这些资源更正确的称呼方式是“组件资源”(assembly resource)。常常,组件资源是二进制文件(binary file),像是icon和位图。但是在第19章,我也向你展示过如何对XML文件使用此技术。这些组件资源被储存在组件中(EXE或DLL),并且可以利用Uri对象来存取。
本章的资源,有时候被称为“局部定义的资源”(locally defined resource),因为它们是定义在XAML中(有时候是在C# 程序代码中),而且它们通常会和应用程序中的某element、控件、页面或窗口有关联。对于资源来说,只有在定义此资源的element内,以及在该element的孩子内,此资源才是可用的。你可以把这种资源想成是XAML中用来“弥补不具有C# 静态只读字段”的替代品。就和静态只读字段一样,资源对象在运行时被建立一次,而且被引用它们的element所共享。
所有的资源储存在一个ResourceDictionary类型的对象中,而且3个非常基本的类(FrameworkElement、FrameworkContentElement、Application)都定义了一个property,名为Resources,类型为ResourceDictionary。ResourceDictionary对象内的每个项目都具有一个key,用来识别该对象。通常这些key只是文字字符串。为了定义资源的key,XAML定义了一个x:Key attribute。
继承自FrameworkElement的element可以有一个Resources collection。此Resources section几乎总是以property element的语法定义在此element的最前面:


...

...

定义在此Resources section内的资源,可以在整个StackPanel内使用,也可以被StackPanel的任何孩子所使用。Resources section内的每个资源都具有下面的形式:

...

你可以用attribute语法或property element语法,设定该对象的property。XAML element然后可以利用“标记扩充”(markup extension),引用到“某个key对应的资源”。顾名思义,markup extension是特别的关键词,是XAML专用的。搭配资源所使用的markup extension,名为StaticResource。
本章一开始描述的问题牵涉到两个不同的font size。下面的独立XAML文件展示了如何在一个StackPanel的Resources collection内定义两个font-size资源,然后StackPanel的child element如何存取这些资源。
FontSizeResources.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib">


18.7


14.7





注意StackPanel element tag定义了一个XML命名空间的前缀s,代表System命名空间(clr-namespace:System;assembly=mscorlib),这允许引用此Resources collection中的Double结构。
StackPanel的Resources section包含两个Double对象的定义,它们的key分别是“fontsizeLarge”和“fontsizeSmall”。在任何Resources dictionary内,key都必须独一无二,不能有相同的两个key。18.7和14.7的值等价于14 pt和11 pt。
此StackPanel或StackPanel的child element都可以使用这些资源,使用方式有两种,都牵涉到StaticResource markup extension。第一个Button使用property element语法,存取FontSize资源,使用一个StaticResource的element和一个ResourceKey的attribute,来表示此项目的key:



第二个Button的语法更常见。FontSize attribute被设定成一个字符串,将“StaticResource”和key的名字放在大括号(curly bracket)内:
FontSize="{StaticResource fontsizeSmall}"
仔细看看此语法,你将会在本章看到许多这样的语法;在第23章,当我开始讨论数据绑定时,此语法也会一再出现。大括号表示此表达式(expression)是在一个markup extension中。没有StaticResource类。然而,有一个类名为StaticResourceExtension,继承自MarkupExtension,而且具有ResourceKey property。StaticResource被归类为markup extension,因为它让我们可以在XAML内做某些“原本只有在编程语言中才有可能”的事。此StaticResourceExtension类负责根据指定的key提供dictionary内的对应值。
在本章中,你还会看到两个其他的markup extension,分别为x:Static与DynamicResource,它们也都会放在大括号内。此大括号用来告诉XAML解析器,“这里出现的是markup extension”。在此大括号内,不可以出现引号。
少数时候,你可能会需要在文字字符串内用到一些大括号,但这和markup extension无关:


为了让XAML解析器不要将它误认为名为just的markup extension,而开始做无谓的(而且会失败的)查找,我们在一组大括号前,插入一组空的大括号,作为escape sequence:


Resources section几乎总是定义在一个element的最顶端,因为任何资源必须在文件中被引用之前定义。对于资源来说,向前引用(forward reference,也就是引用时尚未定义)是不允许的。
虽然特定Resources collection内,所有的key都不能重复,但是相同的key可以出现在两个Resources collection内。当一个资源必须被定位时,会先从element所引用的Resources collection开始查找,然后继续沿着这个树状结构往上找,直到找到此key为止。下面的独立XAML文件展示了这一过程:
ResourceLookupDemo.xaml


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













这里定义了3个StackPanel element。第一个是水平方向;另外两个StackPanel elements是第一个的孩子。为了简单起见,这两个StackPanel孩子都只包含一个按钮。
作为父亲的StackPanel具有一个Resources collection,此collection含有蓝色的SolidColorBrush(其key是“brushText”)。此StackPanel的第一个孩子也具有Resources collection,含有另一个SolidColorBrush对象,其key也是“brushText”,但颜色是红色。两个按钮都用key为“brushText”的StaticResource extension来设定Foreground property。第一个按钮(在红色画刷的StackPanel内)具有红色的文字,第二个按钮在不具有“brushText”资源的StackPanel内,所以“brushText”资源会用父亲StackPanel的,也就是蓝色。
定义具有相同名称的资源,是一个很有威力的技巧,特别是对于style来说(第24章的主题正是style)。Style让你可以定义property,适合多个element使用,甚至可以定义这些element如何对特定的事件和property的改变产生反应。在真实的WPF程序中,大多数的Resources collections都是用来定义style或改变style的定义。因为style是如此地重要,我认为最好先介绍资源,让你对style底层的技术建立良好的基础。记得一件事,如果本章似乎缺少什么(主要是,使用资源为特定的element和控件定义许多property),这些内容都会在第24章补齐。
资源是共享的,每个资源只需要建立一个对象。如果该资源没有被引用到,甚至不会建立对象。
你可能会怀疑,“我可以将一个element或控件定义成资源吗?”是的,你可以。比方说,你可以将下面的内容包含在ResourceLookupDemo.xaml的Resources section:

然后你可以使用下面的语法,将Button作为“父亲StackPanel”的孩子(或者“某个孩子StackPanel”的孩子,这取决于你将此资源定义在何处):

这样是行得通的,但是你不能做两次。此Button对象被当作资源建立,只是一个对象,如果该Button是一个面板的孩子,就不可以是同一个面板的另一个孩子,或者另一个面板的孩子。请注意,当你在StaticResource element中引用此Button时,你无法改变此Button。将Button变成资源,你其实没有因此得到什么好处。那又何必这么做呢?
如果你认为你需要将控件和其他的element定义成资源,你可能真正需要的是,使用资源来定义此element的某些property,而非全部的property。很有可能你真正需要的是style,你应该去看看第24章。
虽然,资源几乎总是定义在XAML中,而非定义在程序代码中,你还是可以用C# 程序代码,将对象加入一个element的Resources collection中:
stack.Resources.Add("brushText", new SolidColorBrush(Colors.Blue));
显然,资源只是一个可以在多个element或控件之间共享的对象。此Add方法是由ResourceDictionary类所定义的,第一个参数是key,类型是object,但是最常用的是字符串。主要的3个定义了Resources collection的类(FrameworkElement、Framework- ContentElement、Application)也都定义了一个方法,名为FindResource,用来找出特定key的资源。StaticResourceExtension正是使用此方法来找出资源的。
有趣的是,调用某个element的FindResource方法,可能会从此element的Resources collection中找到资源,但是不会就停在此element。它也可能从此element的祖先(element tree中的祖先)找到资源。我想,FindResource是像这样实现的:
public object FindResource(object key)
{
object obj = Resources[key];
if (obj != null)
return obj;
if (Parent != null)
return Parent.FindResource(key);
return Application.Current.FindResource(key);
}
能够递归地(recursive)查找element tree,正是FindResource有价值的原因,因为直接使用key对Resources property进行索引,是做不到这一点的。也请注意,当element tree已经完全查找过,FindResource还会检查Application的Resources dictionary。你可以(而且应该)使用Application的Resources collection,来放置整个应用程序都可以使用的设定、style和主题(theme)。
一直到现在之前,我都建议你在Visual Studio中建立空的工程,这样你可以对WPF本身具有更好的理解,而不会为Visual Studio提供的一切所分心。现在你已经知道资源了,让Visual Studio帮我们处理编程风格(programming style),应该不会有问题了,至少我们可以试验性地做做看。
让我们使用Visual Studio来建立一个“Windows Presentation Foundation Application”工程,并且将名称指定为GradientBrushResourceDemo。Visual Studio会建立一个名为MyApp.xaml的文件,其Resources section已经有定义了,等着你输入:
MyApp.xaml

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



这就是为什么应用程序的Resources section被认为在WPF编程中相当重要的原因!让我们使用该Resources section来定义一个适用于整个应用程序的渐变画刷,现在MyApp.xaml变成这样:
MyApp.xaml

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


StartPoint="0, 0"
EndPoint="1, 1">








Visual Studio也为MyApp.xaml建立一个MyApp.xaml.cs code-behind文件,但是此文件做的事不多。Visual Studio所建立的Window1.xaml文件,默认定义了一个Window element和一个Grid。
Window1.xaml

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GradientBrushResourceDemo" Height="300" Width="300"
>



此原始的Window1.xaml.cs code-behind文件只是去调用InitializeComponent。
Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace GradientBrushResourceDemo
{
///
/// Interaction logic for Window1.xaml
///

public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
}
对此程序代码,我使用一行C#语句加了一个资源到该窗口。
Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace GradientBrushResourceDemo
{
///
/// Interaction logic for Window1.xaml
///

public partial class Window1 : Window
{
public Window1()
{
Resources.Add("thicknessMargin", new Thickness(24, 12, 24, 23));
InitializeComponent();
}
}
}
在Window1.xaml文件中,StackPanel可以替代Grid,然后4个TextBlock element可以使用LinearGradientBrush资源与Thickness资源。
Window1.xaml

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GradientBrushResourceDemo" Height="300" Width="300"
>


Foreground="{StaticResource brushGradient}">
Gradient text


Foreground="{StaticResource brushGradient}">
Of black, green, and gold


Foreground="{StaticResource brushGradient}">
Makes an app pretty,


Foreground="{StaticResource brushGradient}">
Makes an app bold.



我不喜欢使用Visual Studio预先构建的工程来建立我书上的例子,这一点都不是秘密。其中有一个麻烦是,我觉得有必要为一切改名,好让我的文件名不是MyApp和Window1。但是现在你已经看到此Application.Resource tag意味着什么,以及如何使用它,如果你想要使用Visual Studio工程和XAML 设计工具,应该不会有问题了。
在本章一开始,我描述了如何在程序中将两个不同的font size定义为静态只读字段。有趣的是,XAML定义一个markup extension,名为x:Static,专门用来引用静态的property或字段,也适合用于枚举成员。
比方说,假设你想要将一个Button的Content property设定成SomeClass类的静态property,名为SomeStaticProp。此markup extension语法是:
Content="{x:Static SomeClass:SomeStaticProp}"
或者,你可以在property element语法内使用一个x:Static element:



静态字段或property的类型应该要符合你正在设定的property类型,或者可以被转成该类型。(当然,对object类型的Content property来说,任何东西都行)。比方说,如果你想要让特定element具有和“标题栏”相同的高度,你可以这么写:
Height="{x:Static SystemParameters.CaptionHeight}"
你没有被限制为只能使用WPF定义的静态property或字段,但是如果你存取非WPF类,你需要对类所在的CLR命名空间作XML命名空间的声明。
下面是一个独立的XAML程序,System命名空间被关联到XML前缀s。此程序显示Environment类的静态property的信息。
EnvironmentInfo.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib">


























此程序只想显示这些property的文字说明。其中大多数的property都是返回字符串,但是有两个例外:OSVersion property(目前正在运行的Microsoft Windows版本)类型为OperatingSystem ;Version property(.NET的版本)类型为Version。幸好,这两个类的ToString方法会将信息格式化成适合阅读的文字。
这两个x:Static标记表达式(markup expression)不能简单地设定给TextBlock的Text property(比方说)。没有自动的转换,可以将这些非字符串的对象转成字符串。取而代之,我将x:Static表达式设定给Label控件的Content property。此Content property可以被设定成任何对象,而且此对象将会通过其ToString方法显示出来。让这些Label element成为一个TextBlock的孩子(这么一来,它们会变成InlineUIContainer element的一部分),这允许在其中散布LineBreak element,以将输出分成多行。
虽然你可以在XAML Cruncher中运行此文件,但是不能在IE内运行。因为所有的项目(OSVersion和Version除外)都需要程序具有“安全权限”(security permission)才行,而在IE内运行XAML的做法,是没有这种安全权限的。
另一种使用x:Static的做法需要在你的C# 源代码中定义静态字段或property,然后从工程的XAML文件中存取它们。我将要展示的下一个工程名为AccessStaticFields。此工程包含一个XAML文件和一个C# 文件,它们都对应着相同的Window类,但是此工程也包含一个
C# 文件,名为Constants.cs,用来定义Constants类,此类具有3个静态只读的字段和property。这是一个全静态的类:
Constants.cs
//------------------------------------------
// Constants.cs (c) 2006 by Charles Petzold
//------------------------------------------
using System;
using System.Windows;
using System.Windows.Media;
namespace Petzold.AccessStaticFields
{
public static class Constants
{
// Public static members.
public static readonly FontFamily fntfam =
new FontFamily("Times New Roman Italic");
public static double FontSize
{
get { return 72 / 0.75; }
}
public static readonly LinearGradientBrush brush =
new LinearGradientBrush(Colors.LightGray, Colors.DarkGray,
new Point(0, 0), new Point(1, 1));
}
}
我定义这些项目的其中两个为静态字段,另一个为静态只读property。这只是为了有变化,对此例子来说其实效果都一样。(它们不需要是只读的,只是XAML文件无法设定这些字段,所以如果不让它们只读,也不会带来什么额外的好处。)因为这些静态字段和property都被定义成你的源代码的一部分,而不是属于标准的WPF组件,所以此XAML文件需要一个XML命名空间的声明来定义一个前缀(prefix),让此前缀和字段和property所属的类的命名空间产生关联。
下面的XAML文件,将XML前缀src和CLR命名空间Petzold.AccessStaticFields关联起来。此文件然后使用x:Static markup extension来存取静态字段和property。这些markup extension其中两个使用attribute语法,而第三个使用property element语法(纯粹只是为了有变化)。
AccessStaticFields.xaml


xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:Petzold.AccessStaticFields"
x:Class="Petzold.AccessStaticFields.AccessStaticFields"
Title="Access Static Fields"
SizeToContent="WidthAndHeight">

FontSize="{x:Static src:Constants.FontSize}"
TextAlignment="Center">



Properties from Static Fields


此code-behind文件没什么特别的。
AccessStaticFields.cs
//---------------------------------------------------
// AccessStaticFields.cs (c) 2006 by Charles Petzold
//---------------------------------------------------
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Petzold.AccessStaticFields
{
public partial class AccessStaticFields : Window
{
[STAThread]
public static void Main()
{
Application app = new Application();
app.Run(new AccessStaticFields());
}
public AccessStaticFields()
{
InitializeComponent();
}
}
}
当然,此静态字段和property不需要在单独的文件内。在此工程的早期版本,我将它们放在AccessStaticFields类的C# 中,并且让x:Static markup extension引用src:Access- StaticFields类,而不是src:Constants类。但是我比较喜欢准备一个全静态的类,专门用来放置应用程序会用到的所有常数。
现在你知道如何把对象定义成资源,并利用StaticResource markup extension引用这些对象。你也知道如何使用x:Static引用静态property和类字段。这里所缺乏的是引用某对象实例(instance)的property和字段的能力。这项工作需要指定对象和该对象的某property,而且语
法已经超过StaticResource与x:Static的能力。这是“数据绑定”(data binding)的工作,是第23章的内容。
下面是另一个x:Static的例子。此独立的XAML文件使用x:Static表达式来设定一个Label控件的Content与Foreground attribute。
DisplayCurrentDateTime.xaml







只有第一个内嵌的StackPanel含有Resources section,这里使用来自SystemColors. ActiveCaptionBrushKey的key,定义了一个红色的画刷。在StackPanel内的Button取得红色的画刷,但是其他的Button取得“active caption”画刷,而且当系统颜色改变时会跟着改变。
当你使用资源越来越多时,你可能会想要在多个应用程序之间共享资源。特别是,如果你开发了一个自定义style的collection,以让你公司的应用程序具有独特的外观与感觉时,尤其如此。
你想要在多个工程之间共享的资源,可以被集中在XAML文件中,其root element是ResourceDictionary。每个资源都是root element的一个孩子。下面是一个可能的resource dictionary,只有一个资源(不过,想要有多个资源也行)。
MyResources1.xaml


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







下面是另一个resource dictionary,可以包含许多资源,但是这里只包含一个资源:
MyResources2.xaml.


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







你现在正在准备一个名为UseCommonResources的工程,而且你想要使用MyResources1.xaml和MyResources2.xaml所定义的资源。你可以让这两个文件成为此项目的一部分,将“Build Action”设定成“Page”或“Resource”。(不过设定成Page比较好,因为某些初步的处理会发生在编译期,将此文件从XAML转成BAML。)在此工程的“应用程序定义文件”(application definition file)中,你可以加上一个Resources section,语法如下面的文件所示。
UseCommonResourcesApp.xaml


StartupUri="UseCommonResourcesWindow.xaml">









在文件的Resources section内,是一个ResourceDictionary element。Resource- Dictionary定义了一个名为MergedDictionaries的property,这是其他ResourceDictionary对象的collection,而这些对象是根据文件名来引用的。如果你只有一个resource dictionary,你可以从一个ResourceDictionary对象直接引用它,不需要使用ResourceDictionary. MergedDictionaries property element。
多个resource dictionary真的会被合并(merge)。如果你碰巧在多个文件中使用相同的key,那么当resource dictionary被合并时,早先出现的资源会被后来出现的相同key的资源替代。
除了可以将ResourceDictionary放在应用程序定义文件中,也可以放在某个XAML文件的Resources section中,但是这么做的话,该资源只能被该文件使用,无法在整个应用程序中使用。
最后,下面是Window element,它使用了定义在MyResources1.xaml与MyResources2.xaml文件内的资源。
UseCommonResourcesWindow.xaml


Title="Use Common Resources"
Background="{StaticResource brushLinear}">


接触像XAML这样的新语言,焦虑可能会伴随而来。这个语言真的定义得那么充分,让我们不会在路上跌得鼻青脸肿吗?很重要的是,程序代码要尽量少地重复,而资源可以帮助我们达到此目标。不只对象可以被定义一次,然后在整个应用程序中使用多次,资源也可以被存储在它们自己的ResourceDictionary文件内,然后让多个应用程序使用相同的资源。

本文转自
http://book.csdn.net/bookfiles/591/10059119380.shtml
  相关解决方案