本篇咱们来学习WPF的绘图,在2D绘图中主要有这么几个重要的类:Drawing、Visual和Shape,顺便讲下Brush和BitmapEffect。ios
Drawing类表示形状和路径的二维图,它继承自Animatable类,因此支持数据绑定、动画和资源引用等。它有这么几个子类:浏览器
Drawing并非UIElement,因此自己没有绘画的能力,若是将其设置为窗体或者内容控件的内容,将只是显示其ToString的结果,要想在呈现Drawing,能够将DrawingImage、DrawingBrush和DrawingVisual这三者对象做为宿主容器。安全
GeometryDrawing,顾名思义就是画几何图形的,这个功能是由Geometry类型属性提供的。Geometry类大的方向可分为Basic Geometry和Aggregate Geometry:ide
Basic Geometry基本几何体又可分为:布局
PathSegment是一个抽象类,它有如下几个实现类:LineSegment(线段)、PolyLineSegment(线段集合)、ArcSegment(曲线段)、BezierSegment(三次贝塞尔)、PolyBezierSegment(三次贝塞尔集合)、 QuadraticBezierSegment(二次贝塞尔)和PolyQuadraticBezierSegment(二次贝塞尔集合)。性能
Aggregate Geometry聚合集合体又可分为:学习
GeometryGroup:顾名思义,有一个或者多个Geometry组成,自己是Geometry类型,经过Transform属性来表现复杂图形测试
CombinedGeometry:他不是一个通用的Geometry集合,它经过GeometryCombineModel枚举器来合并有且仅有的两个Geometry。字体
// 摘要: // 指定可用于合并两个几何图形的不一样方法。 public enum GeometryCombineMode { // 摘要: // 经过采用两个区域的并集合并两个区域。 所生成的几何图形为几何图形 A + 几何图形 B。 Union = 0, // // 摘要: // 经过采用两个区域的交集合并两个区域。 新的区域由两个几何图形之间的重叠区域组成。 Intersect = 1, // // 摘要: // 将在第一个区域中但不在第二个区域中的区域与在第二个区域中但不在第一个区域中的区域进行合并。 新的区域由 (A-B) + (B-A) 组成,其中 A // 和 B 为几何图形。 Xor = 2, // // 摘要: // 从第一个区域中除去第二个区域。 若是给出两个几何图形 A 和 B,则从几何图形 A 的区域中除去几何图形 B 的区域,所产生的区域为 A-B。 Exclude = 3, }
当咱们在用PathGeometry来绘制复杂的图形时,须要写不少的代码,这时候MS总会想些办法来为咱们减小工做量,这就是路径标记语法。动画
很明显,ImageDrawing是用来绘制图像的,它经过ImageSource属性指定要绘制的图像,经过Rect属性来制定每一个图像的位置和大小。
<Grid Grid.Row="1" Grid.Column="2"> <Grid.Background> <DrawingBrush> <DrawingBrush.Drawing> <ImageDrawing ImageSource="/Images/2.png" Rect="0,0,50,50"/> </DrawingBrush.Drawing> </DrawingBrush> </Grid.Background> </Grid>
播放媒体文件。 若是媒体为视频文件,则 VideoDrawing 会将其绘制到指定的矩形中。它包含一个获取或设置与绘制关联的媒体播放器的MediaPlayer类型的Player属性,包含一个获取或设置可在其中绘制视频的矩形区域的Rect属性。虽然能够在 XAML 中声明此类的实例,可是因为MediaPlayer类的依赖项,特别是 Open 和 Play 方法的缘故,若是不使用代码,则没法加载和播放其媒体。 要只在 XAML 中播放媒体,请使用 MediaElement。可使用 VideoDrawing 和 MediaPlayer 来播放音频或视频文件。 加载并播放媒体的方法有两种。 第一种方法是使用 MediaPlayer 和 VideoDrawing 自身,第二种方法是建立您本身的 MediaTimeline,并将其与 MediaPlayer 和 VideoDrawing 一块儿使用,这种方法能够控制Video的播放,好比快进等。
// Create a MediaTimeline.
MediaTimeline mTimeline =
new MediaTimeline(new Uri(@"sampleMedia\xbox.wmv", UriKind.Relative));
// Set the timeline to repeat.
mTimeline.RepeatBehavior = RepeatBehavior.Forever;
// Create a clock from the MediaTimeline.
MediaClock mClock = mTimeline.CreateClock();
MediaPlayer repeatingVideoDrawingPlayer = new MediaPlayer();
repeatingVideoDrawingPlayer.Clock = mClock;
VideoDrawing repeatingVideoDrawing = new VideoDrawing();
repeatingVideoDrawing.Rect = new Rect(150, 0, 100, 100);
repeatingVideoDrawing.Player = repeatingVideoDrawingPlayer;
表示一个呈现GlyphRun的Drawing对象,而GlyphRun表示一序列标志符号,这些标志符号来自具备一种字号和一种呈现样式的一种字体。
<Grid Grid.Row="2" x:Name="grid"> <Grid.Background> <DrawingBrush> <DrawingBrush.Drawing> <GlyphRunDrawing ForegroundBrush="Black"> <GlyphRunDrawing.GlyphRun> <GlyphRun CaretStops="{x:Null}" ClusterMap="{x:Null}" IsSideways="False" GlyphOffsets="{x:Null}" GlyphIndices="43 72 79 79 82 3 58 82 85 79 71" BaselineOrigin="0,12.29" FontRenderingEmSize="13.333333333333334" DeviceFontName="{x:Null}" AdvanceWidths="9.62666666666667 7.41333333333333 2.96 2.96 7.41333333333333 3.70666666666667 12.5866666666667 7.41333333333333 4.44 2.96 7.41333333333333" BidiLevel="0"> <GlyphRun.GlyphTypeface> <GlyphTypeface FontUri="C:\WINDOWS\Fonts\TIMES.TTF" /> </GlyphRun.GlyphTypeface> </GlyphRun> </GlyphRunDrawing.GlyphRun> </GlyphRunDrawing> </DrawingBrush.Drawing> </DrawingBrush> </Grid.Background> </Grid>
GlyphRun是一种比Label等更低级别的文本展现方式,用于固定格式的文档表示和打印方案 。
DrawingGroup用于将一个或者多个Drawing组合起来做为总体绘图,好比你能够将多个Drawing组合起来,使用Transform属性。
<Image Grid.Row="2" Grid.Column="1"> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <DrawingGroup> <DrawingGroup.Transform> <RotateTransform Angle="30" /> </DrawingGroup.Transform> <GeometryDrawing> <GeometryDrawing.Pen> <Pen Brush="Black" Thickness="10"/> </GeometryDrawing.Pen> <GeometryDrawing.Brush> <SolidColorBrush Color="Silver" /> </GeometryDrawing.Brush> <GeometryDrawing.Geometry> <PathGeometry FillRule="Nonzero"> <PathGeometry.Figures> <PathFigure IsClosed="True"> <PathFigure.Segments> <!--<QuadraticBezierSegment Point1="0,0" Point2="1,1" />--> <LineSegment Point="0,100" /> <LineSegment Point="80,100" IsSmoothJoin="True"/> </PathFigure.Segments> </PathFigure> <PathFigure IsClosed="True" StartPoint="50,0"> <PathFigure.Segments> <!--<QuadraticBezierSegment Point1="0,0" Point2="1,1" />--> <LineSegment Point="0,100" /> <LineSegment Point="80,100" IsSmoothJoin="True"/> </PathFigure.Segments> </PathFigure> </PathGeometry.Figures> </PathGeometry> </GeometryDrawing.Geometry> </GeometryDrawing> <GeometryDrawing> <GeometryDrawing.Pen> <Pen Brush="Green" Thickness="1" /> </GeometryDrawing.Pen> <GeometryDrawing.Geometry> <RectangleGeometry Rect="0,0,100,100" /> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image>
Visual类是UIElement类的抽象基类,它是任何东西绘画到屏幕上的基本实现。DrawingVisual也是Visual的一个间接子类(直接继承自ContainerVisual),它主要是呈现Drawing,例如Image、Video等,并且它还支持经过Hit Testing来进行与输入设备的交互,可是它不能接收键盘、鼠标或笔针事件。
咱们知道UIElement都能很好的在屏幕上显示,然而,DrawingVisual在做为ContentControl的内容时,只是显示其ToString的结果。为了让DrawingVisual显示在屏幕上,须要将其添加到UIElement的Visual树结构上,将该UIElment做为其宿主容器。为此,咱们须要作这样的两个工做:一是自定义一个继承自UIElement的类;而是Override其VisualChildrenCount属性和GetVisualChild方法。
class WndVisual:UIElement
{
private DrawingVisual drawingVisual = null;
public WndVisual()
{
drawingVisual = new DrawingVisual();
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index != 0)
throw new ArgumentOutOfRangeException("index");
return drawingVisual;
}
protected override void OnRender(DrawingContext drawingContext)
{
//Drawing drawing = FindResource("glyphRunStyle") as GlyphRunDrawing;
//drawingContext.DrawDrawing(drawing);
base.OnRender(drawingContext);
}
protected override void OnRenderSizeChanged(SizeChangedInfo info)
{
GlyphRunDrawing glyphRun = Application.Current.MainWindow.FindResource("glyphRenStyle") as GlyphRunDrawing;
if (glyphRun != null)
{
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawDrawing(glyphRun);
}
this.AddVisualChild(drawingVisual);
}
base.OnRenderSizeChanged(info);
}
}
Hit Testing译为命中测试,它指的是判断一个点或者一组点是否与一个给定的对象相交。应用于鼠标时,一般是指鼠标指针的位置。
在WPF中有两种命中测试:Visual Hit Testing和Input Hit Testing。前者被全部的Visual对象支持,然后者仅被UIElement对象支持。这个很好理解,输入事件被定义在UIElement类中。这里主要讲Visual Hit Testing。经过VisualTreeHelper的HitTest方法来实现,来看下方法定义:
// // 摘要: // 经过指定 System.Windows.Point 返回命中测试的最顶层 System.Windows.Media.Visual 对象。 // // 参数: // reference: // 要进行命中测试的 System.Windows.Media.Visual。 // // point: // 要进行命中测试的点值。 // // 返回结果: // System.Windows.Media.Visual 的命中测试结果,做为 System.Windows.Media.HitTestResult // 类型返回。 public static HitTestResult HitTest(Visual reference, Point point); // // 摘要: // 使用调用方定义的 System.Windows.Media.HitTestFilterCallback 和 System.Windows.Media.HitTestResultCallback // 方法对指定的 System.Windows.Media.Visual 启动命中测试。 // // 参数: // reference: // 要进行命中测试的 System.Windows.Media.Visual。 // // filterCallback: // 表示命中测试筛选回调值的方法。 // // resultCallback: // 表示命中测试结果回调值的方法。 // // hitTestParameters: // 要进行命中测试的参数值。 public static void HitTest(Visual reference, HitTestFilterCallback filterCallback, HitTestResultCallback resultCallback, HitTestParameters hitTestParameters); // // 摘要: // 使用调用方定义的 System.Windows.Media.HitTestFilterCallback 和 System.Windows.Media.HitTestResultCallback // 方法对指定的 System.Windows.Media.Media3D.Visual3D 启动命中测试。 // // 参数: // reference: // 要进行命中测试的 System.Windows.Media.Media3D.Visual3D。 // // filterCallback: // 表示命中测试筛选回调值的方法。 // // resultCallback: // 表示命中测试结果回调值的方法。 // // hitTestParameters: // 要进行命中测试的三维参数值。 public static void HitTest(Visual3D reference, HitTestFilterCallback filterCallback, HitTestResultCallback resultCallback, HitTestParameters3D hitTestParameters);
第一个版本是一个简单的版本,它仅用于返回命中测试的最顶层的对象;第二个版本可用于重叠的Visual的命中测试的状况;第三个版本可用于重叠的Visual3D的命中测试的状况。
class WndDrawingVisual3:UIElement { DrawingVisual bodyVisual = null; DrawingVisual eyesVisual = null; DrawingVisual mouthVisual = null; public WndDrawingVisual3() { bodyVisual = new DrawingVisual(); eyesVisual = new DrawingVisual(); mouthVisual = new DrawingVisual(); using (DrawingContext dc = bodyVisual.RenderOpen()) { // The body dc.DrawGeometry(Brushes.Blue, null, Geometry.Parse( @"M 240,250 C 200,375 200,250 175,200 C 100,400 100,250 100,200 C 0,350 0,250 30,130 C 75,0 100,0 150,0 C 200,0 250,0 250,150 Z")); } using (DrawingContext dc = eyesVisual.RenderOpen()) { // Left eye dc.DrawEllipse(Brushes.Black, new Pen(Brushes.White, 10), new Point(95, 95), 15, 15); // Right eye dc.DrawEllipse(Brushes.Black, new Pen(Brushes.White, 10), new Point(170, 105), 15, 15); } using (DrawingContext dc = mouthVisual.RenderOpen()) { // The mouth Pen p = new Pen(Brushes.Black, 10); p.StartLineCap = PenLineCap.Round; p.EndLineCap = PenLineCap.Round; dc.DrawLine(p, new Point(75, 160), new Point(175, 150)); } bodyVisual.Children.Add(eyesVisual); bodyVisual.Children.Add(mouthVisual); this.AddVisualChild(bodyVisual); } protected override Visual GetVisualChild(int index) { if (index != 0) throw new ArgumentOutOfRangeException("index"); return bodyVisual; } protected override int VisualChildrenCount { get { return 1; } } protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); /* // Retrieve the mouse pointer location relative to the Window Point location = e.GetPosition(this); // Perform visual hit testing HitTestResult result = VisualTreeHelper.HitTest(this, location); // If we hit any DrawingVisual, rotate it if (result.VisualHit.GetType() == typeof(DrawingVisual)) { DrawingVisual dv = result.VisualHit as DrawingVisual; if (dv.Transform == null) dv.Transform = new RotateTransform(); (dv.Transform as RotateTransform).Angle++; } * */ Point location = e.GetPosition(this); VisualTreeHelper.HitTest(this, new HitTestFilterCallback(hitTestFilterCallback), new HitTestResultCallback(HitTestCallback), new PointHitTestParameters(location)); } private HitTestResultBehavior HitTestCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { DrawingVisual dv = result.VisualHit as DrawingVisual; if (dv.Transform == null) dv.Transform = new RotateTransform(); (dv.Transform as RotateTransform).Angle++; } return HitTestResultBehavior.Continue; } private HitTestFilterBehavior hitTestFilterCallback(DependencyObject potentialHitTestTarget) { if (potentialHitTestTarget == bodyVisual) return HitTestFilterBehavior.ContinueSkipSelf; return HitTestFilterBehavior.Continue; } //重写该方法,可实现自定义命中方式 protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { return base.HitTestCore(hitTestParameters); } }
Shape和GeometryDrawing同样,也是基本的二位画图,它结合了Geometry、Brush和Pen。不一样的是,Shape继承者FrameworkElement,能够直接在屏幕显示。它包括Brush类型的Fill属性和Stroke属性。
它有这么几个子类:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Rectangle Fill="Orange" Stroke="Black" StrokeThickness="5" Width="100" Height="100" RadiusX="30" RadiusY="20" /> <Ellipse Grid.Column="1" Fill="SkyBlue" Stroke="BurlyWood" Width="100" Height="80" StrokeThickness="5"/> <Line Grid.Column="2" Fill="Red" Stroke="DarkSalmon" X1="20" Y1="20" X2="100" Y2="100" StrokeThickness="5"/> <Line Grid.Column="2" Fill="Red" Stroke="DarkSalmon" X1="100" Y1="20" X2="20" Y2="100" StrokeThickness="5" /> <Polyline Grid.Row="1" Points="20,20 20,100 100,100 100,20 20,20" FillRule="EvenOdd" Fill="Bisque" Stroke="Azure" StrokeDashCap="Round" StrokeThickness="5" StrokeEndLineCap="Triangle"/> <Path Grid.Row="1" Grid.Column="1" Fill="MintCream" Stroke="Purple" StrokeDashCap="Round" StrokeDashArray="1,1,10,15"> <Path.Data> <LineGeometry StartPoint="30,30" EndPoint="80,80" /> </Path.Data> </Path> <Path Grid.Row="1" Grid.Column="1" Fill="MintCream" Stroke="Purple" StrokeDashCap="Round" StrokeDashArray="1,1,10,15" Data="M 80,30 L 30,80 Z"/> </Grid>
在使用XAML进行页面布局时,咱们发现几乎不多直接用Color来交互,而是用Color的封装对象Brush来直接交互。它包括三种Color Brush(SolidColorBrush、LinearGradientBrush和RadialGradientBrush)和三种Tile Brush(DrawingBrush、ImageBrush和VisualBrush)
<Rectangle Grid.Row="1" Grid.Column="2" Width="100" Height="100" Stroke="Black"> <Rectangle.Fill> <LinearGradientBrush StartPoint=".0,.0" EndPoint=".8,.8" SpreadMethod="Repeat"> <LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Red" /> <GradientStop Offset="0.5" Color="Green" /> <GradientStop Offset=".5" Color="Yellow" /> <GradientStop Offset=".7" Color="Yellow" /> <GradientStop Offset=".7" Color="Orange" /> <GradientStop Offset="1" Color="Blue" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Ellipse Grid.Row="2" Width="100" Height="80" Stroke="Red"> <Ellipse.Fill> <SolidColorBrush Color="ContextColor file://C:/Windows/System32/spool/drivers/color/sRGB%20Color%20Space%20Profile.icm 1.0,0.0,0.0,0.0"/> </Ellipse.Fill> </Ellipse> <Ellipse Grid.Row="2" Grid.Column="1" Stroke="Black" Width="100" Height="80"> <Ellipse.Fill> <RadialGradientBrush Center="0.3,0.3" RadiusX="0.7" RadiusY="0.7" GradientOrigin="0,0" SpreadMethod="Repeat"> <GradientStop Offset=".0" Color="Red" /> <GradientStop Offset=".5" Color="Yellow" /> <GradientStop Offset="1" Color="Green" /> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> <ResizeGrip Grid.Row="2" Grid.Column="2" Width="100" Height="100" > <ResizeGrip.Background> <DrawingBrush Stretch="Fill" Viewbox="0 0 .5 1" AlignmentX="Left" AlignmentY="Top" Viewport="0 0 .5 .5" TileMode="FlipXY"> <DrawingBrush.Drawing> <GeometryDrawing Brush="Yellow"> <GeometryDrawing.Pen> <Pen Brush="Red" Thickness="5" /> </GeometryDrawing.Pen> <GeometryDrawing.Geometry> <RectangleGeometry RadiusX="5" RadiusY="3" Rect="0 0 10 10" /> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingBrush.Drawing> </DrawingBrush> </ResizeGrip.Background> </ResizeGrip> <Rectangle Grid.Column="3" Stroke="Black"> <Rectangle.Fill> <ImageBrush ImageSource="/DrawingDemo;component/Images/1.png" /> </Rectangle.Fill> </Rectangle> <Grid Grid.Row="1" Grid.Column="3"> <StackPanel> <TextBox x:Name="tb" FontSize="30" Text="Hello"/> <Rectangle Width="{Binding ActualWidth,ElementName=tb}" Height="{Binding ActualHeight,ElementName=tb}"> <Rectangle.Fill> <VisualBrush Visual="{Binding ElementName=tb}" /> </Rectangle.Fill> <Rectangle.LayoutTransform> <ScaleTransform ScaleY="-0.75" /> </Rectangle.LayoutTransform> <Rectangle.OpacityMask> <LinearGradientBrush EndPoint="0,1"> <GradientStop Offset="0" Color="Transparent" /> <GradientStop Offset="1" Color="#77000000" /> </LinearGradientBrush> </Rectangle.OpacityMask> </Rectangle> </StackPanel> </Grid>
BitmapEffect位图效果指的是System.Windows.Media.Effects命名空间下的五种Effect,可被应用于UIElement、DrawingGroup和Viewport3DVisual等。这五种Effect分别是:
从WPF4开始,这些类都已通过时了,用Effect及其子类来代替,这意味着你在WPF4环境中使用上面的类是没有效果的。咱们来看看新的Effect子类:
注意:这三个类是派生自Effect类,并非派生自BitmapEffect类,BitmapEffect和Effect是同级别的类。
WPF4使用Effect代替BitmapEffect的缘由有这么几点:
能够经过继承ShaderEffect抽象类来实现自定义的像素着色器,须要使用HLSL来编写着色器,而后编译成.ps文件供WPF使用,在Codeplex上已经有了这样的第三方的自定义效果类。
这里随便讲一下WritableBitmap类:Image没有提供建立编辑位图的方法,这是WritableBitmap类的由来。先以一张图来讲明继承关系:
首先须要说明的是,并非全部的像素格式都是支持写入的,常见的支持写入的像素格式有:Bgra3二、Pbgra32和Bgr32等。
WriteableBitmap bitmap = new WriteableBitmap(80, 80, 96, 96, PixelFormats.Bgra32, null); int stride = 80 * bitmap.Format.BitsPerPixel / 8;//计算跨距,每行像素数据须要的字节数量 /* //1.逐像素法 byte[] colors = { 0, 255, 0, 255 }; //按照B,G,R,G的顺序,此处表示绿色 for (int i = 0; i < 80; i++) { for (int j = 0; j < 80; j++) { Int32Rect rect = new Int32Rect(i, j, 1, 1); bitmap.WritePixels(rect, colors, stride, 0); } } * */ //2.一次写入法 Int32Rect rect1 = new Int32Rect(0, 0, 80, 60); byte[] colors1 = new byte[80 * 60 * bitmap.Format.BitsPerPixel / 8]; for (int i = 0; i < 60; i++)//行遍历 { for (int j = 0; j < 80; j++)//列遍历 { int offset = (j + 80 * i) * bitmap.Format.BitsPerPixel / 8;//计算像素的偏移量 colors1[offset] = 0; colors1[offset + 1] = 255; colors1[offset + 2] = 0; colors1[offset + 3] = 255; bitmap.WritePixels(rect1, colors1, stride, 0); } } Image img = new Image(); img.Source = bitmap; img.ToolTip = "Image"; grid.Children.Add(img); img.SetValue(Grid.RowProperty, 3); img.SetValue(Grid.ColumnProperty, 1);
对于大片像素的写入,一次写入法的效率显然要好不少。