本篇咱們來學習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);
對於大片像素的寫入,一次寫入法的效率顯然要好不少。