zoukankan      html  css  js  c++  java
  • WPF,Silverlight与XAML读书笔记第三十七 可视化效果之Brush

    说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。

    Brush用于定义形状的填充,包括前景色与背景色,或者填充边框实现描边效果,可使用颜色、图像等作为填充物,使用方式包括像用于形状的Fill属性等。

    WPF中包含了6种不同的Brush,分为两大类,Color Brush与Tile Brush。通过这几类Brush,元素不需要直接与颜色进行交互,一切都通过Brush完成。首先我们详细介绍三个Color Brush:

    • SolidColorBrush
    • LinearGradientBrush
    • RadialGradientBrush

    SolidColorBrush

    最基本的画刷,使用指定的单色填充区域。颜色参数可以是一个已知的颜色名字,如Red,也可以是包含透明度表示的十六进制数。如#FFFF0000表示红色,前两个FF为透明度定义,表示不透明。在XAML中,类型转换器会把"Blue"或"#FFFF00"这样的字符串转换为SolidColorBrush,所以常常在不知不觉中我们就用到了这个Brush。

    提示:使用透明度的方法

    除了使用上文提到的在颜色设置中使用Alpha通到设置透明度的方法。还可以使用Opacity属性,这个属性可给的值从0到1,效果从完全透明到完全不透明。需要注意的是Opacity的设置会覆盖颜色中Alpha通到的设置。通过使用Opacity,可以做出淡入淡出的动画效果。

    细说Color

        SolidColorBrush最重要的属性是System.Windows.Media.Color类型的属性Color。Color结构体支持两种色彩空间。

    • sRGB:这是传统的颜色表示方式,红绿蓝各用一个颜色表示,所以每种颜色在组合中都只有256种可能的值。
    • scRGB:增强型RGB色彩空间,用浮点数表示红绿蓝三色,其可以表示更宽的色域。

    Color为两种命名空间分别提供了不同的属性。其中Byte类型的A,R,G,B用于设置sRGB色彩空间,Single类型的ScA,ScR, ScG,ScB用于设置更复杂的scRGB色彩模型,其中A与ScA均表示alpha通道。只要这些属性中有一个更新,Color内部就会随之更新。通过这些属性也可以在sRGB与scRGB之间进行转换。

        由于scRGB采用浮点数存储颜色,而导致无法测试两个Color的实例(中的4个属性值)是否完全相同。为此Color提供了一个静态方法ArcClose来判断两种颜色是否相同,当两个Color对象表示的颜色每个通道相差都很小时,返回true。

    用于XAML的Color类型转换器支持如下三种形式的字符串:

    • "Red", "Azure"这样的颜色名称,会被转成与Color中同名属性表示的颜色。
    • 如#FFFF0000(或#FF0000,A默认值为255(FF)),用于表示sRGB。
    • 如sc#1.0,1.0,0.0,0.0(逗号可省略,并且同样第一个1.0是默认值可省略),用于表示scRGB。

    LinearGradientBrush

    以线性渐变的方式填充区域。定义两个点,在两点定义两种颜色,在之间的区间使用渐变色进行填充。对于给一个Rectangle定义的线性填充,默认方向是从左上角到右下角(对于所有形状均如此),并且只有这两个渐变点。可以通过设置StartPoint和EndPoint的值(默认值分别为(0,0),(1,1))来改变笔刷的方向

    通过在GradientStops内容属性中指定多个GradientStop(其中通过Color属性定义了一种颜色)可以实现多个颜色间依次渐变。当GradientStop超过两个时(即在中间位置存在GradientStop),必须指定其double类型的Offset属性来给出这个点在整个渐变区域的位置。

    最后给出一个例子,重要属性进行了加粗:

    1 <Rectangle Width="200" Height="128" >
    2     <Rectangle.Fill>
    3         <LinearGradientBrush StartPoint="0,1" EndPoint="1,0">
    4             <GradientStop Color="Red" Offset="0"/>
    5             <GradientStop Color="Yellow" Offset="0.3"/>
    6             <GradientStop Color="Blue" Offset="1"/>
    7         </LinearGradientBrush>
    8     </Rectangle.Fill>
    9 </Rectangle>

    运行后效果如下:

    对于需要手工指定StartPoint和EndPoint的情况,还可以通过设置MappingMode的值来指定以上两点的坐标是绝对值还是相对值。该属性默认值是RelativeToBoundingBox,表示坐标为相对值,如果要指定为绝对值模式,将MappingMode设置为Absolute即可。

    注意,相对值不限于(0,0),(1,1)。指定超过此范围的值,会使渐变效果延伸到目标之外。

    另一个枚举类型的属性ColorInterpolationMode用于指定使用的颜色空间,默认为sRGB,通过将该枚举指定为ScRgbLinearInterpolation可以使用scRGB,从而获得比sRGB更细腻的渐变效果。LinearGradientBrush还有一个GradientSpreadMethod枚举值的属性SpreadMethod(在RadialGradientBrush中也有同名属性,且作用几乎完全一致),用于设置没有显示设置如何渐变填充的区域(如StartPoint和EndPoint为相对模式,且设置为(0,0),(0.3,0.3)时,就会产生这种区域),该属性默认值为Rad,表示使用EndPoint的颜色填充剩余区域,另外两种可选值为Repeat与Reflect分别表示循环渐变与翻转渐变(并以此方式反复循环)。

    提示:将同一Offset处的两个GradientStop指定为不同的Color即可得到突变效果的线条。

    RadialGradientBrush

    用于定义放射性的渐变。这种渐变也有循环效果的。其中GradientStop(及其Offset)的定义方式与LinearGradientBrush中介绍的方式是一致的。RadialGradientBrush与LinearGradientBrush拥有相同的GradientBrush基类。

        默认使用RadialGradientBrush进行填充的时候,中心点位于被填充形状的中心(GradientOrigin值为0.5,0.5),可以使用RadialGradientBrush的GradientOrigin属性更改这个放射渐变的中心点位置。如对于一个矩形当GradientOrigin设置为0,0时中心点位于左上角,为1,1是中心点位于右下角,为0.7,0.7时位于右下部分。为了得到易于理解的结果,GradientOrignin应该位于下文介绍的虚拟椭圆的内部。

        RadialGradientBrush有一个SpreadMethod来设置渐变色的重复方式,这个属性有3个可选值,Pad、Reflect和Repeat。Pad是默认值,使用Offset值最大的GradientStop的颜色来填充剩余区域。Repeat很好理解,按顺序依次填充剩余区域,所以如果第一种与最后一种颜色不同,可以看到明显的边缘,而这每次循环填充的大小由下面介绍的RadiusX与RadiusY属性确定。而Reflect是按相反的颜色顺序填充剩余区域,即会出现一个反射的效果。具体可以参见下文的图片示例。

        Center,RadiusX与RadiusY属性用于设置一个假想的椭圆形渐变填充区域,默认为0.5,即如果Center在中心,第一次渐变正好到填充对象的边缘。如果设置参数小于0.5,当SpreadMethod为Repeat类的属性时就是出现多次重复渐变填充。如果大于0.5,则会出现填充被放大的效果,这样有些情况下会看不到所有填充的颜色。同样下面会给出图片颜色。

    首先看一下示例XAML:

    1 <Rectangle Width="200" Height="128" >
    2     <Rectangle.Fill>
    3         <RadialGradientBrush GradientOrigin="0.3,0.3" SpreadMethod="Pad" RadiusX="0.5" RadiusY="0.5">
    4             <GradientStop Color="Red" Offset="0"/>
    5             <GradientStop Color="Yellow" Offset="0.7"/>
    6             <GradientStop Color="Blue" Offset="1"/>
    7         </RadialGradientBrush>
    8     </Rectangle.Fill>
    9 </Rectangle>

    下面是效果图:

    接下来依次展示SpreadMethod设置为Repeat,Reflect的效果:

    最后我们展示一下RadiusX/RadiusY对渐变的影响,由于这两个属性的效果类似,我们以RadiusY做展示,下面图片分别为RadiusY为0.3与0.8时的效果(为了让效果明显,把代码中GradientOrigin设置为0.5,0.5,SpreadMethod设置为Repeat):

    特别的,当MappingMode设置为Absolute时,RadiusGradientBrush的4个特有属性Center,RadiusX,RadiusY和GradientOrigin都会被看作是绝对值而非相对值。

    注意:

    表面看起来都是透明的颜色本质上可能是不同的,如#00FF0000(透明红色),#0000FF00(透明绿色)和Color.Transparent(#00FFFFFF)透明白,在单独使用时都是完全不可见,但如果用于渐变中的某一个GradientStop,则会有效果上的大不同,一定要注意。

     

        另外RadialGradientBrush的ColorInterpolationMode和MappingMode属性与LinearGradientBrush一节中介绍过的同名属性作用一致,不再赘述。

    WPF中定义了三种tile笔刷,DrawingBrush,ImageBrush和VisualBrush,它们都派生自TileBrush抽象基类。tile笔刷的作用是用重复的图案填充目标区域,三种不同的tile笔刷使用的图案源分别来自Drawing,Image或Visual。除了图像源类型不同,其他行为都是一致的。

        下文将以DrawingBrush为例,详细介绍tile笔刷的特性。

    DrawingBrush

        将Drawing设置在DrawingBrush里实现的效果与放置在DrawingImage中相似。下面的示例代码演示通过DrawingBrush填充Canvas的Backgroud。

    1 <Canvas Width="500" Height="400">
    2     <Canvas.Background>
    3         <DrawingBrush>
    4             <DrawingBrush.Drawing>
    5                 <!-- Drawing -->
    6             </DrawingBrush.Drawing>
    7         </DrawingBrush>
    8     </Canvas.Background>
    9 </Canvas>

    将代码中DrawingBrush换成DrawingImage也可以正常运行,与DrawingImage不同,DrawingBrush默认背景是黑色而不是白色。

        DrawingBrush的Drawing的Stretch默认值为Fill可以设置为其它值改变填充效果。(关于Stretch几个值,见布局系统定位一节)。当Stretch设置为Fill以外的值时,Drawing默认会垂直居中。通过设置AlignmentX(可选值有Left,Center或Right)和AlignmentY(可选值有Top,Center或Bottom),可以改变Drawing的位置。

    DrawingBrush最重要的属性是TileMode(这是被称为tile笔刷的原因),TileMode有如下几种值:

    • None:不进行任何重复处理
    • Tile:这是我们感兴趣的设置,下文将详细介绍
    • FlipX:在水平方向上对tile隔列翻转
    • FlipY:在垂直方向上对tile隔行翻转
    • FlipXY:在两个方向上对tile分别进行隔行与隔列翻转

        当TileMode设置设置为Tile时,Drawing会在两个方向上重复自身。要使用Tile必须指定Rect空间,其用于指定要被重复的部分,通过DrawingBrush的Viewport属性来设置Rect空间。如将Viewport属性设置为0,0,0.1,0.2,通过内置的Rect类型转换器,可以将这个字符串转换成正确的Rect表示。Viewport默认也是相对于边框的,这样可以很简单的计算出可以在水平和垂直方向放置多少个tile,通过设置BrushMappingMode类型(之前多次用到)的ViewportUnit属性可以设置使用绝对坐标。

        最后要介绍的属性是ViewBox。ViewBox的作用是切割Drawing的一部分作为一个Tile的源,(如果TileMode设置为None,则ViewBox切割后的部分会应用到整个画刷),类似于Viewport,ViewBox也是一个矩形框,默认也使用相对相对(边框的)坐标,同样有一个名为ViewBoxUnit的属性用于将模式改为绝对。

    提示:DrawingBrush中接受的Drawing种类很丰富,除了可以是GeometryDrawing,还可以是VideoDrawing等。

    ImageBrush

    使用指定的图像填充目标区域,即将图像作为画笔使用,默认情况下,保持图片标尺宽高比例不变进行填充(参考下文介绍的Stretch,可以把要填充的图片进行拉伸)。ImageBrush与DrawingBrush唯一的区别在于,其含有一个ImageSource类型的ImageSource属性,而非Drawing属性。这样,我们使用DrawingBrush来承载矢量内容,而用ImageBrush来承载位图内容。

    可以使用如下属性控制区域的填充方式。

    属性:

    • TileMode
    • Viewport
    • Stretch:System.Windows.Media.Stretch类型的枚举属性
    • AlignmentX和AlignmentY属性

    当Stretch属性设置为None时,这两个属性就可以起作用,来控制图片在X轴上向左,向右或着中心对齐,或在Y轴上向上,向下或垂直居中对齐。

    以上属性可以见DrawingBrush中同名属性的介绍。

    ImageSource属性,这是ImageBrush中最主要的属性,我们通过一段XAML来了解其使用:

    1 <Canvas Width="500" Height="400">
    2     <Canvas.Background>
    3         <ImageBrush TileMode="FlipXY" Viewport="0,0,0.1,0.2">
    4             <ImageBrush.ImageSource>
    5                 <BitmapImage UriSource="..." />
    6             </ImageBrush.ImageSource>
    7         </ImageBrush>
    8     </Canvas.Background>
    9 </Canvas>

    VisualBrush

    与前面两个Brush很类似,除了VisualBrush含有一个Visual类型的Visual属性,而不是Drawing或ImageSource属性外,其它与前面两个Brush完全一致。这个Brush的强大在于其可以绘制Visual,甚至继承自FrameworkElement的Button都派生自Visual,从而可以被绘制,见如下XAML:

    1 <Canvas Width="500" Height="400">
    2     <Canvas.Background>
    3         <VisualBrush TileMode="FlipXY" Viewport="0,0,0.1,0.2">
    4             <VisualBrush.Visual>
    5                 <Button>OK</Button>
    6             </VisualBrush.Visual>
    7         </VisualBrush>
    8     </Canvas.Background>
    9 </Canvas>

    特别注意,这里的Button仅仅是一个被呈现的外观,而不具备交互能力。要让元素可以根据交互进行外观的改变可以将Visual绑定到一个UIElement实例上,见如下的XAML:

     1 <Window x:Class="WpfApplication2.Window1"
     2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4     Title="Window1" Width="600" Height="500">
     5     <DockPanel>
     6         <StackPanel x:Name="panel">
     7             <Button>Button</Button>
     8             <CheckBox>CheckBox</CheckBox>
     9         </StackPanel>
    10         <Rectangle>
    11             <Rectangle.Fill>
    12                 <VisualBrush TileMode="FlipXY" Viewport="0,0,0.5,0.5" Visual="{Binding ElementName=panel}" />
    13             </Rectangle.Fill>
    14         </Rectangle>
    15     </DockPanel>
    16 </Window>

    效果图:

    怎么样,看起来很抽象吧:)

    右侧的按钮虽然不能进行交互,但其可以根据左侧真实按钮的变化改变自身的外观。

        实际应用中,Vista以上版本的Windows的任务栏项目鼠标移过时出现的小预览窗口,正是基于类似VisualBrush的机制。另外使用VisualBrush还可以实现实时倒影的效果,见下面这段XAML及效果图:

     1 <Window x:Class="WpfApplication2.Window1"
     2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4     Title="Window1" Width="600" Height="500">
     5     <StackPanel x:Name="panel" Margin="36">
     6         <TextBox x:Name="textBox" FontSize="36" />
     7         <Rectangle Height="{Binding ElementName=textBox, Path=ActualHeight}"
     8                    Width="{Binding ElementName=textBox,Path=ActualWidth}">
     9             <Rectangle.Fill>
    10                 <VisualBrush Visual="{Binding ElementName=textBox}" />
    11             </Rectangle.Fill>
    12             <Rectangle.LayoutTransform>
    13                 <ScaleTransform ScaleY="-0.75"/>
    14             </Rectangle.LayoutTransform>
    15         </Rectangle>
    16     </StackPanel>
    17 </Window>

        以上示例有一个小缺陷,倒影过于清晰。下面我们将做进一步处理使效果更逼真,所用到的就是所有Visual都支持的OpacityMask属性(区别于Opacity,前者可以定制使不同区域有不同的透明度,而Opacity只能提供均匀的,对整个对象一致的效果。)

        OpacityMask的alpha通道的定义可以来自各类Brush,如各种Color Brush,或DrawingBrush(的Drawing),又或是ImageBrush(中的图像,如有透明区域或PNG)。了解了OpacityMask,我们看一下如何将前文的文字倒影处理成渐变透明:

     1 <Window x:Class="WpfApplication2.Window1"
     2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4     Title="Window1" Width="600" Height="500">
     5     <StackPanel x:Name="panel" Margin="36">
     6         <TextBox x:Name="textBox" FontSize="36" />
     7         <Rectangle Height="{Binding ElementName=textBox, Path=ActualHeight}"
     8                    Width="{Binding ElementName=textBox,Path=ActualWidth}">
     9             <Rectangle.Fill>
    10                 <VisualBrush Visual="{Binding ElementName=textBox}" />
    11             </Rectangle.Fill>
    12             <Rectangle.LayoutTransform>
    13                 <ScaleTransform ScaleY="-0.75"/>
    14             </Rectangle.LayoutTransform>
    15             <Rectangle.OpacityMask>
    16                 <LinearGradientBrush EndPoint="0,1">
    17                     <GradientStop Offset="0" Color="Transparent"/>
    18                     <GradientStop Offset="1" Color="#77000000" />
    19                 </LinearGradientBrush>
    20             </Rectangle.OpacityMask>
    21         </Rectangle>
    22     </StackPanel>
    23 </Window>

        这里OpacityMask使用了LinearGradientBrush,其中渐变的结束色#77000000最重要的在于77,这表示一种不完全透明的色彩,而77后面是什么颜色对OpacityMask的效果完全没有影响。

    不同于Fill属性用于对形状内部进行填充,Stroke属性用来定义一个形状的外边缘描边效果。但与Fill属性相同的是,对Stroke也使用各类Brush(见)进行内容的填充,一般也是通过如下这样的属性元素形式用复杂Brush进行填充。

    代码(为了让效果明显,我们把线条加粗):

    1 <Rectangle StrokeThickness="10" Width="200" Height="128" >
    2     <Rectangle.Stroke>
    3         <LinearGradientBrush>
    4             <GradientStop Color="Red" Offset="0"/>
    5             <GradientStop Color="Yellow" Offset="0.7"/>
    6             <GradientStop Color="Blue" Offset="1"/>
    7         </LinearGradientBrush>
    8     </Rectangle.Stroke>
    9 </Rectangle>

    这段代码效果如下:

    如果只是单色填充就无需使用属性元素的语法而直接设置Stroke值就可以了,如下:

    1 <Rectangle Stroke="Pink" StrokeThickness="10" Width="200" Height="128" >

    使用Fill填充图形内部也是同理。

    Stroke描边还有一些独有的设置,如指定描边的宽度等,这也是本节主要介绍的内容

    描边宽度

    StrokeThickness属性用于设置描边的宽度。前文代码中利用了此属性增加图形边框宽度来展示边框填充效果。

    描边线型

        StrokeDashArray用于设置描边的线性,例如我们定义一个这样的描边效果,第一条线段为4个单位长,后面跟1个单位空白,然后跟一个2单位的线段,之后再有一个1单位的空白如此往下循环。我们需要定义一个包含线段长与间隔长的double类型的数字的数组,并指定给StrokeDashArray属性:

    1 <Rectangle StrokeDashArray="4,1,2,1" StrokeThickness="10" Width="200" Height="128" >

    运行中的描边效果如下:

    当使用虚线效果时,可以使用StrokeDashOffset定义虚线开始处的距离。该属性为Double类型,默认值为0,表示虚线从头部开始。

    StrokeDashCap用于对描边的线段的形态进行设置,如圆角。StrokeDashCap接受如下几个枚举型参数:

    • Flat:默认值,线段是如上面例子所示的矩形
    • Round:线段边缘是一个直径与线段宽度相同的圆角
    • Square:线段的边缘使用一个正方形进行填充
    • Triangle:使用等边三角形对线段边缘进行填充,三角形的边长为线段的宽度

    StrokeLineJoin用于定义线段接头处,即转角处的样式。这个属性接受PenLineJoin类型的枚举,有下面3种值:

    • Bevel:通过一个斜边进行衔接
    • Miter:保留锋利的边缘
    • Round:使用圆角进行链接

    Pen

        Pen比较简单,基本上是一个带宽度的Brush。Pen的两个主要属性是Brush和Thickness,分别是Brush类型与double类型,用于定义Pen的填充与画笔粗细。Pen其他一些属性如下:

    • StartLineCap与EndLineCap:这两个属性接收PenLineCap类型的枚举值,用于定义线段非交叉端点的外观。有如下几种设置:Flat(默认值),Square,Round与Triangle
    • LineJoin:用于定义相连接的端点的外观。该属性接收PenLineJoin类型的枚举值。有如下几种设置:Miter(默认值),Round与Bevel。当LineJoin设置为Miter时,可以通过MiterLimit属性限制连接点延伸的长度,默认值为10,使用这个属性可以避免在小角度的情况下连接处延伸的过长。
    • DashStyle:这个属性接受DashStyle对象,从而让画笔绘制出虚线效果。DashStyle对象中的虚线的每一小段的两个端点都可以用DashCap属性(该属性也是接收PenLineCap类型枚举值)定义端点的样式,就像StartLineCap和EndLineCap实现的效果,不同的是DashCap属性的默认值是Square。

      DashStyle类中还有一个名为Dashes的属性,该属性为DoubleCollection类型,其中包含的数列,奇数位的值表示短线的长度,偶数位的值表示短线间的间距。如果被应用的Segment足够长,这个序列的效果会循环下去。

      DashStyle另一个double类型的属性offset用来控制虚线开始的位置。

      值得注意的是,DashCap属性默认设置为Square,所以短划线会比指定的数值长一些(所以即使短线长度为0,默认也会像一个小黑点一样),当设置为Flat时,短线长度为实际设置值。另外DashStyles类中定义了一些常用效果,如DashDotDot,可以以如下方式使用:

      1 <Pen Brush="DarkGreen" Thickness="15" DashStyle="{x:Static DashStyles.DashDotDot}"/>

    下表是内置于DashStyles的预制样式与Dashes值的对应效果。

    预制样式

    数值表示

    效果图

    Solid

    无对应

    Dash

    (2,2)

    Dot

    (0,2)

    DashDot

    (2,2,0,2)

    DashDotDot

    (2,2,0,2,0,2)

    提示:Square与Flat的区别

    Flat方式下线头正好会在交叉点处结束,而Square方式下,线头会延伸出交叉点(最终的效果类似在交叉点处放置了一个边长等于Thickness属性的正方体),我们通过下面的图更好的了解这两

    种枚举的效果:

    Square

    Flat

    提示:

    将所有PathSegment的IsSmoothJoin设为true的效果就是将LineJoin属性设为Round。当然即使显式设置了Pen的LineJoin属性,也可以通过StartLineCap和EndLineCap单独改变一个拐角的样式。

    本文完

    参考:

    《WPF揭秘》

  • 相关阅读:
    乌龟棋 (codevs 1068)题解
    全排列 (codevs 1294)题解
    最小伤害 题解
    编码问题 题解
    基础DAY3-运算符 逻辑运算符 if elif
    图解算法——合并k个排序列表(Merge k Sorted Lists)
    算法图解——组合求和( Combination Sum)
    make命令使用 & makefile编写详解
    并发工具CountDownLatch源码分析
    线程局部变量ThreadLocal实现原理
  • 原文地址:https://www.cnblogs.com/lsxqw2004/p/4629233.html
Copyright © 2011-2022 走看看