在WPF用户界面中,绘制2D图形内容的最简单方法是使用形状(shape)——专门用于表示简单的直线、椭圆、矩形以及多变形的一些类。从技术角度看,形状就是所谓的绘图图元(primitive)。可组合这些基本元素来创建更复杂的图形。
关于WPF中形状的重要细节是,它们都继承自FrameworkElement类。因此,形状是元素。这样会带来许多重要的结果:
- 形状绘制自身。不需要管理无效的情况和绘图过程。例如,当移动内容、改变窗口尺寸或改变形状属性时,不需要手动重新绘制形状。
- 使用与其他元素相同的方式组织形状。换句话说,可在前面学过的任何布局容器中放置形状(尽管Canvas明显是最有用的容器,因为它允许在特定的坐标位置放置形状,当构建复杂的具有多个部分的图画时,这很重要)。
- 形状支持与其他元素相同的事件。这意味着为了处理焦点、按下键盘、移动鼠标以及单击鼠标等,不必执行任何额外工作。可使用用于其他元素的相同事件集,并同样支持工具提示、上下文菜单和拖放操作。
一、Shape类
每个形状都继承自抽象类System.Windows.Shapes.Shape。下图显示了形状类的继承层次。
图 WPF形状类
正如上面看到的,相对来说,只有很少一部分类继承自Shape类。Line、Ellipse以及Rectangle都很直观,Polyline是一系列相互连接的直线,Polygon是由一系列相互连接的直线形成的闭合图形。最后,Path类功能强大,能将多个基本形状组合成单独的元素。
尽管Shape类自身不能执行任何工作,但它定义了少量的重要属性。下表列出了这些属性。
表 Shape类的属性
二、矩形和椭圆
矩形和椭圆是两个最简单的形状。为创建矩形或椭圆,需要设置大家熟悉的Height和Width属性(这两个属性继承自FrameworkElement类)来定义形状的尺寸,然后设置Fill或Stroke属性(或同时设置这两个属性)使形状可见。还可以使用MinHeigth、MinWidth、HorizontalAlignment、VerticalAlignment以及Margin等属性。
下面举一个简单示例,该例在StackPanel面板上放置了一个椭圆和一个矩形,效果图如下所示:
<StackPanel> <Ellipse Fill="Yellow" Stroke="Blue" Height="50" Width="100" Margin="5" HorizontalAlignment="Left"></Ellipse> <Rectangle Fill="Yellow" Stroke="Blue" Height="50" Width="100" Margin="5" HorizontalAlignment="Left"></Rectangle> </StackPanel>
Ellipse类没有增加任何属性。Rectangle类只增加了两个属性:RadiusX和RadiusY。如果将这两个属性的值设为非零值,就可以创建出美观的圆形拐角。
可认为RadiusX和RadiusY属性是用于填充矩形拐角的椭圆。例如,如果将这两个属性都设为10,WPF会使用10个单位宽的圆形边缘绘制拐角。随着半径的增大,矩形拐角的更多部分会被替换。如果增加RadiusY属性的值,使其大于RadiusX属性的值,矩形拐角的左边和右边会更平缓,而顶部和底边的边缘会更尖锐。如果增大RadiusX属性的值,使其等于矩形宽度,并增加RadiusY属性的值,使其等于矩形的宽度,矩形最后会变成普通的椭圆。如下图所示:
<Window x:Class="Drawing.RoundedRectangles" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="RoundedRectangles" Height="447.744" Width="300"> <StackPanel> <TextBlock Margin="5,5,0,0">Corner radius of 5.</TextBlock> <Rectangle Fill="Yellow" Stroke="Blue" RadiusX="5" RadiusY="5" Width="100" Height="60" Margin="5" HorizontalAlignment="Left"> </Rectangle> <TextBlock Margin="5,5,0,0">Corner radius of 10.</TextBlock> <Rectangle Fill="Yellow" Stroke="Blue" RadiusX="10" RadiusY="10" Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle> <TextBlock Margin="5,5,0,0">Corner radius of 10 (X) and 25 (Y).</TextBlock> <Rectangle Fill="Yellow" Stroke="Blue" RadiusX="10" RadiusY="25" Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle> <TextBlock Margin="5,5,0,0">Corner radius of 100 (X) and 60 (Y).</TextBlock> <Rectangle Fill="Yellow" Stroke="Blue" RadiusX="100" RadiusY="60" Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle> </StackPanel> </Window>
三、改变形状的尺寸和放置形状
正如前面所知,赢编码尺寸通常不是创建用户界面的理想方法。它们会限制处理动态内容的能力,并会使应用程序本地化到其他语言变得更加困难。
当绘制形状时,不再总是关心这些问题。通常,需要更严格地控制形状的位置。然而,在许多情况下仍需要灵活一点设计。Ellipse和Rectangle为了适应可用的空间,都能自动改变自身。
如果为提供Height和Width属性,形状会根据它们的容器来设置自身的尺寸。在上一个示例中,如果删除Height和Width值(并且不设置MinHeight和MinWidth值),就会导致形状缩小到看不见,因为StackPanel面板为了适应其内容改变了尺寸。然而,如果强制StackPanel面板的宽度为整个窗口的宽度(通过将HorizontalAlignment属性设置为Stretch),并将椭圆的HorizontalAlignment属性设置为Stretch,删除椭圆的Width属性值,这时椭圆的宽度就是整个窗口的宽度。
可使用Grid容器构造更好的示例。如果使用按比例改变行尺寸的行为(默认行为),就可使用下面更精简的标记创建填满窗口的椭圆:
<Grid> <Ellipse Fill="Yellow" Stroke="Blue"></Ellipse> </Grid>
在上面的标记中,Grid面板填满了整个窗口。Grid面板包含了一个按比例改变尺寸的行,该行填满了整个Grid面板。最后,椭圆填满了整行。
改变形状尺寸的行为依赖于Stretch属性的值(该属性在Shape类中定义)。默认情况下,该属性被设置为Fill。如果改变指定明确的尺寸,这一设置会拉伸形状,使其填满容器。下表列出了Stretch属性的所有可能值。
表 Stretch枚举值
下图显示了Fill、Uniform、UniformToFill枚举值之间的区别.
<Window x:Class="Drawing.FillModes" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="FillModes" Height="270" Width="477" > <Grid ShowGridLines="True" Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="Auto"