《Programming WPF》翻译 第6章 资源

6资源(Resource

WPF为我们架构一个应用程序的用户界面提供了相当大的弹性。但是,功能越强大,需要注意的就越多。我们必须避免使用户迷失在不协调而且过分装饰的前端。一个应用程序的用户界面应该是内在协调的,而且,应用程序之间不光区别在视觉化上,更多的是,对于用户所选择的操作系统的视觉主题,在感观上都是一致的。

在上章,我们已经看到样式和模板是如何允许我们控制应用程序的外观。这些都是基于WPF的资源(Resource),从而使得创建一个在视觉上一致得应用程序更加容易,而且这样做不会牺牲弹性。如果你想要创建一个图形化的有特色的应用程序,资源体系提供一个直接的方式,将你的应用程序“包装”成自定义的一致性的外观。除此之外,默认地,资源机制简单地确保了一致性,而不管用户选择的是什么样的操作系统的视觉主题。

在这一章,我们将要研究如何将Resource插入到外观样式中。如何在运行期确保正确的外观应用到我们的应用程序中,如何复用一个或多个Resource在应用程序的多个地方。还有,如何使用资源这个工具管理二进制流以及如何本地化我们的应用程序。

6.1 创建和使用资源

资源这个词具有非常广泛的意义。任何对象都可以是一个资源。一个在用户界面中经常使用的Brush或者Color可以是一个资源。一段文本或者一个图形也可以是一个资源。没有什么特殊的对象不可以成为一个资源。资源的底层处理机制确保了获取你所需要的资源成为可能,而不闭关心这个资源是什么;同时,这套机制可以简单的识别和定位对象。

资源管理的核心是ResourceDictionary这个类。这是一个相当简单的集合类,就像一个普通的Hashtable,允许以关键字聚合对象,同时提供一个索引器,从而获取到使用了这些关键字的对象。因此,原则上,使用ResourceDictionary就像使用一个Hashtable一样,正如范例6-1所示:

示例6-1

实际上,没有必要如此创建一个ResourceDictionary对象。代替的,你可以正常

使用由WPF提供的一些资源字典。例如,FrameworkElement基类——用户界面的大部分元素都从中派生,对外暴露一个名为Resources的属性,这就是一个资源字典。此外,在Xaml标记中,也可以看到这些资源字典的,正如示例6-1所示:

示例6-2

x:Key这个属性指定了资源字典中的关键字。原则上,你可以使用任何类型的对象作为关键字,然而实际上,String是最普遍的选择,尽管资源字典中使用的是存储在公有属性中的截然不同的对象实例。

* 当使用xaml导入资源字典时,WPF可能会选择推迟资源的生成。在特定的环境下,这种做法可以使得xaml资源树的一部分仍然以二进制形式(BAML)存在。只有在需要的时候,才会还原这部分需要的资源。如果在界面出现时并不需要所有的对象,这样做可以明显加快用户界面的启动时间。除了加快速度,这种优化在极大程度上对代码行为并没有直接的效果。尽管如此,如果你的xaml标签出错了,因延迟创建而产生的错误,其浮现的时间可能会超过你的预计。

#微软推荐XAML被编译成BAML(Binary Application ...二进制语言程序标记语言)

示例6-1展示了如何获取示例6-2中定义的资源

示例6-3

注意到,这段代码使用this.Resources访问ResourceDictionary,这对于在同一个xaml中,由后台代码访问前台中的资源是非常适当的。尽管如此,这种方式并不总是方便。如何让一个xaml文件中的资源可以被应用程序中的所有窗体访问?我们所有窗体中复制同样的资源代码,这是乏味和效率低下的方法。此外,如何获取一个自定义控件的资源,而这个资源是由控件的父窗体指定的;而不是在这个控件中对外暴露这些资源?为了解决这些问题,以及更容易通过用户界面取得一致性,FrameworkElement通过一个有层次的资源范围(resource scope),扩展了ResourceDictionary机制

6.1.1 资源范围

FrameworkElement不光为每个用户界面元素提供了ResourceDictionary,还提供了FindResource方法来获取资源。示例6-4展示了如何使用该方法获取和示例6-4同样的资源。

示例6-4

这看起来毫无意思:在示例6-3中,我们已经有this.Resource[“Foo”]这样的方法获取资源,为什么还要有FindResource方法呢?这是因为,如果在指定位置找不到资源,FindResource将继续它的搜索,而不会停止。

示例6-5 使用了示例6-2myGrid元素,以之取代this,此时,Grid没有任何资源,第一行代码将变量b1设置为null。然而,因为b2是通过FindResource设置的,WPF将考虑一定范围内的所有资源,并不是那些直接设置给Grid的。这个范围是这么确定的:从Grid元素开始,如果没有找到资源,就会检测它的父元素,父元素的父元素。。。一直向上,直至根元素(有可能,父元素恰巧就是根元素——这将是一个很短的搜索。但是通常意义说,将要尽其所能的搜索)。结果,变量b2被设为与示例6-3与中同样的对象。

示例6-5

到此并未停止。如果FindResource方法直到UI根节点也没有找到指定的资源,这时,将要在application中寻找。不仅所有的framework元素都有一个Resource属性,Application对象也是如此。示例6-6展示了如何在xaml标签中定义应用程序级范围的资源(如果你使用的是Visual Studio 2005的项目模板,你要将这段代码放在MyApp.xaml中)。

示例6-6

应用程序级范围对于程序中的任何对象而言都是很方便的。例如,如果你使用样式和控件模板,肯定要将其放在应用程序的资源中,从而确保应用程序外观上的一致性。

对资源的搜索并未终止到应用程序级别。如果一个资源在UI树上以及应用程序中都不存在,FindResource方法最后将要在系统级范围搜索,这个范围内的设置都是针对系统级而言的,例如被选中项颜色的配置,以及滚动条的宽度。

6-1展示了一个典型的资源层次。每个应用程序在运行时,都会有一些窗体,而这些窗体内都有一颗由很多元素组成的树。如果FindResource在图中标记为1的元素上被调用,它首先会检索改元素的资源字典。如果没有找到,将会按照途中数字标记的顺序逐层向上搜索,直至达到系统资源这一层。

6-1

WPF在系统范围定义了笔刷,字体,以及度量单位;用户也可以在这一级别对其进行配置。这是由SystemColorsSystemFonts以及SystemParameters这些类的静态属性提供的(在这些类中,定义了400以上的资源,这里并未列出,具体语法请参阅SDK文档)。示例6-7从系统范围的资源获取一个Brush对象,并将其设置给toolTipBackgroud对象(参考第7章获取更多Brush的信息)。

示例6-7

* 这些系统资源的类使用对象作为资源的关键字,而不是字符串。这样避免了命名冲突的风险,因为一个明确的对象可以唯一的标志系统资源。从而系统资源和自定义资源之间不会有冲突。

系统资源的类同时定义了一些静态属性,允许你直接得到相关的对象,而不必从使用FindResource方法。例如,SystemColors类定义了一个InfoBrush属性,可以得到与示例6-7同样的Brush对象,见示例6-8

示例6-8

使用这些属性比通过FindResource方法获取系统资源在编码上要简单的多。尽管如此,使用FindResource方法仍然具有3个优势:

第一个优点,如果用户要改变应用程序的配色方案,而不受限于系统的配色方案,可以通过将这些资源放在应用程序级别,从而覆盖系统级别的设置。示例6-9展示了一个应用程序级别的资源片断,其中重新定义了InfoBrushKey资源的值,这个资源是基于应用程序级别的。

示例6-9

替换过的值只会影响到示例6-7,但不会影响到示例6-8。这是因为后者是直接在系统级别获取资源,而前者,是通过FindResource方法向上搜索,在应用程序级别找到了资源就返回,而不再继续向上搜索了。

第二个优点,通过资源关键字,直接使用在xaml标签中定义的系统资源。

第三个优点,可以使应用程序自动响应系统资源的变更。

后面两个优点使用到了资源引用。

6.1.2 资源引用

目前为止,我们已经看到如何在代码中获取已命名资源的值。既然我们经常使用资源值设置元素属性,我们将着眼于如何为元素属性设置资源值。这看起来想一个可笑的试验步骤。你可能希望代码如同示例6-10

示例6-10

我们在逐步接近一个目标:将Background属性设置为一个Brush对象,这个Brush将使用当前控件背景选中的颜色。尽管如此,如果用户在Windows控制面板-显示中改变设置,这个Background属性并不会自动更新。示例6-10有效获取了一个资源值的快照。

示例6-11并未受上述问题所苦。并不是得到一个快照,而是将资源连接到Background属性上。

示例6-11

不同于示例6-10,如果系统资源值有所改变,这个Background属性将会自动获取到新的值。这样的实际结果时,如果用户在Windows控制面板-显示中改变颜色配置,示例6-11将会确保你的用户界面自动更新。

WPF定义了xaml语法,提供了与上述两个示例同样的功能(附录A有关于XAML更多的信息)。可以使用StaticResourceDynamicResource这两个标签。如果使用系统资源或者其他运行时会发生改变的资源,请选则DynamicResource。如果已知资源不会发生改变,选择StaticResource,此时会进行一个快照,从而避免了在连接跟踪资源改变上的性能损失(这些损失虽然很小,但你也要为那些不改变的资源尽量避免)。示例6-12展示了如何同时使用这两个标签。

示例6-12

顶级的Window标签中,定义了一个brush作为一个资源。TextBlockBackgroud属性使用StaticResource引入了这个资源。这样做与示例6-10很类似:获取一个快照,而且在应用程序运行时不会改变资源。

GridBackgroud属性被设置为系统的“控件”颜色(典型的战舰的灰白色,这种颜色经常用于对话框的背景色)。既然这是一种用户可配置的颜色,所以可以在运行期进行改变,于是我们使用DynamicResource,达到了与示例6-10同样的效果。

这里,DynamicResource语法比StaticResource有一点复杂。这是因为我们希望使用的资源是通过SystemColors.ControlBruhKey对象确定其唯一性的,我们可以尝试使用如下语法:

 

这里缺两行代码

以上代码看上去是对的,其实并未按照我们的设想执行,而是被解释为一个对资源动态的引用,这个资源是以字符串“SystemColors.ControlBruhKey”命名,这样的资源是不存在的,从而导致背景色没有被设置。为了获取到真正需要的资源(按静态属性而不是按字符串形式),我们必须使用x:Static标签,正如示例6-11中的代码所示。

6.1.3对图形的复用

将图片和图形放到资源中是很有用的。这样做有两个主要原因。一个原因是图形可能非常复杂,将其直接放入xaml中会导致代码难于阅读。通过将其放入资源标签中,所有UI的结构将会更加清晰。另一个原因是支持复用,我们可以在多个地方使用同样的图形。

有很多方法用于表示图片和图形,这将在第7章介绍。所有这些图形都可以作为资源,虽然对一些特定的类型有很多限制。尤其是,如果你使用从FrameworkElement派生的任何元素作为一个资源,只能使用这个引用一次。这种约束的原因是FrameworkElement是用户界面树的基础。一个元素知道它的父元素以及它的子元素,所以这个元素不可能出现在这棵树的多个地方(当你使用资源的时候,只能使用资源对象,而不能使用其copy副本)

所以虽然在资源中放置了一个Ellipse,或者是Canvas画布中的所有图形,你都只能一次这个图形,因为EllipseCanvas都是从FrameworkElement派生的。为了减少混乱,将图形放入资源而从xaml的主要标签部分移除,这样做有时是很有用的。既然这样,使用一次的约束就不是一个大的问题了。示例6-13就是使用了名为ShapeEllipse资源,演示了如何使用这个资源。

示例6-13

StaticResource标签元素可以通过替换ResourceKey,在运行期动态修改。该示例的结果如图6-2所示:

6-2

一些绘图类,像GeometryDrawingDrawingGroup,是作为资源存储图形的最好候选者。既然绘图类不是从FrameworkElement派生的,我们可以自由的使用这些类的示例在任何地方。如果需要,DrawingGroup可以把任意多的图形图片放到一个单独的资源图形中,从Drawing类派生的各种不同类型都提供了访问所有WPF图形的机制。参见第7章获取更详细信息。

示例6-14展示了如何定义并使用一个图形资源,典型地使用了Drawing并配合以DrawingBrush。图6-3显示了相应的结果。

示例6-14

6-3

可选则地,我们可以定义一个DrawingBrush资源,从而将复杂的片断移动到Resource标签内,从而在你使用资源的标签位置,代码变得相当地简单,正如示例6-15所示,执行结果与示例6-14相同(见图6-3),但是使用资源的标签则由五行变成了一行。

示例6-15

如果你想在同一个图形出现在多个图像中,你可能想降低一下成为资源的级别,同时使用独立的几何对象作为资源。这一点接下来将会在图形中被提及。示例6-16展示了DrawingBrush标签的用法,在其中嵌套了使用了Geometry资源的GeometryDrawing子标签。(这只是另一个椭圆,和图6-3一样,只不过是青色的。)

示例6-16

在这个特定的例子中,资源的使用可能看上去有点极端。少许成效可能仅仅是从无到有创建了一个新的几何体。尽管如此,一些几何体,例如PathGeometry,可能会变得相当复杂,但是这种类型的复用,会变得更加有意义。

当图形和几何体功能强大而且可复用而且轻量级的时候,就会产生一个缺点。因为它们并不是恰当的框架元素,从而不能利用WPF的系统布局。你可以使用Brush的比例样式(见第7章)按比例缩放这些元素,但是不能使用面板智能地适应元素的布局(见第7章);因为所有的面板都是框架元素。你可能会想,从中选择一种既有使用框架元素特性(如布局)的能力,又有复用资源的能力——ControlTemplate可以符合你的需要。

示例6-17展示了使用ControlTemplate资源的标签。这个例子使用了Ellipse元素多次,有时,我们不能将Ellipse元素成为一个资源。正如你在图6-4中看到的,每个Ellipse都被Grid独立定位并且设置了大小。更多的模板技术见第5章。

示例6-17

6-4

控件模板提供了一个复用任意标记的方法,但是如果你不在图形中使用FrameworkElement基类,使用更轻量级的DrawingBrush类会更有效。而且如果你要创建大量包含相似图形的绘图,你甚至可以将独立的几何图形全都共享为资源。所有这些绘图机制将要在第7章介绍。

6.2 资源与样式

WPF的样式机制以来于资源体系来定位样式。正如你在第5章看到的,样式在元素的资源片段中定义,而且样式通过其名字被引用,正如示例6-18所示:

示例6-18

 

然而,如何定义一个样式,使之自动的应用到一个元素,而无需显示指定要引用的资源——这是可以实现的,而且非常有用——当你需要把一个样式应用到具有独特类型的所有元素上,而不是把资源引用添加到每个元素上。示例6-19对示例6-18做了一些修改,展示了隐式声明这一功能。

示例6-19

注意到Button标签不再有其特定的Style属性。然而,这个样式仍然通过TargetType应用到Button上,而不是定义一个key,这个样式使用x:Type来设置TargetType,于是通知XAML为这个TargetType类提供一个System.Type对象。

如果FrameworkElement没有显示指定Style,它总是会寻找一个使用其自身类型的样式资源,作为其Target类型。

#如果你建立了一些非样式的资源,例如SolidColorBrush,同时设置其x:Key为某个UI元素的类型,如果试着使用该元素的类型就会发生一个错误。这是因为你创建了一个带有TargetTypeStyle却没有指定x:Keyx:Key隐式地设置为同TargetType一样。这个Key用于定位style。因此,通常而言,你应该避免将x:Key设置为Type类型的对象。

因为元素会在资源中搜索它的样式,你可以利用系统级别的资源。你可以定义一个样式资源在局部范围内,如果你仅仅希望影响少量的元素;或者在一个广义范围上,例如Window.Resource;或者在应用程序的范围。而且样式可能延及到系统级别。这种样式和资源之间的联系是使用皮肤和主体的关键

6.2.1皮肤和主题

皮肤和主题都是控制UI外观的技术。主题,是一种系统级别的外观,例如Windows2000的经典外观,又如Windows XP的“Luna”主题。皮肤是一个特定于应用程序的外观,正如各种各样具有不同样式的媒体播放程序,例如WinAppWindows Media Player

皮肤和主题都可以在WPF实现,作为一组资源应用于需要该样式的控件

既然皮肤的意图在于控制一个特定应用程序的外观,它将为标准控件提供更多的样式,可以在应用程序的指定部分定义各种各样有命名的资源。例如,音乐播放器可能使用一个ListBox用来显示歌曲列表。皮肤可以为之提供一个特定的外观而不用影响应用程序中其他的ListBox。因此应用程序可以为这个ListBox设置特定的命名的样式,同时要在这个样式中支持这个样式。对于这种特定情形,提供这样一个样式是可选择的,但是在其他情形中,应用程序需要皮肤提供提供指定的资源。例如,如果应用程序中有一个工具条,皮肤可能就需要提供资源并在其中为这个工具条定义图像。

同样,主体是用于所有应用程序,因此,其必须为所有类型的控件提供模板和样式。比较而言,一个皮肤是特定于应用程序的,所以它不必提供广泛全面的一组样式。如果应用程序并不使用每一个单独的控件类型,皮肤只需要为那些在应用程序中出现的控件提供样式。示例6-20和示例6-21为一个相当简单的皮肤,展示了xaml和相应的后台代码

示例6-20

示例6-21

以上代码为一个按钮设置了前景色和背景色。一个更复杂的皮肤应该可以为更多的类型元素提供样式,并且提供更多的属性。更多的皮肤包括一些模板属性的设定,从而可以定义控件的外观。但是即使是在这个简单的例子中,底层的原理也都是一样的。示例6-22展示了一个UI,示例6-23则是这个UI的相应后台代码,允许皮肤的切换。(这个示例假设有2个皮肤类,BlueSkinGreenSkin,都是使用示例6-20的技术定义的。

示例6-22

示例6-23

SimpleSkin类的代码确保了皮肤只创建一次,而且简单地更换皮肤——通过设置应用程序资源字典,使之成为选中的皮肤。(第二个皮肤的源GreenSkin,在这里没有显示出来,看上去和示例6-20相同,只是用绿色取代了蓝色。)样式和系统资源随着资源的更换自动反映出来:当更改皮肤的时候更新所有有效的控件,因此,这就是我们需要的代码。图6-5显示了代码效果。

6-5

这种切换皮肤的方式有一个小障碍。除了使用皮肤资源,在应用程序级别也存储了一些资源,那么这些应用程序资源会在切换皮肤时丢失。现在,唯一的解决方案是保证每一个皮肤包含一份应用程序级别的资源副本。最好的办法是将这些副本保存在一个单独的类,并将副本和并到资源皮肤中。WPF当前的版本不支持自动和并资源字典,WPF团队的成员已经声明,他们正在考虑更容易的处理办法在未来的发布版本中。目前,只能手动处理,正如示例6-24所示。

 

示例6-24

如上,你可以将代码添加到加载资源的方法中。在示例6-23中,你可以在EnsureSkins方法中和并资源,将bluegreen皮肤都和并到nonSkinAppResources中。

6.3 二进制资源

尽管ResourceDictionary和系统级别的资源适合于作为数据存在于对象中,然而,并不是所有的资源都能很好的满足这个模型。能够处理二进制流通常是很有用的。例如,图像,声频和视频,都是有效地二进制的代表,但是这些资源在xaml内都没有相应的标签,而且毕竟这些对象通常表现为底层数据的包装。标记语言本身代表了一种挑战:xaml页面必须编译到我们的应用程序中。因此,需要一种处理二进制流的方法。

WPF并未引进任何新技术处理二进制数据。.NET框架已经提供了处理内嵌二进制流的机制,WPF只是简单使用了这个技术。

最底层的流支持你内嵌资源流到任何的编译文件中。提供内嵌到编译器的文件是一种简单的方式。在Visual Studio 2005中,你可以通过设置一个文件的Build Action属性来支持内嵌资源。:复制该文件的内容,作为一个内嵌流放入编译文件中。使用AssemblyGetManifestResourceStream方法,可以在运行期获取到这个流,正如示例625所示:

示例6-25

 

这种方式的内嵌流称为“资源清单”。WPF最终依赖于这种资源内嵌机制,可以通过System.Resources命名空间的ResourceManager类直接使用。这是建立在内嵌资源系统上,附加两个特点:本地化和在一个底层流中按名字存储多个流的能力。ResourceManager允许我们按照名字寻找资源,这将要尝试根据UI文化定位最合适的资源,更多细节将在下一部分描述。

按照规定,一个WPF的应用程序或组件将其所有资源放入一个单独的资源清单的中,称之为Appname.g.resources,其中Appname是程序或组件的名称(不包含扩展名)。这个单独的资源流包含二进制的资源,可以通过ResourceManager获取到。示例6-26展示了如何获取一个资源名称的清单。

示例6-26

 

让我们通过这段代码,着眼于一个典型的应用程序内部的发现资源。图6-6展示了一个WPF工程的Visual Studio 2005解决方案管理器视图。这个工程包含了通常的定义了应用程序的MyApp.xaml文件,一个定义了用户界面的Window1.xaml文件(在一个包含多个窗体和页面的应用程序中,你可以看到更多xaml文件)。这个工程还包括一个Images目录,其中有两张图片。正如你在图6-6下半部分的属性面板中看到的,Sunset.jpgBuild Action属性已经设置为Resource。当你添加一个bmp图片到解决方案中时,在解决方案管理器视图的上下文菜单,选择Add--New Item…或者Add—Existing Item…,那么这个图片的Build Action属性会自动设置为Resource。对于Wheel.jpg也是同样的设置。

6-6

如果我们调用示例6-26中的ResourceNames函数并且打印出其返回值,可以看到下列输出:

myapp.baml

window1.baml

image/wheel.jpg

image/sunset.jpg

正如你看到的,所有的bmp文件都在上面列出了。你可以在任意元素中通过指定URL的方式使用这些内嵌的图片,正如示例6-27展示的。这里使用了相对URL路径,表明这个Image元素使用的是本地资源。相对URL不仅可以用于图片文件与应用程序在同一目录,而且可以作为一个内嵌资源。既然图片数据可以内嵌在二进制程序的资源流中,那么没有必要将其转移到一个独立的包含图片数据的文件中了。

示例6-27

上述资源列表还显示了myapp.bamlwindow1.baml两个资源,对应到相应的两个xaml文件。

#BAMLxaml文件的二进制表现形式。Xaml在编译期间被编译成BAML格式有两个原因。首先,BAMLxaml更加显著的简捷,所以你的可执行文件比xaml文件要小得很多。其次,BAML在设计上更易于阅读,支持UI加载的速度更快——相对于xaml的语法解析。

在一个WPF工程中,任意具有Build Action的页面文件都是xaml形式。这将编译成BAML,并被内嵌为一个资源。

图片,BAML文件,以及任意的内嵌二进制资源都使用ResourceManager机制,这为应用程序的本地化提供了一个方法。

6.4 应用程序全球化

如果你打算发布你的应用程序到全球各地,你可能需要为不同地区的用户界面准备不同的版本。至少,这需要解决将文本翻译成适当的语言;同样需要解决UI改变的问题。你可能需要特定的外观适应为本地化的文化习俗。或者,你可能会发现原始的外观在翻译后并不能正常工作,因为词的长度是不一样的。(虽然WPF的外观体系避免了这一问题,更易于创建更弹性的外观。)

为你的软件在不同的市场创建不同的版本是可能的。尽管如此,更加普遍的办法是创建一个单独的版本来适应于不同的场所,通过在运行期选取一个合适的资源文件。WPF的底层架构ResourceManager可以相当直接地使用这个办法。

#Microsoft对本地化和全球化加以区别。本地化是一个支持一个应用程序在意个特定的场所使用的过程,通过创建特定文化的资源如文本的翻译。全球化是一个确保一个应用程序可以被本地化而不用编译的过程。使用ResourceManager可以帮助我们对应用程序进行全球化,因为这个类的在运行期选择的资源支持一个单独的应用程序版本通过提供合适的资源进行本地化。更多信息请参考微软国际站点http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vboriInternationalization.asp (http://shrinkster.com/6m9).

当一个ResourceManager被要求按名称获取资源流时,首先它要决定使用哪种文化。文化时语言与区域的合称,被典型地表示为一个短字符串。例如,en-US表示在美国使用的英语。而enGB则代表在英国使用的英语。前两个字母表明语言,后两个表示区域。同时指明语言和区域是因为即使两种文化共用一种语言,在方言和习语上还是有差别的。例如,本书的作者之一来自en-GB,因此喜欢使用color而不是colour

ResourceManagerGetStream方法使用一个CultureInfo对象作为参数。如果你想使用用户最终配置过的文化,你可以从Thread.CurrentThreadCurrentUICulture属性重新获取一个CultureInfo对象。

虽然可执行体经常将资源编译到其中,ResourceManager仍然会先寻找指定文化的资源,在求诸于内嵌资源之前。首先会在包含应用程序目录的子目录中按文化名称搜索。因此,如果你的应用程序运行在French-Canadian的文化上,会在名为fr-CA的子目录中寻找一个叫做MyApp.resources.dll的文件,这里MyApp是应用程序或组件的名称。如果不存在,会接着在寻找名为fr的目录寻找相同的文件。这意味着一旦你的翻译预算并不扩展到为全世界所有使用法语的不同区域产生不同的版本,你可以取代的提供一套单独的法语资源,专门用于使用法语的区域。如果这些子目录都不存在这个文件,它将求助于使用内嵌资源。

这些被ResourceManager寻找的DLL资源称为全球化编译资源,命名的原因是这些资源都是很小的资源,聚集为一个大的编译集。

注意到,一旦你提供了一个全球化编译集,你就不需要提供所有资源的本地化版本。这样那些内嵌在你的主编译集的资源对于所有资源都是很是的。例如,图6-6中的应用程序有一个内嵌的bmp文件Sunset.jpg。世界大部分都会有日落,所以你仍可能需要为南极和北极的版本做特殊的工作。基本的Sunset.jpg可以为大多数文化使用。这将要浪费空间对于每个全球化资源都要获取同一张图片的副本。幸运的是,这不是必须的。如果一个特定的命名资源在全球化编译资源中并不存在,ResourceManager将会回到内嵌资源中来。

你可以把全球化编译资源想象为仅包含内嵌资源和目标文化所需资源之间的差别。任何普通的资源都单独位于主编译集中。一个特定语言但不限区域的编译集所在的子目录(如fr子目录),包含不同于特定语言的所需资源。而且,完全特定文化的子目录(如fr-CAfr-FRfr-BE等)包含那些需要考虑当地习语的资源(在这里的上下文中,“资源”是一个通过ResourceManager重新获得的流,而不是通过ResourceDictionary重新获取到的对象)

6.4.1使用XAML创建本地化应用程序

因为XAML是编译在BAML资源中的,可以通过ResourceManager获取到,本地化是一种内在的特征,由任何WPF应用程序使用XAML创建。尽管如此,在Visual Studio 2005中,并没有对WPF应用程序本地化提供内在支持,因此需要一些手动步骤。

如果你要在XAML中创建一个UI,有效的本地化每次产生一个XAML文件。ResourceManager不能比一个单独的BAML资源得到更加细密的资源,因此,每一个BAML资源不是本地化就是非本地化的。由于xaml文件和其后台代码之间的紧密联系,尽管如此,本地化BAML资源具有和原始版本一样的基本结构,这是非常重要的。基本上,你可以通过为本地化版本写一个xaml文件获取资源,并且尝试保持结构的一样。然而,这是一种具有更具强壮性的方式来保证一致性。

取代以为每种文化创建一组xaml文件,你可以写一组主要的xaml文件,每个窗体或页面都有一个这样的文件。你可能希望对每种恶化都提供支持,可以使用一个工具生成特定文化的全球化编译资源,其中包含本地化资源。你提供配置文件的工具表明资源应该如何被修改,从而创建本地化的版本。图6-7解释了全部的过程。

#这是一个些微冗长的过程。在WPF最新的发布版本中,并未更好的改进和集成到Visual Studio 2005中。眼下,欢迎此预发布的软件来到这个精彩的世界。

6-7

 

首先,你必须保证工程通过指定一个默认的UI文化,被设置为创建一个本地化的应用程序。在写代码的时候,没有办法在Visual Studio 2005中进行这样的操作,所以你必须使用文本编辑器编辑.csproj文件。在PropertyGroup元素中添加一个UICulture元素(UICulture元素出现在那个部分并不要紧),将其设置为默认文化。该文化为将要创建的主要资源。这将引起Visual Studio 2005为了这个默认文化,将所有的二进制资源置入全球化编译资源中。示例6-28展示了一个xaml的例子。

示例6-28

下一步,你必须将Uids标签添加到xaml中。Uid标签是xaml元素的一个特殊的属性,显示需要本地化的内容。包含了本地化指令的本地化配置文件,使用Uid属性显示那些元素被修改了。示例6-29展示了一个使用了UidTextBlock

示例6-29

你可以手动添加你想要的。或者你能使用msbuild自动生成它们(msbuild是一个命令行工具,用来生成工程。Visual Studio 2005使用同样的创建技术,但是msbuild提供更多的控制。例如,它允许你在Uid生成WPF创建系统的特征)。为了自动添加Uids到你的xaml中,运行这个命令:

 msbuild /t:updateuid MyProject.csproj

如果你已经做完这些,随后编辑好你的xaml,你可能想检查是否以重复的Uid标签终结,可以使用以下命令:

msbuild /t:checkuid MyProject.csproj

现在你可以生成这个工程了,或者使用Visual Studio 2005的命令行执行msbuild,将工程名作为一个参数传递。你会发现在生成一个exedll的同时,你的工程也在子目录中添加了一个全球化资源。(这个子目录将是添加到工程中的UICulture元素名称重的一个。)

下一步是创建一个配置文件用于引导本地化过程。这个文件包含所有的本地化资源,如翻译过的字符串。你可以使用LocBaml命令行工具创建这个文件的骨架。这个工具检查了BAML流的编译资源,创建了一个文件,文件中每一行都是一条本地化信息。你可以将翻译字符串和任何需要的资源放入这个文件中。

#WPF的当前预览版本中,LocBaml只提供源代码的形式,因此在执行之前需要先生成它。你可以在WinFX SDK文档中找到这些代码。在AvalonGlobalization and LocalizationHow-to Topic section,找到“Localize an Application”标题。这里将提供给你LocBaml的代码。

示例6-30展示了如何执行LocBaml生成配置文件的骨架。

示例6-30

这将创建一个CSV文件。表6-1描述了CSV文件中每一列(按出现顺序)。为了本地化一个资源,编辑Value这一列。(注意这个CSV文件并不包含标题行,只依赖于列的位置。)

6-1

列名

描述

Baml Name

唯一标志BAML流,这个值表现为AssemblyManifestStreamName:SubStraemName 的形式

Resource Key

唯一标志本地化资源,这个值表现为Uid.ElementType.$Property的形式

Localization Category

一个来自LocalizationCategory枚举的入口,表明是哪一类内容

Readable

表明在转换期间资源是否可见

Modifiable

表明在转换期间Value是否可以更改

Comments

本地化的注释

Value

资源的值

示例6-31展示了配置文件中的一行。(由于页面宽度,这一行被拆成了3行显示在下面)。

示例6-31

一旦你已经转换了所有的值,你就可以再次执行LocXaml生成新的资源dll。你必须传递原始资源dll的路径,CSV文件(包含翻译)的路径,一个目标地址,以及一个目标文化,正如示例6-32所示。。(由于页面宽度,这一行被拆成了3行显示在下面)。

示例6-32

这将生成一个新的全球化编译资源在指定的目录,以选中的文化为目标。(如果你想生成多个编译资源,为每一种文化创建一个CSV文件,为每个文件只执行一次LocBaml。)如果你把资源编译结果放在一个按文化命名的应用程序目录的子目录中,这将自动在运行期被获取到,应用程序会执行选中的特定文化。

6.5 我们进行到哪里了?

WPF提供了资源工具,让我们运用在用户界面中,动态并具有一致性。我们可以在资源字典中存储任意资源,并且可以遍及应用程序引用这些资源。WPF的样式机制依赖于资源字典——通过为控件设置属性和模板,基于应用程序的皮肤或当前的系统配置主题。而且,对于二进制资源,包含了编译后的BAML版本的xaml文件,WPF使用明显的本地化ResourceManager体系,为终端用户选取最适合的资源作为用户界面的文化。

posted @ 2008-03-14 12:14  包建强  Views(1258)  Comments(0Edit  收藏  举报