通过上一章的学习,Geometry抽象类表示形状或路径。Drawing抽象类扮演了互补的角色,它表示2D图画(Drawing)——换句话说,它包含了显示矢量图像或位图需要的所有信息。
尽管有几类画图类,但只有GeometryDrawing类能使用已经学习过的几何图形。它增加了决定如何绘制图形的画笔和填充细节。可将GeometryDrawing对象视为矢量插图中的形状。例如,可将标准的窗口元文件格式(.wmf)转换成准备插入用户界面的GeometryDrawing对象的集合。
分析一个简单的示例有帮助的。前面已经看到了如何定义表示三角形的PathGeometry对象:
<PathGeometry> <PathFigure IsClosed="True" StartPoint="0,100"> <LineSegment Point="100,100"/> <LineSegment Point="100,50"/> </PathFigure> </PathGeometry>
可使用PathGeometry对象创建GeometryDrawing对象,如下所示:
<GeometryDrawing Brush="Yellow" > <GeometryDrawing.Pen> <Pen Brush="Blue" Thickness="3"></Pen> </GeometryDrawing.Pen> <GeometryDrawing.Geometry> <PathGeometry> <PathFigure IsClosed="True" StartPoint="10,100"> <LineSegment Point="100,100" /> <LineSegment Point="100,50" /> </PathFigure> </PathGeometry> </GeometryDrawing.Geometry> </GeometryDrawing>
现在,PathGeometry对象定义了形状(三角形)。GeometryDrawing对象定义了形状的外观(具有蓝色边界的黄色三角形)。PathGeometry对象和GeometryDrawing对象都不是元素,所以不能直接使用这两个对象中的任何一个为窗口添加自己绘制的内容,而需要使用另一个支持图画的类。
GeometryDrawing类不是WPF总唯一的图画类(尽管当使用2D矢量图形时,该类是最相关的一个类)。实际上,Drawing类用于表示所有类型的2D图形,并且还有一个小组类继承自该类。下表列出了所有这些类。
表 图画类
一、显示图画
因为继承自Drawing的类不是元素,所以不能将它们放置到用户界面中。为了显示图画,需要使用下表列出的三个类中的一个。
表 用于显示图画的类
所有这些类中都存在通用主题。非常简单,它们提供了使用更少系统资源显示2D内容的方法。
例如,假如希望使用矢量图像为按钮创建图表。最简单的方法(也是占用资源最多的方法)是在按钮中放置Canvas控件,并在Canvas控件中放置一系列继承自Shape类的元素:
<Button ...> <Canvas ...> <Polyline ...> <Polyline ...> <Rectangle ...> <Ellipse ...> <Polygon ...> </Canvas> </Button>
现在已经知道,如果是使用这种方法,每个元素都是完全独立的,具有自己的内存区域和事件处理程序等。一个更好的减少元素数量的方法是Path元素。因为每个路径具有单独的笔画和填充,所以仍需要大量Path对象,不过这还是能够在一定程度上减少元素数量:
<Button ...> <Canvas ...> <Path ...> <Path ...> ... </Canvas> </Button>
一旦开始使用Path元素,就将独立形状变换为不同的几何图形。可从路径中提取几何图形、笔画以及填充信息并将它们转换成图画,从而再增加一个抽象层。然后可在DrawingGroup对象中将这些图画融合在一起,并将DrawingGroup对象放置到DrawingImage对象中,DrawingImage对象又可被放入到Image元素中。下面是这一过程创建的新标记:
<Button ...> <Image ...> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <DrawingGroup> <GeometryDrawing ...> <GeometryDrawing ...> <GeometryDrawing ...> ... </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> </Button>
这是一次意义重大的改变。该例并没有简化标记,只是用GeometryDrawing对象代替了每个Path对象。然而,由于减少了元素的数量,因此降低了所需的开销。在前面的示例中创建了包含在按钮中的Canvas控件,并为每个路径添加了单独的元素。但该例只需要一个嵌套的元素:位于按钮中的Image元素。付出的代价是不能再为每个不同的路径处理事件(例如,不能探测鼠标在图画中独立区域的单击操作)。但在用于按钮的静态图像中,未必需要使用这种功能。
尽管使用DrawingImage对象已经节省了大量资源,但仍可进一步提高效率,借助于Drawing删除另一个元素。
基本思想是在DrawingBrush对象中的封装DrawingImage对象,如下所示:
<Button ...> <Button.Background> <DrawingBrush> <DrawingBrush.Drawing> <DrawingGroup> <GeometryDrawing ...> <GeometryDrawing ...> <GeometryDrawing ...> <....> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Button.Background> </Button>
DrawingBrush方法和前面介绍的DrawingImage方法不完全相同。因为Image元素改变其内容的默认方式是不同的。Image.Stretch属性的默认值是Uniform,该设置会为了适应可用空间而放大或缩小图像。DrawingBrush.Stretch属性的默认值是Fill,该设置可能会扭曲图像。
当改变DrawingBrush的Stretch属性时,为明确扭曲填充区域中图画的位置和尺寸,也可能希望调整Viewport设置。例如,下面的标记缩放由图画画刷使用的图画,以占用填充区域的90%:
<DrawinBrush Stretch="Fill" Viewport="0,0 0.9,0.9">
对于按钮示例这是非常有用的。因为可为按钮周围的边框留出一定的空间。因为DrawingBrush并非元素,所以不能使用WPF布局过程。这意味着和Image元素不同,DrawingBrush中的内容放置不会考虑Button.Paddin属性值。
使用DrawingBrush方式的一个古怪问题是,将鼠标移到按钮上时内容会消失,并且会使用一个新画刷绘制按钮表面。但当使用Image方式时,图片就不受影响。为了解决这个问题,需要为按钮创建自定义按钮控件模板,该模板使用不同的方式绘制按钮的背景。
无论是在DrawingImage本身中使用图形,还是使用DrawingBrush封装图形,都应当考虑使用资源分解标记。基本思想是作为不同资源定义每个DrawingImage或DrawingBrush对象,从而当需要时就可以引用定义的对象。如果希望在多个元素或窗口中显示相同的内容,这是特别好的思想,因为只需要重用资源,而不必复制整块标记。如下面的示例所示:
<Window x:Class="Drawing.Drawings" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Drawings" Height="300" Width="300"> <Window.Resources> <GeometryDrawing x:Key="Drawing" Brush="Yellow" > <GeometryDrawing.Pen> <Pen Brush="Blue" Thickness="3"></Pen> </GeometryDrawing.Pen> <GeometryDrawing.Geometry> <PathGeometry> <PathFigure IsClosed="True" StartPoint="10,100"> <LineSegment Point="100,100" /> <LineSegment Point="100,50" /> </PathFigure> </PathGeometry> </GeometryDrawing.Geometry> </GeometryDrawing> </Window.Resources> <StackPanel Orientation="Horizontal" Margin="5"> <Button Width="30" Height="30"> <Image> <Image.Source> <DrawingImage Drawing="{StaticResource Drawing}"> </DrawingImage> </Image.Source> </Image> </Button> <Button Width="30" Height="30"> <Button.Background> <DrawingBrush Stretch="Uniform" Viewport="0,0 0.9,1" Drawing="{StaticResource Drawing}"> </DrawingBrush> </Button.Background> </Button> </StackPanel> </Window>
二、导出插图
尽管所有这些示例都内联地声明他们的图画,但更常用的方法是将该内容的某些部分放到资源字典中,从而可在整个应用程序中重用(并非一个地方进行修改)。由用户来确定如何将这些标记分割到资源中,但两种常用的方法是,存储一个充满DrawingImage对象的字典,或存储一个保存DrawingBrush对象的字典。此外,也可以分离出Geometry对象,并将它们存储为独立的资源。如上面的示例所示。
当然,很少有开发人员会手动编写大量图形。反而, 他们将使用专门的设计工具导出所需的XAML内容。大多数设计工具目前还不支持XAML导出功能,不过有许多插件和转换工具可以弥补这一缺陷。下面是几个例子:
- http://www.mikeswanson.com/XAMLExport/上有一个用于Adobe Illustrator工具的免费XAML插件
- http://www.mikeswanson.com/swf2xaml/上有一个用于Adobe Flash文件的免费XAML转换工具。
- Expression Design是Microsoft公司的插入和图形设计程序,内置了XAML导出功能。该程序能够读取各种矢量图形文件格式,包括.wmf(Windows元文件格式)文件,还可以导入已经存在的插图并将其导出为XAML格式。
然而,即使使用其中某个工具,前面学习的有关图形和图画的知识依然十分重要,主要原因如下几点:
首先,许多程序允许选择是希望作为Canvas控件中的独立元素的组合导出图画,还是希望作为DrawingBrush或DrawingImage资源的集合导出图画。通常,第一种选择是默认选择,因为它保留了许多特性。然而,如果使用大量图画,并且图画很复杂,或者只是希望为了尽可能减少内存需求而使用静态图形,如按钮图形,使用DrawingBrush或DrawingImage资源更好的多。而且,这些格式和用户界面的其他部分是相互独立的,所以在以后很容易更新它们。
之所以说理解2D图形基础知识是很重要的,另一个原因是这样可以更容易地控制它们。例如,可通过以下方式替换标准的2D图形:修改用于绘制各种形状的画刷、为单个几何图形应用变化、改变不透明度或者变换整个形状层(通过DrawingGroup对象)。更富有戏剧性的是,可添加、删除和替换单个几何图形。