- 在WPF用户界面中,绘制2D图形内容最简单的方法是使用形状(shape):它是专门用于表示简单的直线、椭圆、矩形及多边形的类。从技术角度讲,形状就是所谓的绘图图元,可以组合这些基本元素来创建更复杂的图形。在WPF中,形状都是继承自FrameworkElement类,因此,形状是元素。
- Shape类
每个形状都继承自抽象的System.Windows.Shapes.Shape类。下图是形状类的继承层次:
以下是Shape类定义了几个重要的属性:
- 矩形和椭圆
Ellipse类没有添加任何属性,Rectangle只添加了两个属性:RadiusX和RadiusY。必须为形状设置Fill和Stroke属性,否则就根本看不见形状,以下示例在StackPanel面板中放置了一个椭圆和一个矩形:
<StackPanel> <Ellipse Width="100" Height="50" Margin="5" Fill="Red" Stroke="Blue" StrokeThickness="3" /> <Rectangle Width="100" Height="50" Margin="5" Fill="Green" Stroke="Yellow" StrokeThickness="3" RadiusX="20" RadiusY="20" /> </StackPanel>
- 放置形状和改变形状的尺寸
通常放置形状的理想容器是Canvas,它要求使用Left、Right、Top、Bottom附加属性为每个形状指定坐标,这样可以完全控制形状如何相互重叠。改变形状的尺寸依赖于Stretch属性的值,它是在Shape类中定义的。以下是Stretch属性的四种取值:- Fill:默认值,如果没有指定一个明确的尺寸,这一设置会拉伸形状,使其填满窗器。
- None:形状不被拉伸,除非设置Width和Height,否则不会显示形状。
- Uniform:对于Ellipse它会以设置的Width和Height中的最小者设置椭圆的直径,如果没有设置Width和Height,它就以容器的最小边长做为椭圆的直径;而对于Rectangle它会以设置的Width和Height中的最小者设置矩形的边长,如果没有设置Width和Height,它就以窗器的最小边长做为矩形的边长。
- UniformToFill:对于Ellipse它会以设置的Width和Height中的最大者设置椭圆的直径,如果没有设置Width和Height,它就以容器的最大边长做为椭圆的直径;而对于Rectangle它会以设置的Width和Height中的最大者设置矩形的边长,如果没有设置Width和Height,它就以窗器的最大边长做为矩形的边长。
- 使用Viewbox控件缩放形状
使用Canvas控件的唯一限制是图形不能改变自身的尺寸以适应更大或更小的窗口。对此,WPF提供了Viewbox元素,它是一个继承自Decorator的简单类,它只接受一个子元素,并且拉伸或缩小其子元素以适应可用的空间,这个单一的子元素可以是布局容器,其中可以包含大量的形状或其它元素,这些元素将同时改变尺寸,然后,Viewbox更常用于矢量图像而不是普通的控件。通常,在Viewbox中放置一个Canvas面板,并在Canvas面板中放置形状。
Viewbox元素执行缩放时,它是按比例改变屏幕上的每个元素,包括图像、文本、直线及形状。例如,如果在Viewbox元素中放置一个普通按钮,尺寸的改变会影响它的整个尺寸,内部的文本及周围的边框的厚度,如果在Viewbox内部放置一个形状元素,它会按比例地改变形状内部的区域和边框,从而当放大形状时,其边框也将变粗。
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock>The first row of Grid.</TextBlock> <Viewbox Grid.Row="1"> <Canvas Width="200" Height="150"> <Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="10" Canvas.Top="10" Width="100" Height="50" /> <Rectangle Fill="Red" Stroke="Blue" Canvas.Left="20" Canvas.Top="70" Width="80" Height="50" /> </Canvas> </Viewbox> </Grid>
- Line 直线
Line形状表示连接一个点和另一个点的一条直线。对于直线,Fill属性不起作用,必须设置Stroke属性。
<Line Stroke="Green" X1="-10" Y1="130" X2="150" Y2="130" StrokeThickness="2" ></Line>
在直线中可以使用负坐标,从而可以在窗口中其它任意部分上绘制直线。
直线不能使用流内容模型,所以为直线设置Margin、HorizontalAlignment、VerticalAlignment属性是没有任何效果的。对于Polyline和Polygon形状也有同样的限制。 - Polyline 折线
通过Polyline类可以绘制一系列相互连接的直线,只需要使用Points属性提供一系列X和Y坐标。从技术角度讲,Points属性需要一个PointCollection对象,但在XAML中使用基于简单字符串的语法填充该集合。只需要提供一个点的列表,并在每个坐标间添加空格或逗号。
如下示例是绘制了一条直线,它从点(5,5)伸展到点(100,10):
<Polyline Stroke="Red" Points="5 5 100 10"></Polyline>
<Polyline Stroke="Red" Points="5,5 100,10"></Polyline>
- Polygon 多边形
实际上Polygon和Polyline是相同的,和Polyline一样,Polygon也有一系列坐标的Points集合,唯一的区别是Polygon形状添加最后一条线段,将起点的终点连接起来。可以使用Fill属性填充该形状的内部区域。对于线条从不相交的简单形状,填充其内部是很容易的,但有时会遇到更加复杂的形状,哪些部分是内部(并且应当被填充),以及哪些部分是外部并不明显。所以WPF为Polygon和Polyline提供了一个FillRule属性,它提供了两种方式来控制形状的填充区域:- EvenOdd:默认值,为了确定是否填充区域,WPF计算为了到达形状的外部必须穿过的直线的数量,如果是奇数,就填充区域,如果是偶数,就不填充区域。
- Nonzero:它和EvenOdd一样,先计算到达形状外部必须穿过的直线的数量,但是它会考虑经过的每条直线的方向,如果经过的直线中在某个方向上直线的数量和相反方向上直线数量的差值等于0就不会填充,不等于0才会填充。而Nonzero规则依赖于形状的绘制,而不是形状自身的外观,所以在以不同的Point顺序但相同的点集合绘制的形状的填充可能是不一样的。
- 直线线帽和直线交点
当绘制Line和Polyline时,可以使用 StartLineCap 和 EndLineCap 属性选择如何绘制直线的开始端和结束端(这些属性不影响其它形状,因为其它形状都是闭合的),它们有如下四种取值,除Flat外,其它三种设置都会增加直线的长度,额外的长度是直线宽度的一半:
除了Line外,所有形状都允许使用 StrokeLineJoin 属性扭曲它们的拐角,有三种取值,默认值是Miter使边缘尖锐,Bevel来切掉点边缘,Round使边缘平滑。当为较宽且角度非常小的直线拐角使用Miter设置时,尖锐的拐角会延伸很长距离,此时可以使用StrokeMiterLimit属性来限制拐角的长度,长出的部分会被自动剪切掉,该属性是一个系数,它是指用于锐化拐角的长度和直线宽度的一半的比值,比如直线的宽度为4,StrokeMiterLimit属性设置为2,则拐角的长度就为4/2*2=4(4/2指直线宽度的一半):
- 点划线
可以使用 StrokeDashArray 属性选择实线段的长度和断开空白的长度,分析下面的形状:
<Polyline Canvas.Top="10" Stroke="Blue" Points="10,30 30,0 90,40 80,10 90,10" StrokeDashArray="2 1" StrokeThickness="2"></Polyline>
- 像素对齐
WPF使用设备无关的绘图系统,在不同分辨率设置下的像素很少是整数,例如,在96dpi显示器上的50个像素,在120dpi的显示器上会变为62.5个像素,当绘制一条62.5个像素的红线时,WPF可以正常填充前62个像素,然后应用WPF的自动反锯齿特性为第63个像素使用红色和背景色进行着色,这会在形状边缘导致一个模糊区域,这未必是一个问题,实际上,根据正在绘制的图形类型,它可能看起来很正常,然而,如果不希望这种模糊,可以通过将UIElement类的SnapsToDevicePixels属性设置为true来启用称为像素对齐的特性,它会将尺寸舍入到最近的设备像素。 - 画刷
所有的画刷都位于System.Windows.Media名称空间中。
- LinearGradientBrush(线性渐变画刷)
<Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,0.5" SpreadMethod="Reflect"> <GradientStop Color="Blue" Offset="0"></GradientStop> <GradientStop Color="Yellow" Offset="0.5"></GradientStop> <GradientStop Color="Red" Offset="1"></GradientStop> </LinearGradientBrush> </Rectangle.Fill>
GradientStop:为了创建渐变,需要为每种颜色添加一个GradientStop,使用Color属性指定颜色,使用Offset指定偏移量。
StartPoint 和 EndPoint:它们指定渐变的开始点和结束点的坐标,但它们不是真实的坐标,而使用比例坐标系统。点 (0,0) 指定填充区域的左上角,点 (0,1) 指定填充区域的左下角,点 (1,0) 指定填充区域的右上角,点 (1,1) 指定填充区域的右下角。
SpreadMethod:默认值为Pad代表渐变之外的区域使用恰当的纯色填充;而值Reflect代表翻转渐变,从第二种颜色反向渐变到第一种颜色;值Repeat复制相同的颜色变化过程。 - RadialGradientBrush(径向渐变画刷)
<Rectangle.Fill> <RadialGradientBrush RadiusX="0.4" RadiusY="0.4" SpreadMethod="Repeat" Center="0.6,0.5" GradientOrigin="0.8,0.3"> <GradientStop Color="Azure" Offset="0"/> <GradientStop Color="Red" Offset="1"/> </RadialGradientBrush> </Rectangle.Fill>
GradientOrigin:此属性指定第一种颜色在渐变中的开始点,其默认值为 (0.5,0.5),此值表示填充区域的中心。
Center:它用于设置内部渐变圆的位置,默认情况下,Center被设置为 (0.5,0.5),该设置将限定圆的中心放置在填充区域的中央。并且该点同时也是渐变开始点。
RadiusX、RadiusY:它们决定了限定圆的尺寸,它们根据填充区域的对角范围进行度量,这意味着半径0.5定义了一个圆,该圆的半径是对角线长度的一半。 - ImageBrush(图像画刷)
<Rectangle.Fill> <ImageBrush ImageSource="images/happyface.jpg" Viewbox="0.1,0.1,0.8,0.8" AlignmentX="Left" Stretch="Uniform"/> </Rectangle.Fill>
ImageBrush.Stretch属性:如果设置为Uniform则为了适应容器在缩放图像时会保持图像的高宽比。设置为None以图像的自然尺寸绘制图像。
如果图像比填充区域小,图像会根据AlignmentX和AlignmentY属性进行对齐,未填充的区域会保持透明。
ImageBrush.Viewbox属性:使用它可以从图像上剪裁一部分进行填充,为此需要指定四个数值描述希望从源图像上剪裁并使用的矩形部分,并两个数值指定矩形开始的左上角,后两个数值指定矩形的宽度和高度,而Viewbox使用的是比例坐标系统,图像的左上角为(0,0),图像的右下角为(1,1)。而宽度0.4指源图像的宽度的0.4倍。
ImageBrush的平铺
可以使用Viewport属性设置每个平铺图像的尺寸,为了使用按比例尺寸平铺模式,必须将ViewportUnits属性设置为 RelativeToBoundingBox。然后使用在两个方向上的坐标范围都是从0到1的按比例坐标系统定义平铺图像的尺寸。下面的标记创建了一个从填充区域左上角(0,0)开始,并拉伸到中间点(0.5,0.5)的Viewport方框,因此填充区域总是包含4幅平铺图像,而不管填充区域的大小。
<ImageBrush ImageSource="images/tile.jpg" Viewport="0,0,0.5,0.5" TileMode="Tile"/>
<ImageBrush ImageSource="images/tile.jpg" Viewport="0,0,16,16" TileMode="Tile" ViewportUnits="Absolute"/>
- VisualBrush 画刷
使用VisualBrush可以获取一个元素的可视化内容,且使用该内容填充任何表面。下面的标记定义了一个按钮和一个复制该按钮的VisualBrush,但复制的按钮不能被单击,也不能通过任何方式与其进行交互,它只是复制了元素的外观。
VisualBrush会监视元素外观的变化,例如,如果复制了一个按钮的可视化外观,并且之后按钮获取了焦点,VisualBrush会使用新的可视化内容重新绘制填充区域。还可以对VisualBrush获取的可视化内容进行剪裁、拉伸、翻转和变换操作。 - BitmapCacheBrush 画刷
BitmapCacheBrush画刷在许多方面和VisualBrush画刷类似,VisualBrush提供了一个引用其它元素的Visual属性,而BitmapChcheBrush类提供了一个完全相同目的的Target属性。最大区别是BitmapChcheBrush画刷采用可视化内容并且要求显卡在显存中存储该内容,通过这种方式,当需要时可以快速的重新绘制内容,而不必要求WPF执行额外的工作。
<Button x:Name="btnCmd" Content="Button" Canvas.Left="120" Canvas.Top="10" Width="50" Height="18" FontSize="9"/> <Rectangle Width="50" Height="18" Canvas.Top="33" Canvas.Left="120"> <Rectangle.Fill> <BitmapCacheBrush Target="{Binding ElementName=btnCmd}"> <BitmapCacheBrush.BitmapCache> <BitmapCache/> </BitmapCacheBrush.BitmapCache> </BitmapCacheBrush> </Rectangle.Fill> </Rectangle>
- LinearGradientBrush(线性渐变画刷)
- 变换
变换是改变形状或元素使用的坐标系统来改变形状或元素绘制方式的对象,在WPF中,变换由继承自System.Windows.Media.Transform抽象类的类表示,所有变换都通过Transform类继承自Freezable类,所以它们支持自动更改通知功能,如果改变了在形状中使用的变换,形状会立即重新绘制自己。以下列出了这些类:- 变换形状
为了变换形状,需要将 RenderTransform属性指定为希望使用的变换对象,例如 ,如果要旋转形状,需要使用RotateTransform变换,并以度为单位提供旋转的角度,下面的示例将矩形围绕矩形的中心位置旋转10度。
<Rectangle Width="100" Height="4" Stroke="Black" Fill="Azure"></Rectangle> <Rectangle Width="100" Height="4" Stroke="Black" Fill="Azure"> <Rectangle.RenderTransform> <RotateTransform Angle="10" CenterX="50" CenterY="2"/> </Rectangle.RenderTransform> </Rectangle>
<Rectangle Width="100" Height="4" Stroke="Black" Fill="Azure"></Rectangle> <Rectangle Width="100" Height="4" Stroke="Black" Fill="Azure" RenderTransformOrigin="0.5 0.5"> <Rectangle.RenderTransform> <RotateTransform Angle="10"></RotateTransform> </Rectangle.RenderTransform> </Rectangle>
- 变换元素
RenderTransform和RenderTransformOrigin属性并不限制只能用于形状,实际上,Shape类的这些属性是从UIElement类继承而来的,这意味着所有WPF元素都支持这两个属性。FrameworkElement类还定义了一个与变换相关的LayoutTransform属性,但它是在布局之前执行其变换工作。RenderTransform和LayoutTransform的区别是:RenderTransform控制的元素在布局窗口中首先定位,然后在呈现之前进行变换,而LayoutTransform控制的元素首先进行变换,而后在布局窗口中定位。只有很少几个元素不能被变换,因为它们的呈现工作不是由WPF本身负责的,这些元素包括WindowsFormHost元素和WebBrower元素。
- 变换形状
- 透明
可以采用几种方式使元素具有半透明效果: - 设置元素的Opacity属性。每个元素,包括形状,都从UIElement基类继承了Opacity属性,不透明度是0到1之前的小数,1表示完全不透明,0表示完全透明,当使用这种方式设置不透明度时,它被应用整个元素的可见内容。
- 设置画刷的Opacity属性,每个画刷也从Brush基类继承了Opacity属性,以控制使用画刷绘制的内容的透明度。
- 使用具有透明Alpha值的颜色,所有alpha值小于255的颜色都是半透明的。
- 透明掩码
Opacity属性使元素的所有内容都是部分透明的,而OpacityMask属性可以使元素的特定区域透明或部分透明。OpacityMask接受任何画刷,画刷的alpha通道确定了什么地方是透明的,如下标记片段,为按钮的OpacityMask属性设置了一个线性渐变,第一个渐变色为不透明色,它对按钮的左部不会有任何影响,第二个渐变色为透明色,它将逐渐隐藏按钮的右部。OpacityMask相当于PhotoShop里的图层蒙板。
<Button x:Name="btnCmd" Content="Button" Canvas.Left="120" Canvas.Top="10" Width="50" Height="18" FontSize="9"> <Button.OpacityMask> <LinearGradientBrush EndPoint="1,0"> <GradientStop Color="Black" Offset="0"></GradientStop> <GradientStop Color="Transparent" Offset="1"></GradientStop> </LinearGradientBrush> </Button.OpacityMask> </Button>