【WPF学习】第三十九章 理解形状

  在WPF用户界面中,绘制2D图形内容的最简单方法是使用形状(shape)——专门用于表示简单的直线、椭圆、矩形以及多变形的一些类。从技术角度看,形状就是所谓的绘图图元(primitive)。可组合这些基本元素来建立更复杂的图形。数据库

  关于WPF中形状的重要细节是,它们都继承自FrameworkElement类。所以,形状是元素。这样会带来许多重要的结果:app

  •   形状绘制自身。不须要管理无效的状况和绘图过程。例如,当移动内容、改变窗口尺寸或改变形状属性时,不须要手动从新绘制形状。
  •   使用与其余元素相同的方式组织形状。换句话说,可在前面学过的任何布局容器中放置形状(尽管Canvas明显是最有用的容器,由于它容许在特定的坐标位置放置形状,当构建复杂的具备多个部分的图画时,这很重要)。
  •   形状支持与其余元素相同的事件。这意味着为了处理焦点、按下键盘、移动鼠标以及单击鼠标等,没必要执行任何额外工做。可以使用用于其余元素的相同事件集,并一样支持工具提示、上下文菜单和拖放操做。

1、Shape类ide

  每一个形状都继承自抽象类System.Windows.Shapes.Shape。下图显示了形状类的继承层次。工具

 图 WPF形状类布局

  正如上面看到的,相对来讲,只有不多一部分类继承自Shape类。Line、Ellipse以及Rectangle都很直观,Polyline是一系列相互链接的直线,Polygon是由一系列相互链接的直线造成的闭合图形。最后,Path类功能强大,能将多个基本形状组合成单独的元素。字体

  尽管Shape类自身不能执行任何工做,但它定义了少许的重要属性。下表列出了这些属性。编码

表 Shape类的属性spa

 2、矩形和椭圆设计

  矩形和椭圆是两个最简单的形状。为建立矩形或椭圆,须要设置你们熟悉的Height和Width属性(这两个属性继承自FrameworkElement类)来定义形状的尺寸,而后设置Fill或Stroke属性(或同时设置这两个属性)使形状可见。还可使用MinHeigth、MinWidth、HorizontalAlignment、VerticalAlignment以及Margin等属性。3d

  下面举一个简单示例,该例在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>
RoundedRectangles

 3、改变形状的尺寸和放置形状

  正如前面所知,赢编码尺寸一般不是建立用户界面的理想方法。它们会限制处理动态内容的能力,并会使应用程序本地化到其余语言变得更加困难。

  当绘制形状时,再也不老是关心这些问题。一般,须要更严格地控制形状的位置。然而,在许多状况下仍须要灵活一点设计。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"></RowDefinition>
        </Grid.RowDefinitions>

        <Ellipse Fill="Yellow" Stroke="Blue"></Ellipse>
        <Ellipse Fill="Yellow" Stroke="Blue" Grid.Column="1" Stretch="Uniform"></Ellipse>
        <Ellipse Fill="Yellow" Stroke="Blue" Grid.Column="2" Stretch="UniformToFill "></Ellipse>

        <TextBlock Grid.Row="1" TextAlignment="Center">Fill</TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="1" TextAlignment="Center">Uniform</TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Center">UniformToFill</TextBlock>
    </Grid>
</Window>
FillModes

 

   一般,将Stretch的值设置为Fill至关于将HorizontalAlignment和VerticalAlignment属性设置为Stretch。但若是选择为形状设置固定的宽度和高度,两者就有区别了。对于这种状况,会简单地忽略HorizontalAlignment和VerticalAlignment值。而Stretch设置仍然起做用——该属性决定如何在给定的范围内改变形状内容的尺寸。

  到目前位置,已看到如何改变Rectangle和Ellipse形状的尺寸,但如何准确地将它们放到指望的位置呢?WPF形状与其余元素使用相同的布局系统。然而,有些布局容器是不合适的。例如,一般不但愿使用StackPanel、DockPanel以及WrapPanel面板,由于它们都被设计为独立的元素。Grid面板更灵活一些,由于它容许在同一个单元格中放置任意多个元素(尽管不能在单元格中的不一样部分定位矩形和椭圆)。理想容器是Canvas,该容器要求使用Left、Top、Right或Bottom附加属性,为每一个形状指定坐标。这样能够彻底控制形状如何相互重叠:

<Canvas>
        <Ellipse Fill="Yellow" Stroke="Blue" Height="50" Width="100" Canvas.Top="50" Canvas.Left="100"></Ellipse>
        <Rectangle Fill="Yellow" Stroke="Blue" Height="50" Width="100" Canvas.Top="40" Canvas.Left="30"></Rectangle>
    </Canvas>

  若是使用Canvas容器,标签的顺序是很重要的。在上面的示例中,矩形叠加在椭圆之上,由于在标签列表中首先出现的是椭圆,因此首先绘制椭圆。

   请记住,Canvas容器不在须要占据整个窗口。例如,彻底能够建立一个Grid面板,并在该Grid面板的某个单元格中使用Canvas容器,对于在可自由流动的动态用户界面中锁定一小部分绘图逻辑,这是一种很是好的方法。

4、使用Viewbox控件缩放形状

  使用Canvas控件的惟一限制是图形不能改变自身的尺寸以适应更大或更小的窗口。对于按钮这很是合理(在这些状况下,按钮不改变尺寸),可是对于其余相似的图形内容,状况就未必如此了。

  对于此类状况,WPF提供了简便的解决方法。若是但愿联合Canvas控件的精确控制功能和方便的改变尺寸功能,可以使用Viewbox元素。

  Viewbox是继承自Decorator的简单类。该类只接受一个子元素,并拉伸或缩小子元素以适应可用的空间。固然,这个单一的子元素能够是布局容器,其中包含大量形状(或其余元素),这些元素将同步地改变尺寸。然而,Viewbox更长用于矢量图像而不是普通控件。

  尽管可在Viewbox元素中放置单个形状,但这并不能提供任何实际的优势。反而,当须要封装构成一幅图画(drawing)的一组形状时,Viewbox元素才有用处。一般,将在Viewbox控件中放置Canvas,并在Canvas面板中放置形状。

  下面的示例在Grid控件的第二行中放置了一个包含Canvas面板的Viewbox元素。Viewbox元素占用改行的整个高度和宽度。该行占用绘制自动改变尺寸的第一行剩余的全部空间,下面是标记:

<Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <TextBlock> The first row of a grid</TextBlock>
        <Viewbox Grid.Row="1" HorizontalAlignment="Left" MaxHeight="500">
            <Canvas Width="200" Height="150">
                <Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="10"  Canvas.Top="50"
               Width="100" Height="50" HorizontalAlignment="Left"></Ellipse>
                <Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30"  Canvas.Top="40"                 
                 Width="100" Height="50" HorizontalAlignment="Left"></Rectangle>
            </Canvas>
        </Viewbox>
    </Grid>

  下图显示了当改变窗口尺寸时,Viewbox控件如何调整自身。第一行没有变化。然而为填满额外控件,第二行进行了扩展。正如看到的,Viewbox控件中的形状也根据窗口增大的比例改变了他们的大小。

  

   默认状况下,Viewbox元素按比例地执行缩放,保持它所包含内容的纵横比。在当前示例中,这意味着即便包含行的形状发生了变化(变宽或变高),内部形状也不会变形。相反,Viewbox元素使用适应可用空间内部的最大缩放系数。然而,可以使用Viewbox.Stretch属性改变该行为。默认状况下,将该属性设置为Uniform。可将其改变为Fill,Viewbox元素中的内容会在两个方向上被拉伸以彻底适应可用空间,即便可能会破坏原来的绘图也会如此。还可经过使用StretchDirection属性得到更大的控制权。默认状况下,该属性被设置为Both,但可以使用UpOnly值建立只会增加而不会收缩超过其原始尺寸的内容,而且可使用DownOnly建立只会缩小而不会增加的内容。

  为时Viewbox元素执行其缩放工做,须要可以肯定两部分信息:(若是不放在Viewbox元素中)内容应当具备的原始尺寸和但愿内容具备的新尺寸。

  第二个细节——新尺寸——很是简单。Viewbox元素根据Stretch属性,让其内部的内容使用全部可用空间,这意味着Viewbox元素越大,其内部的内容就越大。

  第一个细节——原始尺寸,不使用Viewbox空间时的尺寸——隐含在定义嵌套内容的方式中。在前面的示例中,Canvas的尺寸被明确设置为200X150单位大小。所以,Viewbox从该开始点缩放图像。例如,椭圆最初是100单位宽,这意味着它占用Canvas面板一半的绘图空间。随着Canvas控件的增大,Viewbox元素会遵循这些比例,而且椭圆继续占用一半的可用控件。

  然而,若是删除Canvas控件的Width和Height属性,分析会发生什么状况。如今,Canvas控件的尺寸被设置为0X0单位大小,因此Viewbox控件不能改变它的尺寸,而且嵌套在其中的内容不会显示(这与只使用Canvas控件时的行为不一样。由于尽管Canvas控件的尺寸仍设置为0X0,但只要Canvas.ClipToBounds属性没有被设置为true,就仍然容许在Canvas控件以外的区域绘制形状。而Viewbox控件不能容忍这一错误)。

  如今分析一下,若是在按比例改变尺寸的Grid面板的单元格中封装Canvas面板,而且没有指定Canvas面板的尺寸,状况又会怎样。若是没有使用Viewbox元素,该方法可工做得很好——拉伸Canvas面板以填充单元格,而且内部的内容是可见的。但若是将全部内容放在Viewbox元素中,这种方法就会失效。Viewbox控件不能肯定最初尺寸,所以不能响应地改变Grid面板的尺寸。

  可经过直接在能自动改变尺寸的容器(如Grid面板)中放置特定的形状(如Rectangle和Ellipse)来避免这个问题。而后Viewbox控件就能评估Grid面板为了适合其内容所需的最小尺寸,而且缩放Grid面板以适应可用空间。然而,在ViewBox元素中获取真正所但愿的尺寸的最简单方法,是在具备固定尺寸的元素中封装内容,能够是Canvas面板、按钮或其余控件。这样,固定尺寸就变成了Viewbox控件进行计算所须要的原始尺寸。以这种方式硬编码尺寸不会限制布局的灵活性,由于Viewbox元素根据可用空间和布局容器按比例改变尺寸。

5、直线

  Line形状表示链接一个点和另外一个点的一条直线。起点和重点由4个属性设置:X1与Y1(用于第一个点)和X2与Y2(用于第二个点)。例如,下面是一条从点(0,0)伸展到点(10,100)的直线:

<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"></Line>

   对于直线,Fill属性不起做用,必须设置Stroke属性。

  在直线中使用的坐标是相对于放置直线的矩形区域左上角的坐标。例如,若是在StackPanel面板上放置上面的直线,坐标(0,0)指向在StackPanel面板上放置该矩形区域的位置,这多是窗口的左上角,也可能不是。若是StackPanel面板的Margin属性值不为0,或直线在其余元素以后,直线的开始点(0,0)与窗口顶部会有必定的距离。

  然而,在直线中使用负坐标值是很是合理的。实际上,可为直线使用能超出为直线保留的空间的坐标,从而在窗口的其余任意部分绘制直线。对于到目前位置介绍的Rectangle和Ellipse形状;这是不可能的。然而,这一模型也有缺点,直线不能使用流内容模型。这意味着为直线设置Margin、HorizontalAlignment以及VerticalAlignment属性是没有意义的,由于它们没有任何效果。对于Polyline和Polygon形状具备相同的限制。

  若是在Canvas面板上放置了Line形状,那么仍应用附加的位置属性(如Top和Left)。它们决定直线的开始位置。换句话说,两个直线坐标被平移必定的距离。分析下面的直线:

<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"
    Canvas.Left="5" Canvas.Top="100"/>

   这条直线从点(0,0)伸展到点(10,100),使用的坐标系统将Canvas控件上的点(5,100)做为点(0,0)。这至关于下面不使用Top和Left属性的直线:

<Line Stroke="Blue" X1="5" Y1="100" X2="15" Y2="200"/>

  当在Canvas面板上放置Line形状时,是否使用位置属性由本身决定。一般,可经过选择好的开始点简化直线的绘制,还可使移动部分图画变得容易。例如,若是在Canvas面板的特定位置绘制几条直线和其余形状,相对于附近的点绘制它们是不错的主意(经过使用相同的Top和Left坐标)。经过这种方法,可根据须要将整个图画移到新的位置。

6、折线

  能够经过Polyline类绘制一系列相互链接的直线。只须要使用Points属性提供一系列X和Y坐标。从技术角度看,Points属性须要使用PointCollection对象,但在XAML中使用基于简单字符串的语法填充该集合。只须要提供点的列表,并在每一个坐标之间添加空格或逗号。

  Polyline形状可能只有两个点。例以下面的Polyline形状,从点(5,100)伸展到点(15,200):

<Polyline Stroke="Blue" Points="5 100 15 200"/>

  为便于阅读,可在每一个X和Y坐标之间使用逗号:

<Polyline Stroke="Blue" Points="5,100 15,200"/>

  下面是绘制的更复杂Polyline形状。点不断右移,并在更高的Y值——好比(50,160),和更低的Y值——好比(70,130)之间摆动:

<Canvas>
    <Polyline Stroke="Blue" StrokeThickness="5" 
        Points="10,150 30,140 50,160 70,130 90,170 110,120 130,180 150,110 170,190 190,100 210,240" >
    </Polyline>
</Canvas>

  下图显示了最终绘制的线条。

 

   对于这个示例,经过代码使用各类相应地自动增长X和Y值的循环填充Point集合可能更容易。若是须要建立高度动态的图形,事实倒是如此——例如,根据从数据库中提取的数据集改变其外观的图标。可是,若是只是但愿构建固定的图形内容,就根本不须要形状的具体坐标。相反,可以使用另外一个工具,如Express Design,绘制恰当的图形,而后处处到XAML。

7、多边形

  实际上,Polygon和Polyline是相同的。和Polyline类同样,Polygon类也有包含一系列坐标的Points集合。惟一的区别是:Polygon形状添加最后一条线段,将最后一个点链接到开始点(若是最后一个点就是第一个点,Polygon类和Polyline类就没有区别了)。可以使用Fill画刷填充该形状的内部区域。经过修改上一节的示例,显示Polygon:

<Canvas>
        <Polygon Stroke="Blue" StrokeThickness="5" Fill="Yellow" 
        Points="10,150 30,140 50,160 70,130 90,170 110,120 130,180 150,110 170,190 190,100 210,240" >
        </Polygon>
</Canvas>

  最终效果图以下所示:

 

   对于线条从不相交的简单形状,填充其内部很容易作到。但有时会遇到更复杂的Polygon形状,哪些部分属于内部(而且应当被填充)以及哪些部分属于外部并不明显。

  下面一个示例,该形状的特色是一条线段和其余多条线段相交,可能但愿填充也但愿不填充中央的不规则区域。显然,可经过将该图像分割成更小的形状来准确地控制填充区域。但不须要这么作。

 <Polygon Stroke="Blue" StrokeThickness="1" Fill="Yellow" 
        Points="15,200 68,70 110,200 0,125 135,125" >
</Polygon>

 

   每一个Polygon和Polyline形状都有FillRule属性,该属性用于从两种填充方法中选择一种来填充区域。默认状况下,FillRule属性被设置为EventOdd。为了肯定是否填充区域,WPF计算为了到达形状的外部必须穿过的直线的数量。若是是奇数,就填充区域;若是是偶数,就不填充区域。对于上图显示的中央区域,为了到达形状外部就必须通过两条直线,因此不会填充该区域。

  WPF还遵循NonZero填充规则,该规则更加复杂。本质上,当使用NonZero填充规则时,WPF使用和EventOdd填充规则相同的方法计算穿过的直线的数量,可是会考虑通过的每条直线的防线。若是在通过的直线中,在某个方向上(好比从左项右)直线的数量等于相反方向(从右向左)上直线的数量,就不会填充区域。若是这两个直线数量的差不为0,就填充区域。对于上个示例,若是将FillRule属性设置为NonZero,J就会填充内部区域。

<Polygon Stroke="Blue" StrokeThickness="1" Fill="Yellow" 
               FillRule="Nonzero"
         Points="15,200 68,70 110,200 0,125 135,125" >
</Polygon>

 

   有关NonZero规则的复杂问题在于填充设置依赖于形状的绘制,而不是形状自身的外观。

8、直线线帽和直线交点

  当绘制Line和Polyline形状时,可以使用StartLineCap和EndLineCap属性选择如何绘制直线的开始端和结束端(这些属性不影响其余形状,由于其余形状都是闭合的)。

  StartLineCap和EndLineCap属性一般都设为Flat,这意味着直线在它的最后坐标处当即终止。其余选择包括Round(该设置会平滑地绘制拐角),Triangle(绘制直线的两条侧边最后交于一点)以及Square(该设置使直线端口具备尖锐边缘)。这两个设置都会增长直线的长度——换句话说,它们使直线超出了其余状况下的结束位置。额外的距离是直线宽度的一半。下图显示了直线端口处不一样线帽之间的区别。

<Window x:Class="Drawing.LineCaps"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LineCaps" Height="333" Width="376">
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Polyline Stroke="Blue" StrokeThickness="15" StrokeEndLineCap="Flat" SnapsToDevicePixels="True"
      Points="10,10 30,0 50,20 90,10 200,10" >
        </Polyline>
        <TextBlock Grid.Column="1">Flat Line Cap</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="1" StrokeEndLineCap="Square" SnapsToDevicePixels="True"
      Points="10,10 30,0 50,20 90,10 200,10" >
        </Polyline>
        <TextBlock Grid.Row="1" Grid.Column="1">Square Line Cap</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="2" StrokeEndLineCap="Round" SnapsToDevicePixels="True"
      Points="10,10 30,0 50,20 90,10 200,10" >
        </Polyline>
        <TextBlock Grid.Row="2" Grid.Column="1">Round Line Cap</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="3" StrokeEndLineCap="Triangle"  SnapsToDevicePixels="True"
         Points="10,10 30,0 50,20 90,10 200,10" >
        </Polyline>
        <TextBlock Grid.Row="3" Grid.Column="1">Triangle Line Cap</TextBlock>
    </Grid>
</Window>
LineCaps

 

   除Line形状外,全部形状都运行使用StrokeLineJoin属性扭曲它们的拐角,有4中选择。Miter值(默认值)使用尖锐的边缘,Bevel值切掉点边缘,Round值平滑地过渡边缘,Triangle值显示尖点。下图显示StrokeLineJoin效果图。

<Window x:Class="Drawing.LineJoins"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LineJoins" Height="431" Width="303"
    >
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Polyline Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Bevel" SnapsToDevicePixels="True"
      Points="10,60 30,10 50,70 90,40" >
        </Polyline>
        <TextBlock Grid.Column="1" VerticalAlignment="Center">Bevel Line Join</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="14" Grid.Row="1" StrokeLineJoin="Round"  SnapsToDevicePixels="True"
      Points="10,60 30,10 50,70 90,40" >
        </Polyline>
        <TextBlock Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">Round Line Join</TextBlock>

        <Polyline Grid.Row="2" Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Miter"  StrokeMiterLimit="1"
              SnapsToDevicePixels="True" 
      Points="10,60 30,10 50,70 90,40" >
        </Polyline>
        <TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Miter Line Join</TextBlock>

        <Polyline Grid.Row="3" Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Miter"  StrokeMiterLimit="3"
                SnapsToDevicePixels="True" 
        Points="10,60 30,10 50,70 90,40" >
        </Polyline>
        <TextBlock Grid.Row="3" Grid.Column="1" VerticalAlignment="Center">Miter Line Join With Limit of 3</TextBlock>

    </Grid>
</Window>
LineJoins

 

   当为较宽而且角度很是小的直线拐角使用尖锐的边缘时,尖锐的拐角会不切实际地延伸很长一段距离。对于这种状况,可以使用Bevel或Round设置修剪拐角。也可以使用StrokeMiterLimit属性,当达到特定的最大长度时,该属性自动地剪切边缘。StrokeMiterLimit属性是一个系数,该系数是用于锐化拐角的长度和直线宽度的一半的比值。若是将该属性设置为1(这是默认值),就容许拐角延长直线宽度的一半距离。若是设置为3,就容许拐角延长直线宽度的1.5倍距离。如上图的最后一条直线使用了更高的锐化范围,从而具备更狭长的拐角。

9、点划线

  除了为形状的边框绘制乏味的实线外,还可绘制点划线(dashed line)——根据指定的模式使用空白断开的直线。当在WPF中建立一条点划线时,不限制进行特定的预先设置。相反,可经过设置StrokeDashArray属性来选择实线段的长度和断开空白(空白)的长度。例如,分析下面的这条直线:

<Polyline Stroke="Blue" StrokeThickness="10"
              StrokeDashArray="1 2" 
      Points="10,30 60,0 90,40 120,10 350,10" SnapsToDevicePixels="True">
        </Polyline>

  这条点划线的实线段长度值为1,空白长度值为2.这些值都是相对于直线宽度的。所以,若是直线宽度是10个单位(本例中设置的宽度),实线部分的长度就为10个单位,后面跟着20个单位的空白部分。直线在整个长度中重复该模式。

  另外一方面,若是像下面这种交换这两个值:

StrokeDashArray="2 1"

  直线的实现部分就是20个单位长,空白部分为10个单位长。下图显示了这着两条直线。正如将会注意到得,当一条很是粗的线段位于拐角处时,它会被不均匀地割断。

<Window x:Class="Drawing.DashedLines"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="DashedLines" Height="401" Width="589"
    >
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>

        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Polyline Stroke="Blue" StrokeThickness="10"
              StrokeDashArray="1 2" 
      Points="10,30 60,0 90,40 120,10 350,10" SnapsToDevicePixels="True">
        </Polyline>
        <TextBlock Grid.Column="1" VerticalAlignment="Center">Dash Pattern "1 2"</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="1" 
               StrokeDashArray="2 1" SnapsToDevicePixels="True"
      Points="10,30 60,0 90,40 120,10 350,10" >
        </Polyline>
        <TextBlock Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">Dash Pattern "2 1"</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="2"
              StrokeDashArray="5 0.2 3 0.2" SnapsToDevicePixels="True"
      Points="10,30 60,0 90,40 120,10 350,10" >
        </Polyline>
        <TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Dash Pattern "5 0.2 3 0.2"</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="3" SnapsToDevicePixels="True"
              StrokeDashArray="3 0.5 2"
  Points="10,30 60,0 90,40 120,10 350,10" >
        </Polyline>
        <TextBlock Grid.Row="3" Grid.Column="1" VerticalAlignment="Center">Uneven Dash Pattern "2 0.5 2"</TextBlock>

        <Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="4" SnapsToDevicePixels="True"
             StrokeDashArray="1 2"  StrokeDashCap="Round"
 Points="10,30 60,0 90,40 120,10 350,10" >
        </Polyline>
        <TextBlock Grid.Row="4" Grid.Column="1" VerticalAlignment="Center">Dash Pattern with Rounded Caps</TextBlock>
    </Grid>
</Window>
DashedLines

 

   不见得非要使用整数值。例如,下面的StrokeDashArray属性设置彻底合理:

StrokeDashArray="5 0.2 3 0.2"

  这样的设置提供了更复杂序列——5X10单位长的点划线,而后是0.2X15单位长的空白,接下来是3X10单位长的实线和0.2X10单位长的空白。在该序列的尾部,直线从头开始重复该模式。

  若是为StrokeDashArray属性提供的数值的个数是奇数,将发生一个有趣的现象。分析下面的示例:

StrokeDashArray="3 0.5 2"

  当绘制该直线时,WPF首先绘制3倍直线宽度长的实线,而后是0.5倍直线宽度长的空白,在接下来时2呗直线宽度长的实线。但当在从头开始重复该模式时,首先是3倍直线宽度长的空白,接着是0.5被直线宽度长的实线,依次类推。本质上,点划线在线段和空白之间交替其模式。

  若是但愿从中间开始绘制模式,可以使用StrokeDashOffset属性,该属性是一个从0开始的索引,该索引指向StrokeDashArray中的某个值。例如,在上一个示例中,若是将StrokeDashOffset属性设置为1,直线将从0.5倍直线宽度长的空白开始。若是设置为2,直线将会从2倍直线宽度长的线段开始。

  最后,可控制如何为直线的断开边缘添加线帽。一般是一条平直的边缘,但可将StrokeDashCap属性设置为Bevel、Square以及Triangle等值。请记住,全部这些设置都会在点划线的端点增长直线宽度的一半长距离。若是没有考虑这一额外的距离,最终可能会使点划线相互重叠。解决方法是增长额外的空白以进行补偿。

10、像素对齐

  WPF使用与设备无关的绘图系统。为字体和形状等内容指定的数值使用“虚拟”像素,在一般的96dpi显示器上,“虚拟”像素和正常像素的大小相同,可是在更高dpi的显示器上其尺寸会被缩放。换句话说,绘制50像素宽的矩形,根据设备的不一样,实际上可能使用更多或更少的像素进行渲染。设备无关单位和物理像素之间的转换会自动进行,而且一般根本不须要考虑这个问题。

  不一样dpi设置之间的像素比不多是整数。例如, 在96dpi显示器上的50个像素,在120dpi显示器上会变为62.4996个像素(这不是一种错误的状况——实际上,当以设备无关单位提供数值时,WPF始终运行使用非整数的双精度值)。显然,没法在像素之间的点上放置一条边缘。WPF使用反锯齿特性进行补偿。例如,当绘制一条62.4992个像素长的红线,WPF可正常填充前62个像素,而后使用直线颜色(红色)和背景色之间的颜色为第63个像素着色。但在此存在一个问题。若是正在绘制直线、矩形或具备直角的多边形,这种自动反锯齿特性会在形状边缘致使一片模糊区域。

  可能会认为,仅在显示分辨率不是96dpi的显示器上运行应用程序时,才会出现这个问题。然而,状况未必如此,由于全部形状均可以均可以使用小数值得长度和坐标设置尺寸,这会引发相同的问题。在绘制形状时,尽管可能没有使用小数值,但能够改变尺寸的形状——那些由于尺寸依赖于容器或被放在Viewbox元素中而被拉伸的形状——尺寸一般几乎老是小数。相似地,奇数单位宽的直线在两侧的像素数也是小数值。

  模糊边缘问题未必是问题。实际上,根据正在绘制的图像类型,它可能看起来很正常。然而,若是不但愿这种行为,可告诉WPF不要在特定形状使用反锯齿特性进行处理,反而WPF会将尺寸舍入到最近的设备像素。可经过将UIElement类的SnapsToDevicePixels属性设置为true来启动这个称为像素对齐(pixel Snapping)的特性。

  为查看二者之间的区别,可观察下图中被放大的窗口,该窗口比较两个矩形。底部的矩形使用了像素对齐特性,而顶部的矩形没有使用。若是仔细观察,就会发如今未使用像素对齐特性的矩形的顶部和左部有一条很细的淡色边缘。

<Window x:Class="Drawing.PixelSnapping"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="PixelSnapping" Height="300" Width="300">
    <Grid Margin="7">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <TextBlock VerticalAlignment="Center">Not Snapped:</TextBlock>
        <Rectangle SnapsToDevicePixels="False" Grid.Column="1"
      Margin="10" Height="10" Fill="Red"></Rectangle>

        <TextBlock VerticalAlignment="Center" Grid.Row="1">Snapped:</TextBlock>
        <Rectangle SnapsToDevicePixels="True" Grid.Column="1" Grid.Row="1"
      Margin="10" Height="10" Fill="Red"></Rectangle>
    </Grid>
</Window>
PixelSnapping

相关文章
相关标签/搜索