zoukankan      html  css  js  c++  java
  • 《Programming WPF》翻译 第7章 绘图 (2)

     

    7.3 笔刷和钢笔

    为了在屏幕上绘制一个图形,WPF需要知道你想要为图形填充什么颜色以及如何绘制它的边框。WPF提供了一些Brush类型支持各种绘图样式。Pen类增加这些笔刷以提供边框的厚度和样子。

    在这一章,我们将要看一下各种类型的笔刷和钢笔类。可是,由于所有的笔刷和钢笔类最终是关于指出在哪里使用哪一种颜色,以及如何将它们联合在一起,我们必须首先看一下眼色是如何被表示的。

    7.3.1 颜色

    WPFSystem.Windows.Media命名空间中使用了Color结构来表示一种颜色。注意到如果你以往工作于Windows FormsASP.NETGDI+Color机构是不同于那些技术使用的机构的——它们使用了System.Drawing命名空间的Color结构。WPF引进了这种新的Color结构,是因为它可以工作于浮点形式的颜色值——支持更高的颜色精度,以及更好的弹性。

    Color颜色结构使用了四个数字,或者说是通道,来表示一种颜色。这些通道是红、绿、蓝以及Alpha。红、绿、蓝通道是计算机图形学中传统的表示颜色的方式。(这是因为颜色屏幕通过将这三种基本颜色混合在一起来工作。)一个0值表示颜色部分完全不存在;三个通道都是0对应黑色。Alpha通道表示颜色不透明度的等级。Color可以是不透明的、完全不透明的、以及在这两种极限值之间的任意值。WPF的合成引擎充分支持透明度,因此任何图形都可以被绘制为带有透明等级。0值用来表示完全透明。

    Windows是传统的使用24位颜色信息,每个通道8位颜色,以表示“真彩色”;同时还有32位带有透明度的真彩色。这仅仅是充分的关于计算机屏幕的平均水平。常规计算机表现的颜色和亮度范围是,24位颜色对于大多数用途总是足够的。然而,对于很多图形化应用程序,这是不够的。例如,电影,相比于计算机屏幕,可以提供一个更宽范围的亮度,还有24位颜色对于将电影作为输出媒介的图形化工作,是完全不足够的,同样也不适用于很多医疗影像应用程序。即使对于计算机和视频影像,24位颜色也会引起一些问题。如果图像需要经过很多阶段的处理,这些增强24位原材料的限制。

    WPF因此在它的颜色表示中支持一个相当高等级的细节。每个颜色通道使用16位代替8位。这种Color结构仍然在需要的地方支持8位通道的使用,因为大多数图像软件依赖于这样的表示。Color通过ARGB属性暴露了这些8位通道,这些属性接受0255间的值。这种更高级的定义表示法——通过ScAScRScGScB属性——也是有效的,这些属性代表了01范围内的单精度的浮点值。


    #ScA、ScR、ScG和ScB属性中的”Sc”涉及了这样的事实,它们支持标准的“Extended RGB colour spacescRGB”,颜色空间定义在IEC61966-2-2规范中。”sc”是“scene”的缩写,因为这通常是一个scenereferred的颜色空间。这意味着scRGB空间的颜色值代表了原始图像的颜色。这是不同于计算机图像通常是如何存储的。传统上说,我们已经使用了outputreferred颜色空间,颜色值不需要在显示于目标设备之前映射到那里。

    使用Outputreferred颜色空间可以有效地工作,只要它们恰好设定了输入设备的目标。然而,scenereferred颜色空间保护了所有的可利用信息在捕获或生成图像时。为了更高精度的颜色表示,scenereferred模型因此是更清晰的。即使它们工作起来有点低效率。

    这里还有一个Color类,它提供了一组标准的命名颜色,包括所有旧有的喜好,如PapayWhip、BurlyWood、LightGoldenrodYellow和Brown。

    7.3.2 SolidColorBrush

    SolidColorBrush是最简单的笔刷。它使用一种颜色给整个区域上色。它只有一个属性,Color。注意到这个颜色允许使用透明度,尽管在单词中使用了Solid。

    我们已经看到广泛使用SolidColorBrush,即使我们并未提供名称涉及它。这是因为WPF创建这种类型的笔刷,一旦你详细指出了标记中颜色的名称。如果你

    大都在标记中工作,你会很少需要指出你需要一个SolidColorBrush,因为你会得到一个默认值。(通常你会详细指明它以完整的词,唯一的原因是你想使用笔刷属性的数据绑定)。考虑下面这个示例:

    <Rectangle Fill=”Yellow” Width=”100” Height=”20” />

    xaml编译器会认出Yellow为Color类中一个标准命名的颜色,它会提供一个合适的SolidColorBrush。(参见附录A获取更多xaml映射字符串到属性值的信息)。这就不需要创建一个笔刷,因为存在一个Brushes类,在Colors中为每一个命名颜色提供了一组笔刷。

    如果你的标记使用了数字颜色值,你还可以被提供一个SolidColorBush。示例7-26显示了各种各样带有数字颜色值的示例。它们都开始于一个#记号,并包含16进制的数字。一个由3个数字组成的号码,每一个数字分别表示红、黄、蓝。四个数字的号码解释为alpaa、红、黄、蓝。这些是紧凑的格式,但是仅为每个通道提供了4位。6到8个数字的号码允许RGB或ARGB各自为每个通道8位。(为了开发完全的16位精确度的scRGB,你需要使用属性-元素语法来设置属性。这里没有简化的文本。参见附录A获取更多关于xaml属性-元素的语法信息)

    示例7-26

    SolidColorBrush是轻量的和直接的。然而,它导致了相当flat-looking的可视化。WPF提供更多有趣的笔刷,如果你想制作自己的用户界面——看起来更吸引人的。

    7.3.3 LinearGradientBrush

    使用LinearGradientBrush,被绘制的区域从一种颜色过渡为另一种颜色,或者即使一个颜色的序列。图7-32显示了一个简单的示例。

    图7-32

    这个笔刷从黑色淡出为白色,开始于左上角,结束于右下角。这种淡出总是以直线进行,你不能进行曲线的过渡,因此命名为Linear”。示例7-27显示了图7-32的标记。

    示例7-27

    StartPoint和EndPoint属性指明了颜色过渡的开始和终结位置。这些坐标是相对于被填充区域的,因此(0,0)是左上角,(1,1)是右下角,如图7-33所示。(注意到如果笔刷绘制或宽或窄的区域,坐标系统从而是积压的)

    图7-33

    示例7-27使用了属性-元素语法来初始化笔刷。在这个特定的示例中,这不是严格必要的,因为xaml支持更复杂的语法。这严密地等价于示例7-27:

    <Rectangle Width=”80” Height=”60” Fill=”LinearGradient 0, 0 1,1 Black White” >

    这种紧凑地语法允许设置最重要的方面。两对数字对应到StartPoint和EndPoint属性,以及剩余地两个条目是颜色名称。如果你想这种渐变是垂直的或水平的,你可以使用更简单的语法。

     

    <Rectangle Width=”80” Height=”60” Fill=”VerticalGradient Black White” />

    <Rectangle Width=”80” Height=”60” Fill=”HorizontalGradient Black White” />

    在所有这些字符串表示中,开始和结束颜色都是详细指定的。这些符合示例7-27中的GradientStop元素。注意到在充分延伸的复合属性的版本中,每个GradientStop都有像Color的一个Offset属性。这支持了更详细的样式来完成字符表达式不能做的事情。它允许填充来传递多个颜色。示例7-28显示了带有多个颜色的LinearGradientBrush

    示例7-28

    结果显示在图7-34中。注意到早期显示的速记字符串的语法,并不支持多个颜色值。如果你想要这个效果,你不得不充分地使用属性-元素语法。

    示例7-34

    LinearGradientBrush经常用于添加深度感到用户界面。示例7-29显示了一个典型的例子。它仅使用了两个形状——一对圆角矩形元素。(Grid不直接对外观产生影响。这使得调整图形的大小更容易——改变gridWidthHeight将会引起所有矩形的适当地调整大小。)第二个矩形是渐变填充的,从部分透明的白色渐变到完全透明的颜色,这将提供一个有趣的可视化效果。

    示例7-29

    7-35显示了结果。这是极其简单的一个图形,只包括两个形状。渐变填充的使用增加了深度,否则这些形状是不会传输的。

    7-35

    7.3.4 RadialGradientBrush

    RadialGradientBrush非常类似于LinearGradientBrush。它们都允许转变经历一系列的颜色。但是当LinearGradientBrush用直线绘制这些转变时,RadialGradientBrush渐变从一个外部的开始点,到一个椭圆的边界。这就展开了更多的机会使你的用户界面看起来不是很单调,如实例7-30所示。

    示例7-30

    RadialGradientBrush使用一个GradientStop列表对象来决定填充进行的颜色,正如LinearGradientBrush。这个示例使用了RadiusXRadiusY属性来决定椭圆边界的大小,以及Center属性来设置椭圆的位置。这里选择的值使得填充边界完全的适合这个形状,正如示例7-36所示。这个形状落在了边界的外面,它的区域由最后的GradientStop的颜色填充。注意到填充的焦点在左边。这是因为GradientOrigin已经被设置了。(默认的焦点在椭圆中间。)

    7-36

    示例7-30使得看到RadialGradientBrush属性的效果更容易,但这不是一个激动人心的例子。示例7-31显示了有点挑战的东西。这类似于示例7-29,它使用了少量的带有渐变填充的形状,来传输深度感和反射感,但是这次使用的是放射性填充。

    示例7-31

    这次,使用了三种椭圆,其中两种使用了RadialGradientBrush填充,另一种使用了LinearGradientBrush绘制轮廓。第一个椭圆的填充,创建了发光体在图中的下方。第二个椭圆的填充添加了一个反光在图形的上方。第三个椭圆绘制了一条贝赛尔曲线在椭圆的外围。结果如图7-37所示。放射性填充建议使用一个曲面以及给这个图形一个些微透明的外观。

    7-37

    7.3.5 ImageBrushVisualBrushDrawingBrush

    使用某种类型的样式或者图片填充形状,这经常是有用的。WPF提供了三种笔刷,从而允许我们使用无论什么图形作为笔刷。ImageBrush让我们使用一张图片进行绘制。而DrawingBrush允许我们使用可伸缩的图形。VisualBrush允许我们使用任意的标记作为笔刷的图像。我们可以,有效地,使用我们的用户界面的一部分,来绘制另一部分。
     

    7.3.5.1 TiteBrush

    ImageBrushVisualBrushDrawingBrush都使用源图片的某种形式来进行绘制。它们的基类,TiteBrush,决定了如何延展源图片来填充有效的空间,是否要重复图像,以及如何定位形状中的一个图像。(TiteBrush是一个抽象的基类,因此你可以直接使用它。它存在用来为ImageBrushVisualBrushDrawingBrush定义公共的API

    7-38显示了默认的TiteBrush行为。这个图像显示了3个矩形,为了你可以看到当笔刷变宽或变窄时发生了什么,同样的效果当笔刷形状匹配目标区域的形状。所有这三个矩形都使用ImageBrush绘制的,明确指出了所需的图像。

    7-38

    当图7-38用一个ImageBrush绘制的时候,这个行为将会是和任何TiteBrush一样。不久我们将要看到ImageBrush更多的细节,但是目前示例7-32显示了基本的用法。

    示例7-32

    笔刷伸展了源图像来填充有效的区域。我们可以通过修改笔刷的Stretch属性来改变这个行为。默认值为Fill,但是我们可以通过指定为None以原始大小显示图像,正如示例7-33所示。

    示例7-33

    这就保持了屏幕的宽高比,但是如果图像很大,它就会简单的裁剪以适应有效的空间,正如示例7-39

    7-39

    为了显示这些图片,你可能更想伸展图像以匹配有效的空间,而不用扭曲屏幕的宽高比。TiteBrush支持Uniform伸展模式,显示如图7-40。缩减源图像从而使它完全在有效的空间里。

    7-40

    Uniform伸展模式典型地导致了图像小于被填充的区域。默认的行为是绘制图像,无论对齐属性指明在哪里,以及使余下的区域为透明的。然而,你可以为多余的空间选择其他的行为——通过TiteMode属性。默认为None,但是如果你指定了Tite,如示例7-34,这个图像就会被重复填充多余的空间。

    示例7-34

    7-41显示了Uniform伸展模式和Tite图块模式的效果。这里有一个潜在的关于图块的问题。这经常使非常明显的在每一个图块开始重复的地方。如果你的目标使简单地用一种纹理填充一个区域,这种不连续可能有点刺眼。为了减轻这个问题,TiteBrush提供了3种其它图块模式:FlipXFlipYFlipXY。这些模式交替镜像图形,如图7-42所示。虽然镜像可以减少图块间地不连续,对于一些源文件而言,它可以非常充分地改变笔刷的外观。

    7-41

    7-42

    一个折中的使用图块的方法是,伸缩图片从而它完全填充有效的空间,同时保持了屏幕的宽高比,以及如果需要就在一个方向上裁减。UniformToFill伸展模式实现了这个方法,显示如图7-43

    如果你要使用某种不重复样式的模式来填充一个区域,UniformToFill是最恰当的,因为它保证了可以绘制整个区域。这可能是不太恰当的——如果你的目标是简单的显示一个图像,如图7-43所示,这种伸缩模式会在必要的时候裁剪图像。如果你想要显示整张图片,Uniform是最好的选择。

    7-43

    Fill之外,所有的伸展模式存在一个额外的问题:如何定位这个图像。使用NoneUniformToFill,裁剪动作发生了,因此WPF需要决定要显示哪一部分。使用Uniform,图片可能比需要填充的空间小,因此WPF需要决定把它放在哪里。

    图片默认是居中的。在示例中(图7-39和图7-43),最中间的部分显示在图片被裁剪的位置。在Uniform的情形中,在图像比被绘制区域小的地方,图像被放置在区域中间位置(图7-40)。你可以使用AlignmentXAlignmentY属性来改变这一点。这两个属性可以分别设置为LeftMiddleRightTopMiddleBottom。示例7-35再次显示了UniformToFill模式,但是折椅此使用了LeftBottom的对齐方式。图7-44显示了结果。

    示例7-35

    7-44

    伸展和对齐属性是方便于使用的,但是它们不允许你选择在图形的任意部分设置焦点或详细指出缩放因素。TileBrush通过ViewBoxViewportViewportUnits来支持这些样式。

    ViewBox属性选择显示图像的一部分。默认的,这个属性设置为环绕整张图片,但是你可以改变它的焦点在一个特定的部分。图7-45显示了UniformToFill模式,但是使用一个ViewBox设置放大车子的前面部分。

    7-45

    如示例7-36所示,ViewBox被详细指定为4个数字。前面两个是ViewBox左上角的坐标,后两个是ViewBox的宽度和高度。这些坐标是相对于圆图像的。在这种情形下,因为使用了一个ImageBrush,这些就都是源图像中的坐标。在DrawingBrushVisualBrush的情形下,ViewBox会使用绘制源的坐标系统。

    示例7-36

    Viewbox使你可以选择定焦到源图像的哪个部分,而Viewport使你选择使用笔刷的图像结束在哪个位置。它的功能与对齐属性交叠,但是Viewport允许更多的控制。

    7-46说明了ViewBoxViewport的联系。在左边,我们看到这种情形的源图像,但是它可能还是一个绘图或者可视化树。Viewbox详细指出了源图像的区域。在右边,我们看到了笔刷。Viewport详细指出了笔刷所在的区域。WPF将会伸缩和定位源图像,从而在Viewbox中详细指出的区域终结于被Viewport在被绘制区域详细指出区域。

    Viewport并没有详细指出笔刷的范围。正如示例7-46显示,笔刷并不需要和Viewport的大小一样。笔刷不可以被裁剪到Viewport的大小。所有ViewportViewbox做的是确定源图像用目标笔刷绘制的比例和位置。示例7-37显示了ViewportViewbox的设置,对应到图7-46中的高亮区域。

    7-46

    示例7-37 

    注意到示例7-37中,当Viewbox的坐标是相对于源图像的,Viewport适用01间的数值。默认的,Viewport坐标系统是基于笔刷的完全大小,(00)是在左上角,而(11)是在右下角。这意味着由Brush显示的图像区域总是相同的,不管笔刷的大小。这就导致了一个扭曲的行为——相似于默认的StretchModeFill,如图7-47所示。(事实上,Fill伸展模式等价于Viewbox设置为源图像的大小,以及Viewpoint为“0011”。)

    你可以使用ViewportUnit属性为Viewport详细指定不同的单位。默认为RelativeToBoundingBox,但是一旦你将它改为Absolute,这个Viewport使用用户界面的坐标系统来测量。

    记住所有的伸缩和定位功能,对于所有派生于TileBrush的笔刷都是公共的。我们现在将来看一下详细指出独立的笔刷的样式。

    7.3.5.2 ImageBrush

    ImageBrush使用一个图片来绘制屏幕上的区域。ImageBrush用于在前面的章节创建所有图片。这个笔刷是直接了当的,你可以简单地告诉它使用哪一个图片在ImageSource属性上,正如示例7-38所示。

    7-47

    示例7-38

    为了使图片文件对ImageBrush是有效的,简单地在VS2008中把它添加到你的工程中。示例7-38中这个文件在这个工程的名为Images的子目录中。这个图片必须作为一个资源嵌入到工程中。为了做到这一点,在VS2008中的“解决方案”面板中选择一个图片,接着在“属性”面板中,确保它的BuildAction属性被设置为Resource。这就嵌入这个图片到可执行程序中,支持ImageBrush在运行时找到它。(参见第6章获取更多关于管理二进制资源的信息。)可选择性的,你可以为这个属性详细指明一个绝对路径,例如,你可以显示一个来自网站的图像。

    #ImageBrush非常乐于处理带有透明通道的图像(又被称为“alpha”通道)。并不是所有的图像格式支持局部透明,但是,一些是可以的,如PNGBMP格式。(其次,GIF也是可以的。它们也支持完全透明和完全不透明的像素。这时有效的1alpha通道。)ImageBrush将给它特殊的权利,在alpha通道出现的地方。

    7.3.5.3 DrawingBrush

    ImageBrush是便利的,如果你有一个绘制时需要的图片。然而,图像并不非常适合于独立的分辨率。ImageBrush将为你的屏幕分辨率正确地缩放图像,但是在缩放时,图片易于变得模糊。DrawingBrush不会为这个问题所累,因为你通常提供一个可缩放的向量图作为它的资源。这就支持一个DrawingBrush在任意大小和分辨率保持清晰的和锐利的。

    这个向量图由一个Drawing对象表示。这时一个抽象的基类。形状可以用GeometryDrawing绘制,这就允许你使用所有相同的由Path支持的几何体元素来构造绘图。你还可以通过ImageDrawingVideoDrawing来使用图片和视频。文本由GlyphRunDrawing支持。最后,你可以使用DrawingGroup将这些联合在一起。

    即使你只使用形状,你仍然可能想要通过DrawingGroup来分组这些形状。每个GeometryDrawing都有效的等同于一个单独的Path,因此如果你想使用不同的钢笔和笔刷来绘制,或者如果你想你的形状是交叠的而不是联合的,你将需要使用多个GeometryDrawing元素。示例7-39显示了使用一个FillDrawingBrushRectangle

    示例7-39

    示例7-39绘制了与早期图7-35同样的可视化。因为组成绘图的每一个矩形元素,使用了不同的线性渐变填充,它们都有自己的GeometryDrawing,嵌入到DrawingGroup中。

    使用DrawingBrushViewbox默认为(0,0,1,1)。示例7-39中所有的坐标和大小都相对于坐标系统。如果你想要在一个更广范围的坐标上工作,你可以简单地设置Viewbox为你需要的范围。我们已经看到在示例7-36中如何使用Viewbox。使用DrawingBrush的唯一区别是,你使用它表示一个绘图区域,而不是一个图片。

    注意到,我们可以使用Viewbox在图片的一小部分上设定焦点,正如我们早期使用ImageBrush一样。在示例7-39中,我们可以修改DrawingBrush以使用一个更小的Viewbox,正如图7-40所示。

    示例7-40

     

    结果是大多数的绘图现在都在Viewbox的外面,因此这个笔刷只显示了全部绘图的一部分,如图7-48显示的那样。

    7-48

    DrawingBrush是极其强大的,正如它让你或多或少使用任意你喜欢的图形,如笔刷,以及因为它是基于向量的,结果在任意比例都保持为易碎的。它由一个缺点,如果你从标记中使用它,尽管:它有点笨重的——从xaml中使用。考虑一下示例7-39,生成了和示例7-29同样的外观,但是这些示例分别是28条线段长和10条线段长。

    Drawing是非常麻烦的,因为它需要我们与几何体对象一起工作,而不是高级别结构如在示例7-29中使用的GridRectangle。(注意到这么一个不是很敏感的问题,当在代码中使用这个笔刷时,这里高级别对象不再比几何体使用便利)。因此这确实是一个xaml的问题。幸运的是,VisualBrush运行我们使用这些高级别对象来绘制。

    7.3.5.4 VisualBrush

    VisualBrush可以使用任意派生自Visual的元素的内容来绘制。由于Visual是所有WPF用户界面元素的基类,这就意味着实际上你可以插入任何你想要的标记在一个VisualBrush中。示例7-41显示了使用VisualBrush填充的一个Rectangle

    示例7-41

    在示例7-41中,笔刷的可视化是直接从示例7-29复制过来的,导致了一个更简单的等同于DrawingBrush的笔刷。(这个结果看起来非常像图7-35VisualBrush的要点在于,它绘制区域就像它包装的可视化。)

    你可能想知道究竟为什么要使用DrawingBrush,既然VisualBrush是非常简单的?一个原因是DrawingBrush更加有效。一个绘图不为每一个基础绘图承载一个完整的FrameworkElement性能消耗。虽然创建一个DrawingBrush可以更有效,它在运行时消费了比较少的资源。如果你想你的用户界面有独特的复杂的可视化,DrawingBrush将支持你以很少的消耗这么做。如果你打算使用动画,这种低消耗可能转变为更加平滑外观的动画。

    DrawingBrush使得创建一个笔刷——像你的用户界面的某一部分——是非常容易的。你可能使用它来创建反光效果,或使用户界面看起来是在三维中旋转的。(这就超越了本书的范围。)

    7.3.6 Pen

    笔刷用于填充形状的内部。为了绘制形状的轮廓,WPF需要更多一些的信息,不仅需要一个笔刷为屏幕的区域着色,还需要知道绘制的线段有多厚以及你是否需要一个虚线样式和一个密封盖。Pen类提供了这些信息。

    Pen总是基于笔刷的,意味着到目前为止所有我们看到的绘图效果,在我们绘制轮廓的时候,都可以使用它。笔刷使用Brush属性来设置。

    #记住如果你与任何高级别的形状元素一起工作,你将不会直接和Pen一起工作。Pen用于盖子下面,但是你间接地设置所有属性。表7-1显示了Shape属性是如何对应到Pen属性的。

    你可以典型地直接处理Pen,仅在你工作在比较低的级别时,正如DrawingBrush中的GeometryDrawing

    线段的宽度由Thickness属性设置。为了简单的轮廓,这和Brush可能是你设置的仅有的属性。然而,Pen可以提供更多。例如,你可以设置一个虚线样式在DashArray属性上。这是一个简单的数字数组。每个数字对应到虚线框中特别的部分的长度。示例7-42说明了最简单的可能的样式。

    示例7-42

    这说明了虚线样式的第一个片段是一个长度。虚线样式进行了重复,以及由于仅有的一个长度片段已经被详细指定了,每一个片段都是长度1。示例7-49显示了结果。

    7-49

    示例7-43显示了两个略微比较有趣的样式序列。注意到第二个例子提供了一个奇数的序列。这意味着第一次环绕,实心片段为6以及间隙大小为1,但是当重复一个序列时,实心片段将会是长度1以及间隙大小为6。因此,虚线样式的有效长度被加倍了。这两种样式的结果都在图7-50中显示。

    示例7-43

    7-50

    拐角处被绘制成三种不同的方式。LineJoin属性可以被设置为MiterBevel或者Round,从左到右显示在图7-51中。

    7-51

    对于开放的形状如LinePolyLine,你可以详细指出形状的开始线段和结束线段——通过StartLineCapEndLineCap属性。这些属性支持四种样式的洋葱皮:RoundTriangleFlatSquate,在图7-52中从上到下显示。FlatSquare在线段的终端都是方方正正的。区别在于Flat,扁平的终端交叉了线段的终点,但是对于Square,它的扩展超越了其自身。它跨越线段的数量等同于线段厚度的一半。

    7-52

    7.4 转换

    支持高分辨率显示是WPF中的重要样式。这是被部分地支持——强调了可伸缩的向量图,而不是图像。但是,正如使用GDI+GDI32显示的,如果可伸缩性没有完全集成到图像化构架中,独立的分辨率实际上是非常难于达到连续性的。

    WPF对伸缩的支持是建立在一个基础的级别。任何用户界面中的元素都可以应用一个转换,使得伸缩和旋转任何事物都很容易。

    所有的用户界面元素都有一个transform类型的RenderTransform属性。这是一个抽象的基类。从这个基类中派生的类实现了各种仿射性的转换:旋转、缩放、变形以及修剪。所有的这些都是便利的类,所有支持的转换都可以被MartrixTransform表示。这包括了一个3X3矩阵,允许使用任何仿射性的转换。

    *一个仿射性的转换是,样式排列在一条直线上的位置,在转换仍然在一条直线上之前,在转换之后。注意到,3-D视图转换没有保持这些直线。

    示例7-44显示了RenderTransform属性的使用。

    示例7-44

    注意到TransformGroup在这里用来联合两种转换效果。(注意到这里旋转角度被详细指明了度数。)结果如图7-53所示。

    7-53

    在标记中,你不会正常的用完整的词写出转换,如示例7-44所示,因为你可以使用字符串简写的语法,如代码7-45所示。

    示例7-45

    RenderTransform属性允许你详细指定转换的序列,这将被转换为适合的Transform对象。

    RenderTransform改变元素的外观而在外观上没有任何效果。注意到图7-53中“Hello”这个TextBlock——在“world”这个块的下面——是如何运行的。这些元素都在一个水平的StackPanel中,因此你通常可以希望第二个元素完全的位于第一个元素的右边,而不是交叠在一起。然而,RenderTransform对于外观逻辑是有效可见的,因此StackPanel排列元素好像转换并不在适当的位置。

    实际上,你可以经常想通过外观系统将转换考虑在内。在这种情形中,你不应该使用RenderTransform属性。代替的,你应该使用LayoutTransform属性,正如示例7-46所示。

    示例7-46

    LayoutTransform属性在某种程度上应用了转换——这对于外观系统是可见的。正如图7-54所示,这意味着StackPanel现在为变形的文字分配了充分的空间,以及这两个元素不再是交叠的了。

    7-54

    你可以应用任何数量的转换在可视化树的任意地方。图7-55显示了一个用户界面——主要区域可以被旋转和轻微地放大,但是部分内容已经被减少和以相反的方向旋转。显然地,这个特殊的示例不是十分有用,但是它确实显示了变换可以被添加到任意的位置。进一步而言,外观继续正确的工作,主要的用户界面使用Grid来排列,以及被旋转的内部内容在一个嵌入的Grid中。如果主要的窗体重新调整了大小,这两个grid都会正确地重新排列它们的内容,不会被装换的出现而混乱。

    7-55

    这种对转换的彻底支持是重要的,不是以为它支持如图7-55古怪的外观,而是因为你可以依赖于转换进行一致性和可靠性的工作。

    7.5 可视化层编程

    形状元素能提供一种便利的方式与图形一起工作,在一些情形中,添加表示绘图的元素到UI树中,可能是比它的价值更加麻烦。你的数据可能被构造以一种易于编写代码的方式——简单地表现一系列基于数据的绘图操作,而不是构造一棵对象树。

    WPF提供了一个“可视化层”API,作为一个对形状元素较低级别的折中。(实际上,形状元素全都在可视化层得顶部被实现。)这个API使我们编写按需生成的代码。

    #可视化是一个可见的对象。WPF应用程序的外观是将它所有的可视化组合到屏幕上形成的。由于WPF生成在在可视化层的顶级,每个元素都是可视化的。FrameworkElement基类间接派生于Visual。在可视化层编程,简单地解决了创建一个可视化和编写代码告诉WPF我们想要在可视化中显示什么。

    即使在这个低的级别,WPF的表现非常不同于Win32。图形加速的方式是托管的,这意味着你的按需生成的代码很少被调用——少于在经典Windows应用程序中。

    7.5.1 按需生成

    按需生成的关键定制为OnRender方法。这个方法被WPF调用在它需要你的组件生成它的外观时。(这是内嵌形状的类生成它们自身的方式。)

    #OnRender虚方法定义在OnDemandVisual类。大多数元素都间接的通过FrameworkElement派生于此,这就增加了核心样式如外观和输入处理。

    示例7-47显示了一个字定义的元素,覆写了OnRender

    示例7-47

     

    OnRender方法传递了一个单独的DrawingContext类型的参数。这是一个WPF中低级别的绘图API。它提供了一组基础绘图操作,这些都在表7-4中列出。示例7-47使用了DrawRectangleDrawText方法。

    注意到DrawingContext使用了BrushPen类来指出形状是如何填充和画轮廓的,正如我们之前看到的高级别形状对象。我们还可以传递同样的GeometryDrawing对象——也是在本章前面看到的。

    7-4

    因为我们的自定义元素派生于FrameworkElement,它自然地集成到任何WPF应用程序中。示例7-48为一个窗体的标记,其中包含了自定义元素,我们可以使用它就像我们之前使用的任何自定义元素。这个窗体如图7-56所示。

    示例7-48

    7-56

    注意到,示例7-47中的OnRender方法调用了Debug.WriteLine。如果程序运行在VS2008调试器的内部,这将在每次调用OnRender方法的时候,打印出一条消息到“输出”窗体。这支持我们看到WPF如何经常要求我们的自定义可视化来生成它自身。如果你习惯于如何在在标准Win32Windows Forms中的按需绘制下工作,你可能希望看到这被规则地调用——无论窗体重新调整大小还是部分模糊或隐藏。实际上,它只会被调用一次。

    结果是按需生成并不相似于Win32的旧有样式生成,正如你可能想到的。WPF会调用你的OnRender方法当它需要知道你的可视化显示什么内容,但是图形加速的工作方式意味着这种情况的发生远远少于Win32中等价的重画。WPF缓存了生成指令。这种缓存的范围和形式并没有文本化,但是它清晰的发生了。进一步而言,这比简单的基于图像的缓存要微妙的多。我们可以添加这些代码到示例7-48的宿主窗体(在后台代码文件中可能是这样的)。

     protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

     {

             VisualOperations.SetTransform(CustomRender, new ScaleTransform(6, 6));

     }

    先前的片断应用了转换到我们的元素,以因数6放大。当点击用户界面时,自定义可视化会如你想希望的方式延伸,而且OnRender并没有被调用。不仅如此,放大的可视化并没有显示任何你可以看到的像素化或模糊化的人造物,通过一个简单的图像缩放。它继续是锐利的,正如你在图7-57看到的。

    7-57

    这指出了WPF获取关于可视化内容的缩放信息。它能重绘我们的可视化的屏幕外观,而不使用我们的OnRender方法,即使当转换有所改变。这在局部上取决于加速构架,而且同样因为转换支持在大多数基础的级别生成在WPF中。

    WPF重绘的能力——不使用OnRender,允许用户界面在屏幕上保持完整,即使我们的应用程序很繁忙。它同样支持动画系统工作而不用很多来自应用程序的干扰,因为所有的基础绘制操作都被保留了,WPF可以重建任何部分的UI——当独立的元素改变的时候。

    如果我们对象的状态必须在某种程度上改变——需要更新外观,我们可以调用InvalidateVisual方法。这将导致WPF调用我们的OnRender方法,允许我们重建外观。

    注意到,当你重写OnRender的时候,你还应该典型地覆写MeasureOverridArrangeOverride方法。否则,WPF的外观系统不会知道你的元素有多大。我们得到的唯一原因,而不用在这里这么做,是我们在Canvas上使用该元素,这将不会关心它的子元素有多大。为了在其它的面板上工作,这是实质的让外观系统知道你的大小。第二章描述了MeasureOverridArrangeOverride方法。

    7.6 视频和3-D

    虽然详细地讨论视频和3-D超越了这本书的范围,但是获得这些特征的支持是值得的。

    视频由MediaElement类型支持。这个元素可以被添加到UI树的任何地方。简单的设置它的Source属性以关联到它要播放的视频流,如示例7-49所示。

    示例7-49

    3-D内容通过Viewport3D支持。直到WPF的外观系统被关联,Viewport3D只是一个正规的控件,同时它可以被设定大小和定位,像其它控件那样。然而,你提供了这样的控件,带有3-D模型、发光、照相机位置信息。它会生成这个模型。这个控件担当了一个3-D屏幕上的窗体,正如示例7-50所示。

    示例7-50

    这就建立了一个非常简单的3-D模型,包含了一个单独的基于正方形的锥形。图7-58显示了结果。这个模型还包含了一些光源来保证模型是可见的。这个Viewport还有一个详细指出的照相机位置。

    7-58

    实际上,你可以正常的使用任意类型的3-D设计工具模型,因此你不能典型地希望和模型标记一起工作,如示例7-50所示。Viewport3D仅提供了一个便利的方法,将结果集成到你的可视化树中。

    7.7 我们进行到哪里了?

    WPF提供了一个范围的高质量生成和合成服务。一组形状元素支持各种的绘图基础。一些笔刷类型是可利用的,对于决定如何绘制形状,以及钢笔,增大了笔刷来定义如何绘制轮廓。转换在所有级别都是被支持的,使得缩放用户界面到任意分辨率和大小变得更容易。你可以集成视频和3-D内容到你的应用程序中。以及一个低层次的API是可用于在必要的时候,工作在可视化层次。

  • 相关阅读:
    HDU
    稀疏表(ST / Sparse Table)
    HDU
    HDU
    树状数组
    用 BitArray 来编写埃拉托斯特尼筛法
    BitArray 内置是逆序存储, 因此要自行实现正序输出
    位运算,位移,窗体
    按位运算,窗体程序,And,Or,Xor
    基数排序
  • 原文地址:https://www.cnblogs.com/Jax/p/1121959.html
Copyright © 2011-2022 走看看