本文主要講解WPF中的基本圖形知識,內容如下:
1。圖形的基礎知識准備
2。WPF中的圖形體系結構
3。顏色和畫刷
4。Shape
5。Drawing和Visual
1.1WPF中的坐標
1.1.1 WPF的默認坐標:WPF中平面坐標系主要包括原點位置、X和Y軸方向,以及坐標單位。WPF的默認坐標系原點位置在繪制區域的左上角,X軸向右增加,Y軸向下增加。
自定義坐標系:自定義坐標系主要通過Transform類來實現,一般可以使用ScaleTransform和TranslateTransform來進行坐標的反轉和水平移動,如下:
<Canvas> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform ScaleY="-1"></ScaleTransform> <TranslateTransform Y="200"></TranslateTransform> </TransformGroup> </Canvas.RenderTransform> <Line X1="0" Y1="0" X2="100" Y2="100" Stroke="Red" StrokeThickness="2"></Line> <Button Canvas.Top="100" Canvas.Left="120" Foreground="Green" Content="Test Button" FontSize="14"></Button> </Canvas>
上述代碼為一個簡單的進行自定義坐標的例子,一般來說圖形的繪制會使用Canvas來布局,因為Canvas可以指定相對的Top和Left。代碼中RenderTransform決定了坐標系的效果,通過ScaleTransform將Y軸的正方向從向下變為了向上(因為ScaleY是-1,則和之前的相反),TranslateTransform則將原點向下移動了200.效果如下:
可以看到直線的起始位置為下移200后的位置,現在還存在一個問題,進行翻轉后按鈕的文字也是倒的,所以要給按鈕添加一個轉換,即給按鈕添加如下代碼:
<Button.RenderTransform> <ScaleTransform ScaleY="-1"></ScaleTransform> </Button.RenderTransform>
1.1.2WPF中的對象變換
之前已經提到了Transform,當然Transform的類型不僅僅是上邊的兩種類型。WPF中的的變換對象均繼承自類型Transform,類型如下:
TranslateTransform 平移變換,X,Y表示在X軸和Y軸上的平移量;
RotateTransform 旋轉變換,Angle表示旋轉的角度,CenterX和CenterY表示旋轉的中心坐標;
ScaleTransform 縮放變換,ScaleX和ScaleY表示在X軸和Y軸上的縮放比例,當然像上述例子進行翻轉也是ok的,CenterX和CenterY表示縮放的原點坐標;
SkewTransform 錯切變換(也可以成為扭曲變換),AngleX和AngleY表示錯切的角度,CenterX和CenterY表示錯切的原點;
MatrixTransform 矩陣變換;
TransformGroup 當對一個元素執行多個變換的時候,可以通過TransformGroup來設置多個變換,實現組合變換,在這里切記,不同順序的變換,看到的效果是完全不同的。(造成順序很重要的一個原因就是,像旋轉和縮放這樣的變換是針對坐標系的原點進行的。 縮放以原點為中心的對象與縮放已離開原點的對象所得到的結果不同。 同樣,旋轉以原點為中心的對象與旋轉已離開原點的對象所得到的結果也不同。)
這樣說似乎不太明白,還使用剛才的例子,之前的例子中有兩個Transform,ScaleTransform和TranslateTransform,我們的順序也是先前者后后者,這樣看到的效果是正常的,如果我們修改如下:
<TransformGroup> <TranslateTransform Y="200"></TranslateTransform> <ScaleTransform ScaleY="-1"></ScaleTransform> </TransformGroup>
這時候你會很“神奇”的發現,什么都沒了,整個窗體都是空白的,原因就是坐標的問題哦,聽我道來。前文的紅色字體已經說了,縮放是以原點進行變換的,我們將Canvas向下移動了200,而原點為左上角(0,0)所以旋轉的效果其實是在Canvas的范圍之外的,所以我們並不能看到任何元素。要解決這個問題其實也很簡單,那就是設置ScaleTransform的Centery即原點中心,如下代碼:
<TransformGroup> <TranslateTransform Y="200"></TranslateTransform> <ScaleTransform ScaleY="-1" CenterY="200"></ScaleTransform> </TransformGroup>
這樣我們看到的效果和之前看到的就是一樣正確的,那是因為我們將ScaleTransform的中心原點Y軸向下移動了200,這樣還是Canvas的左上角(0,0)位置。
1.2WPF的圖形架構
1.2.1 WPF的二維圖形體系結構首先從邏輯樹(Logic Tree)和可視樹(Visual Tree)。
XAML文件的結構就是一個樹型結構,從一個根節點層層展開。這棵樹就是“邏輯樹”,樹上的每一個節點都是一個完整而獨立的元素。WPF中提供了遍歷邏輯樹和可視樹的方法,即LogicTreeHelper和VisualTreeHelper。
邏輯樹和可視樹的區別:
(1)邏輯樹的層次結構和XAML文件的組織結構類似,而可視樹則與其他節點必須是繼承自Visual和Visual3D;
(2)邏輯樹主要是用在如下方面。
資源查找:如一個元素添加了一個畫刷資源,則會按照邏輯樹的節點層層向上查找,直至邏輯樹根節點;
屬性值繼承:舉個例子,比如某一個窗體設置了字體大小,則將按照邏輯樹的層次結構依次影響其下面的節點。如果某一個節點沒有字體大小屬性,那么則會穿過該節點影響 該節點影響下面包含此屬性的邏輯節點。(比如設置了Window 標簽的屬性FontSize,則會影響其下的邏輯節點)
(3)可視樹主要用來描述用戶界面的外觀,可以通過自定義控件模板修改控件的可視樹從而改變控件的外觀。
1.2.2 WPF二維圖形中重要元素
(1)Visual
可視樹是WPF渲染的基礎,拋開Visual3D,整個圖形用戶界面都是由Visual。即Visual的派生類構成了可視樹,WPF從根節點開始渲染可視樹。Visual主要作用是為WPF繪 制提供支持。
(2)Drawing
Visual可以想象成一個窗口,那么窗口里繪制的內容就交給了Drawing來進行描述。Drawing針對矢量,圖像,文字甚至是視頻派生了下面幾種不同的Drawing。
一。GeometryDrawing:繪制集合圖形。
二。ImageDrawing:繪制圖像。
三。GlyphRunDrawing:繪制文本。
四。ViedoDrawing:播放音頻和視頻文件。
五。DrawingGroup:Drawing的集合。
VisualTreeHelper中的方法GetDrawing從Visual中獲取Drawing集合,其輸入參數是一個Visual,返回值是一個DrawingGroup。
(3)Geometry
繪制幾何圖形時GeometryDrawing需要需要幾何數據,然后設置畫刷和畫筆來繪制。這個幾何數據使用Geometry及其派生來來描述,Geometry只是用來描述二維圖形 的數據,不負責顯示,派生類中包括橢圓,矩形及直線等幾何圖形。
(4)Shape
是一種高級圖形元素 ,派生自FrameworkElement,因此可以任意潛逃在控件或者容器中。
(5)四者之間的關系
一。WPF的可視樹的每一個節點均派生自Visual;
二。Shape和Visual是繼承關系。
Visual <-- UIElement <-- FrameworkElement <-- Shape;
三。Visual的繪制內容通過Drawing來描述,通過VisualTreeHelper的靜態方法GetDrawing可以從一個Visual中獲得一個DrawingGroup。從而可以遍歷Visual中所有Drawing對象;
四。Drawing的一個派生類GeometryDrawing需要用Geometry來描述其幾何數據;
五。Shape派生自Visual,因此具有Visual和Drawing之間的關系。並且Shape具有Fill屬性,可以通過DrawingBrush和Drawing關聯;
六。Path通過一個Data屬性和Geometry關聯。
1.3 顏色和畫刷
1.3.1 WPF中的顏色
WPF中的顏色除了使用Colors枚舉來進行訪問外,還可以通過A(透明值),R,G,B(三原色)屬性指定任意顏色。
Color color = new Color(); //設置為紅色 color = Color.FromRgb(255, 0, 0); //設置為半透明的紅色 color = Color.FromArgb(100, 255, 0, 0); //通過字符串方式為顏色賦值,字符串格式為"#aarrggbb".前兩位為透明度A,后面依次為R,G,B,並且需要十六進制數值表示 color = (Color)ColorConverter.ConvertFromString("#FFFF0000");
1.3.2 畫刷
WPF中有6種畫刷:
(1)SolidColorBrush:單色或者是純色畫刷;
(2)LinearGradientBrush:線性漸變畫刷,通過指定兩個點連成一條線段,在兩點之間的顏色進行線性插值,然后進行漸變填充;
(3)RadialGradientBrush:擴散漸變畫刷,和LinearGradientBrush漸變不同,它是一個呈橢圓狀向外散發;
(4)ImageBrush:圖片畫刷,將圖片填充到控件區域;
(5)DrawingBrush:圖形畫刷,通過Drawing繪制幾何圖形,圖像,文字甚至是視頻,並且還可以形成更復雜的圖形,最終將這些圖形填充到控件區域;
(6)VisualBrush:使用Visual對象填充目標區域。所有的控件均派生自Visual,所以可以將所有的控件填充到區域,但是VisualBrush僅僅繪制了Visual的外觀,填充的區域的 按鈕是不能點擊的。
LinearGradientBrush:
<Grid> <Grid.Background> <LinearGradientBrush> <GradientStop Color="Red" Offset="0"></GradientStop> <GradientStop Color="Blue" Offset="1"></GradientStop> </LinearGradientBrush> </Grid.Background> </Grid>
LinearGradientBrush畫刷一般需要2個或者2個以上的Color對象和兩個Point對象即Offset值,顏色的填充從第一個Point一直到最后一個Point連成一條線段漸變的填充顏色,填充的范圍和Point的值有關系,填充的線段的中心點位兩個Point的中心,整個第一個GradientStop顏色也是從起始Offset漸變到最后一個GradientStop顏色,中間以插值填充.
LinearGradientBrush有StartPoint起始位置和EndPoint結束為止,默認情況下StartPoint為(0,0),EndPoint為(1,1),如果起始位置位置不在左上角(0,0),終點位置也不在右下角(1,1),則有一部分超出了起始點和終點的范圍。漸變畫刷提供了SpreadMethod屬性(適用於LinearGradientBrush和RadialGradientBrush),有3個值可選(Pad,Reflect,Repeat),三個值的區別看下圖:
三幅圖分別為Pad,Reflect,Repeat屬性值對應的效果,Pad應該是沒有什么變化,Reflect是進行了反射的處理,即左邊和右邊的顏色進行對稱,第三個就是重復顯示。
RadialGradientBrush:
RadialGradientBrush以一個起始點呈橢圓狀向外散發漸變,與LinearGradientBrush共享GradientBrush的屬性。RadialGradientBrush由Center,GradientOrigin,Radiusx,RadiusY屬性決定漸變方式,Center表示填充橢圓的中心位置,相對坐標默認為(0.5,0.5),GradientOrigin表示漸變的源點,相對坐標的默認值是(0.5,0.5),即GradientOrigin和Center默認值是重疊在一起的;RadiusX和RadiusY橢圓的半徑,其值均為0.5,表示默認該橢圓的長短半徑是填充區域的長和寬的一半。
<Grid> <Grid.Background> <RadialGradientBrush SpreadMethod="Pad"> <GradientStop Color="Red" Offset="0.2"></GradientStop> <GradientStop Color="Blue" Offset="0.5"></GradientStop> <GradientStop Color="Yellow" Offset="1"></GradientStop> </RadialGradientBrush> </Grid.Background> </Grid>
Tile畫刷:(ImageBrush,DrawingBrush,VisualBrush均派生自TileBrush)
TileBrush可以用重復圖案來填充目標區域,圖案可以是Image,Visual或者Drawing。畫刷的內容分別由不同類型的畫刷決定,ImageBrush通過ImageSource指定畫刷內容;DrawingBrush通過Drawing屬性指定;VisualBrush通過Visual屬性指定。
TileBrush的屬性:
Stretch屬性:有4個枚舉值None,Fill,Uniform和UniformToFill。
None,圖片會被裁減。裁減的區域可以通過AlignmentX和AlignmentY確定;
<StackPanel Orientation="Horizontal"> <Rectangle Width="200" Height="200"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="None"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="None" AlignmentX="Center" AlignmentY="Top"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="None" AlignmentX="Center" AlignmentY="Bottom"></ImageBrush> </Rectangle.Fill> </Rectangle> </StackPanel>
Uniform,將圖片按照比例進行縮放,圖片的長度和段度取決於區域的最小值(即Height和Width中小的一個)。
<StackPanel Orientation="Horizontal"> <Rectangle Width="50" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="Uniform"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="100" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="Uniform" ></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="Uniform" ></ImageBrush> </Rectangle.Fill> </Rectangle> </StackPanel>
可以看到圖片的高度取決於Rectgangle的Height和Width中小的那個值。(當值取決於Height則AlignmentY才有效,取決於Width則僅AlignmentX才有效)
UniformToFill,先考慮完全填充區域,然后考慮比例的縮放。(通過對圖片的裁減,將圖片完全填充到區域,超出的部分則被裁減,說到底就是根據Height和Width的大的 那個值決定圖片的值。)
<StackPanel Orientation="Horizontal" Margin="10"> <Rectangle Width="50" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="100" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" ></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="300"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" ></ImageBrush> </Rectangle.Fill> </Rectangle> </StackPanel> </StackPanel>
可以看到,圖片超出的區域都被裁減了。
ViewBox屬性:指定顯示到區域的畫刷內容,它是一個Rect類型,可以使用相對坐標和絕對坐標,由ViweboxUnits屬性指定。相對坐標依然是圖片的左上角(0,0),右下角為(1,1).默認值為(0,0,1,1)即顯示全部內容。如果ViewBox為(0,0,0.5,0.5)則表示顯示左上角1/4部分到區域。
Viewport屬性:它是一個Rect類型,指定Brush映射到目標區域的哪個部分,可以使用相對坐標和絕對坐標,由ViweboxUnits屬性指定。如果Viewport的值為(0,0,0.5,0.5)且TileMode屬性為None,那么目標區域只有左上角.
TileMode:描述Brush如何填充到目標區域,枚舉值如下。
None:不重復填充。
Tile:重復填充。
FlipX:在水平軸上對Brush隔列翻轉填充。
FlipY:在垂直軸上對Brush隔行翻轉填充。
FlipXY:在兩個方向對Brush隔行隔列翻轉填充。
<StackPanel Orientation="Horizontal" Margin="10"> <Rectangle Width="200" Height="200" Stroke="Black" StrokeThickness="1"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" Viewport="0,0,0.5,0.5" TileMode="None"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200" Stroke="Black" StrokeThickness="1" Margin="5"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" Viewport="0,0,0.5,0.5" TileMode="Tile"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200" Stroke="Black" StrokeThickness="1" Margin="5"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" Viewport="0,0,0.5,0.5" TileMode="FlipX"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200" Stroke="Black" StrokeThickness="1" Margin="5"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" Viewport="0,0,0.5,0.5" TileMode="FlipY"></ImageBrush> </Rectangle.Fill> </Rectangle> <Rectangle Width="200" Height="200" Stroke="Black" StrokeThickness="1" Margin="5"> <Rectangle.Fill> <ImageBrush ImageSource="1.jpg" Stretch="UniformToFill" Viewport="0,0,0.5,0.5" TileMode="FlipXY"></ImageBrush> </Rectangle.Fill> </Rectangle> </StackPanel>
5幅圖依次為None,Tile,FlipX,FlipY,FlipXY.
使用Brush制作一個特效:
<Grid > <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Image x:Name="imgVisual" Grid.Row="0" Source="1.jpg" Height="300" Width="200" Stretch="Fill"></Image> <Rectangle Grid.Row="1" Width="{Binding ActualWidth,ElementName=imgVisual}" Height="{Binding ActualHeight,ElementName=imgVisual}"> <Rectangle.Fill> <VisualBrush Visual="{Binding ElementName=imgVisual}"> <VisualBrush.RelativeTransform> <ScaleTransform ScaleX="1" ScaleY="-1" CenterX="0.5" CenterY="0.5"></ScaleTransform> </VisualBrush.RelativeTransform> </VisualBrush> </Rectangle.Fill> <Rectangle.OpacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="0,0.5"> <GradientStop Offset="0" Color="Red"></GradientStop> <GradientStop Offset="0.5" Color="Blue"></GradientStop> <GradientStop Offset="1" Color="Transparent"></GradientStop> </LinearGradientBrush> </Rectangle.OpacityMask> </Rectangle> </Grid>
實現了倒影的效果。
1.4 Shape
1.4.1 Rectangle(矩形)
<Rectangle Height="200" Width="200" Fill="Yellow"></Rectangle>
Rectangle通過指定Height和Width來設置高和寬,除此之外,Rectangle還可以實現圓角矩形,如下:
<Rectangle Width="200" Height="200" RadiusX="50" RadiusY="50" Fill="Yellow"></Rectangle>
通過指定RadiusX指定使矩形的角變圓的橢圓的 x 軸半徑,指定RadiusY使矩形的角變圓的橢圓的 y 軸半徑.
1.4.2 Ellipse(橢圓)
<Ellipse Width="200" Height="200" Fill="Yellow"></Ellipse>
除了和Rectangle擁有Height和Width屬性之外,它們兩個還有Stroke(設置邊框顏色)和StrokeThickness(設置邊框寬度).
1.4.3 Line(直線)
<Line X1="10" X2="100" Y1="10" Y2="100" Stroke="Black" StrokeThickness="2"></Line>
Line有四個屬性X1,X2,Y1,Y2標識兩個點的坐標,除此之外同樣有Stroke何StrokeThickness。
1.4.4 Polyline和Polygon(線段)
Polyline:
<Polyline Points="10,10 60,10 110,100" Stroke="Black" StrokeThickness="2" Fill="Red"></Polyline>
Polygon:
<Polygon Points="10,10 60,10 110,100" Stroke="Black" StrokeThickness="2" Fill="Red"></Polygon>
Polyline和Polygon使用Points(Point類型)屬性來表示一組線段,兩個的區別是后者會自動添加一條從第一個點到最后一個點的連線。
1.4.5 線型,線帽,線的連接和填充規則
1.4.5.1 線型
線型由StrokeDashArray和StrokeDashOffset兩個屬性控制。StrokeDashArray類型為Double類型的集合,設置一串double數值描述線型。如果設置為"4,3"則表示填充4個單元(線的寬度,即StrokeThickness屬性控制),然后空3個單元,並循環下去;如果設置為"4,1,3"則表示填充4個單檐,空1個單元,然后再填充3個單元,依次循環。StrokeDashOffset描述線型的開始位置,如果StrokeDashArray設置為(4,3),並且設置StrokeDashOffset為2,則表示線段的第一段只填充2個單元(4-2),空3個單元,然后再填充4個單元,繼續循環。
<Line X1="10" X2="300" Y1="30" Y2="30" Stroke="Black" StrokeThickness="2" StrokeDashArray="2,3" ></Line> <Line X1="10" X2="300" Y1="40" Y2="40" Stroke="Black" StrokeThickness="3" StrokeDashArray="2,3" ></Line> <Line X1="10" X2="300" Y1="50" Y2="50" Stroke="Black" StrokeThickness="3" StrokeDashArray="2,3,1" ></Line>
設置StrokeDashOffset:
<Line X1="10" X2="300" Y1="30" Y2="30" Stroke="Black" StrokeThickness="2" StrokeDashArray="2,3" StrokeDashOffset="2"></Line> <Line X1="10" X2="300" Y1="40" Y2="40" Stroke="Black" StrokeThickness="3" StrokeDashArray="2,3" StrokeDashOffset="1"></Line> <Line X1="10" X2="300" Y1="50" Y2="50" Stroke="Black" StrokeThickness="3" StrokeDashArray="2,3,1" StrokeDashOffset="10" ></Line>
1.4.5.2 線帽
線的兩端可使用StrokeStartLineCap和StrokeEndLineCap設置,WPF中有4種線帽可選擇Flat(平線帽,默認),Square(方帽),Round(圓帽),Triangle(尖帽);Square和Flat的區別在於,Square會將線段的兩頭延伸線寬的一半作為方帽。StrokeDashCap控制線段內部每一小段的兩端線帽,對線段的兩端無效。
<Line X1="30" X2="300" Y1="30" Y2="30" Stroke="Black" StrokeThickness="10" StrokeStartLineCap="Round" StrokeEndLineCap="Triangle" ></Line> <Line X1="30" X2="300" Y1="30" Y2="30" Stroke="Black" StrokeThickness="10" StrokeEndLineCap="Triangle" ></Line> <Line X1="30" X2="300" Y1="30" Y2="30" Stroke="Black" StrokeThickness="10" StrokeStartLineCap="Square" StrokeEndLineCap="Triangle" ></Line>
1.4.5.3 線的連接
線的連接由StrokeLineJoin屬性控制,WPF中有3種方式,Miter(尖角,默認),Bevel(平角),Round(圓角)
<Polyline Points="10,40 30,10 50,60 70,10 " Stroke="Black" StrokeThickness="10" StrokeLineJoin="Miter"></Polyline> <Polyline Points="10,40 30,10 50,60 70,10 " Stroke="Black" StrokeThickness="10" StrokeLineJoin="Bevel"></Polyline> <Polyline Points="10,40 30,10 50,60 70,10 " Stroke="Black" StrokeThickness="10" StrokeLineJoin="Round"></Polyline>
除此之外還有一個屬性StrokeMiterLimit,用來控制尖角的長度防止角度過小導致的尖角很長,該值大於或等於1,描述的是尖角長度和線寬一半的最大比,即尖角長度最長不能超過 0.5 x StrokeThickness x StrokeMiterLimit。StrokeMiterLimit默認值為10.
1.4.5.4 填充規則
通過設置FillRule屬性實現,只有Polyline和Polygon兩種Shape才有該屬性,該屬性有兩個值,EvenOdd和NonZero。
EvenOdd 枚舉值確定形狀上的某一點是否在“內部”。 它從該點沿任意方向畫一條無限長的射線,然后計算該射線在指定形狀中因交叉而形成的路徑段數。 如果該數字為奇數,則該點在內部;如果該數字為偶數,則該點在外部。
Nonzero 枚舉值確定形狀上的某一點是否在“內部”。 它從該點沿任意方向畫一條無限長的射線,然后檢查形狀段與該射線的交點 計數從零開始,每當段從左向右跨過射線時增加 1,而每當路徑段從右向左跨過射線時減去 1。 計算交點的數目后,如果結果為零,則說明該點在路徑外部。 否則,它位於路徑內部。
注:詳細解釋可參考MSDN(幫助)
1.4.6 調整Shape大小(Strecth的設置)
Strecth仍然是四個None,Fill,Uniform,UniformToFill,具體解釋參考前文中提到的。例子:
<StackPanel Orientation="Horizontal"> <Grid Height="300" Width=" 150"> <Ellipse Stroke="Black" StrokeThickness="2" Stretch="Fill" Fill="Green"></Ellipse> </Grid> <Grid Height="300" Width=" 150"> <Ellipse Stroke="Black" StrokeThickness="2" Stretch="Uniform" Fill="Yellow"></Ellipse> </Grid> <Grid Height="300" Width=" 150"> <Ellipse Stroke="Black" StrokeThickness="2" Stretch="UniformToFill" Fill="Gray"></Ellipse> </Grid> </StackPanel>
1.5 Drawing和Visual
1.5.1 Drawing包括所有需要繪制的信息,如幾何形狀,畫筆和畫刷。不僅能夠繪制幾何形狀,還能夠繪制圖像 、文本,甚至視頻。
Drawing派生類:
GeometryDrawing 繪制幾何圖形 Geometry:描述幾何圖形
Brush:描述如何填充
Pen:描述如何填充外部輪廓線
ImageDrawing 繪制圖像 ImageSource:指定圖像文件的路徑
Rect:指定圖像文件的所在位置
VideoDrawing 播放視頻 Player:媒體播放器
Rect:指定媒體播放器的所在位置
GlyphRunDrawing 繪制文本 GlyphRun:描述繪制文本的信息
ForegroundBrush:指定前景色
DrawingGroup Drawing的集合,類似GeometryGrop Children:指定該集合下的子對象
注:Drawing並非一個元素,所以不能直接放在界面中,使用方法如下:
(1)將Drawing放在DrawingImage中,一般作為Image的屬性。
<Image Height="300" Width="300"> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <DrawingGroup> <GeometryDrawing Brush="Green"> <GeometryDrawing.Geometry> <EllipseGeometry RadiusX="50" RadiusY="50"> </EllipseGeometry> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingGroup> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image>
(2)將Drawing放在DrawingBrush類中,DrawingBrush派生自Brush。
<Button Height="30" Width="200"> <Button.Background> <DrawingBrush> <DrawingBrush.Drawing> <DrawingGroup> <GeometryDrawing Brush="Red"> <GeometryDrawing.Geometry> <EllipseGeometry RadiusX="10" RadiusY="10"></EllipseGeometry> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Button.Background> </Button>
(3)將Drawing放在DrawingVisual中。
(4)使用Drawing將圖形、視頻統一起來。
<Rectangle> <Rectangle.Fill> <DrawingBrush> <DrawingBrush.Drawing> <DrawingGroup> <VideoDrawing></VideoDrawing> <ImageDrawing></ImageDrawing> </DrawingGroup> </DrawingBrush.Drawing> </DrawingBrush> </Rectangle.Fill> </Rectangle>
1.5.2 Visual
WPF中引入了DrawingVisual類,該類只保留了渲染所必需的特性,汝透明、剪切。同時由於DrawingVisual派生自Visual,因此保留了Hit-test行為。
1.5.2.1 DrawingVisual和DrawingContext
DrawingVisual不能直接在XAML中設置Drawing屬性,所以需要DrawingContext繪制Drawing。
FormattedText text = new FormattedText("Listenfly",new System.Globalization.CultureInfo("en-us"),FlowDirection.LeftToRight, new Typeface(this.FontFamily,FontStyles.Normal,FontWeights.Normal,new FontStretch()),this.FontSize,this.Foreground); DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); drawingContext.DrawText(text,new Point(2,2)); drawingContext.Close(); RenderTargetBitmap bmp = new RenderTargetBitmap(200, 20, 120, 100, PixelFormats.Pbgra32); bmp.Render(drawingVisual); img.Source = bmp;
通過DrawingContext的DrawText寫入文本,DrawingContext通過DrawingVisual的RenderOpen方法得到,最后還要調用Close方法關閉。
使用Visual繪制一個兔子(摘自 WPF 葵花寶典):
<Application.Resources> <SolidColorBrush x:Key="headcolor" Color="#FFC9CACA"/> <!--第二個兔頭--> <PathGeometry x:Key="geometryhead2" Figures="M136.75,142.11L130.298,147.411L130.798,159.911L133.798,173.411L146.298,199.911L150.798,206.911L157.798,207.411L193.298,205.411L199.298,202.911L210.798,198.911L212.798,191.411L217.298,167.911L215.798,144.911L212.798,134.411L203.298,130.911L184.298,129.411L168.798,130.911L156.298,133.411L136.75,142.11z"/> <PathGeometry x:Key="geometryear2" Figures="M140.5,140.11L127.5,117.61L126.5,112.11L132.5,105.11L136,102.61L142.5,101.61L145.5,105.11L156.5,132.61L140.5,140.11 M184.5,128.61L184.5,101.11L186.5,99.11L193.5,96.61L202,98.61L206,101.11L204,114.11L203.5,130.11L184.5,128.61z"/> <PathGeometry x:Key="geometryeyeorbit2" Figures="M142.5,171.11L142.25,172.61L148,177.86L157,180.61C157,180.61,162,181.61,162,180.86C162,180.11,169.5,175.61,169.5,175.61L174,169.86L171.25,157.61L142.5,171.11 M174,168.86L181.5,173.61L184,174.11L196.75,174.86L199.5,173.11L204.25,168.11L206.25,161.11L207.25,157.11L181,157.508L174.25,157.61L171.25,156.61L174,168.86z"/> <PathGeometry x:Key="geometryeyelid2" Figures="M142.25,150.36L139.75,154.36L138.25,164.11L140.25,170.36L142.5,171.11L150.25,168.61L158.75,164.61L165,160.86L168.5,159.61L171.25,157.61L171,149.61L166,145.36L161,143.86L153.5,143.36L145.75,147.11L142.25,150.36 M208.75,157.36L174.25,158.61L171.25,157.61L171.75,149.36L174.25,144.36L178.75,141.11L182.75,138.86L195,138.86L198.5,140.61L203.5,145.36L206.5,149.86L208.75,157.36z"/> <PathGeometry x:Key="geometryeyeball2" Figures="M167.75,159.61L171,158.86L170.25,161.61L169.25,162.86L165.75,162.61L164.75,161.86L167.75,159.61 M180.5,160.11L179.5,161.61L177.5,162.11L175.5,161.36L174.25,158.61L180.25,158.36L180.5,160.11z"/> <PathGeometry x:Key="geometrymouth2" Figures="M160,198.36L159,200.36L159,202.36L194.75,200.86L199.25,198.86L201.25,196.36L198.25,192.61L193.739,192.793L179.75,193.36L169.25,195.11L163.25,196.36L160,198.36z"/> <PathGeometry x:Key="geometrylongue2" Figures="M193.25,195.11L193.25,203.11L193,210.86L190.5,214.86L187,220.11L183.25,221.86L179,223.11L172,223.11L165.75,221.11L160.75,215.86L158.25,210.86L159,202.36L160.5,200.36L173,196.61L193.25,195.11z"/> <!--第一個兔頭--> <PathGeometry x:Key="geometryhead1" Figures="M214.667,131.11L213.667,148.11L214.333,159.444L217,167.777L220,179.11L225,187.777L238,192.11L267,191.777L271.667,190.444L279.333,189.444L285,178.777L288.333,166.444L291.333,154.444L290.667,132.11L287,129.11L282,126.444L266,121.444L239,121.777L223,126.11L214.667,131.11z"/> <PathGeometry x:Key="geometryear1" Figures="M223,126.777L221,117.11L219.333,110.444L219.333,102.444L222.667,98.11L227.667,96.11L235.333,95.444L237.333,98.11L238.667,105.444L237.667,110.444L238.667,118.11L238,122.777L223,126.77 M274.667,100.444L270,108.444L267.333,115.444L267,122.11L272.667,123.777L281.333,126.444L283.333,120.777L287,118.444L289.667,113.777L291,107.444L284,101.11L274.667,100.444z"/> <PathGeometry x:Key="geometryeyeorbit1" Figures="M230,132.11L226.333,134.777L222.667,143.444L223,150.11L227.333,156.11L232.667,159.777L242,160.777L248.667,157.11L252.667,153.11L252.667,136.11L245.333,130.11L234.333,129.777L230,132.11 M277.667,133.444L272.667,130.777L267,130.11L263,129.777L258,131.444L255,134.444L252.667,136.11L252.667,153.777L255.333,156.777L261.333,159.777L268.667,161.11L275.667,158.444L279.667,154.444L282,150.444L282.333,143.11L279.667,136.11L277.667,133.444z"/> <PathGeometry x:Key="geometryeyelid1" /> <GeometryGroup x:Key="geometryeyeball1"> <EllipseGeometry Center="268,144.444" RadiusX="2.334" RadiusY="3"/> <EllipseGeometry Center="237.667,144.444" RadiusX="2.334" RadiusY="3"/> </GeometryGroup> <LineGeometry x:Key="geometrymouth1" StartPoint="247,186.11" EndPoint="257.667,186.11"/> <PathGeometry x:Key="geometrylongue1" /> </Application.Resources>
上述代碼主要是兔子的頭部、耳朵、眼睛、嘴巴四部分的Path路徑,另外考慮到對各個部分實現點擊的效果,所有分別有兩個對應的Path。
繪制Visual需要一個放Visual的容器,該容器重寫一個屬性VisualChildCount(告訴WPF該容器VIsual的數量)和一個方法GetVisualChild(根據索引的到任意一個VIsual),代碼如下:
public class DrawingVisualHost : FrameworkElement { // Create a collection of child visual objects. private VisualCollection _children; private DrawingVisual headdrawingvisual; private DrawingVisual eardrawingvisual; private DrawingVisual eyedrawingvisual; private DrawingVisual mousedrawingvisual; public DrawingVisualHost() { _children = new VisualCollection(this); headdrawingvisual = CreateHeadDrawingVisual(true); eardrawingvisual = CreateEarDrawingVisual(true); eyedrawingvisual = CreateEyeDrawingVisual(true); mousedrawingvisual = CreateMouseDrawingVisual(true); _children.Add(headdrawingvisual); _children.Add(eardrawingvisual); _children.Add(eyedrawingvisual); _children.Add(mousedrawingvisual); this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(MyVisualHost_MouseLeftButtonUp); this.MouseRightButtonDown += new System.Windows.Input.MouseButtonEventHandler(DrawingVisualHost_MouseRightButtonDown); } void DrawingVisualHost_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { _children.RemoveRange(0, 4); headdrawingvisual = CreateHeadDrawingVisual(true); eardrawingvisual = CreateEarDrawingVisual(true); eyedrawingvisual = CreateEyeDrawingVisual(true); mousedrawingvisual = CreateMouseDrawingVisual(true); _children.Add(headdrawingvisual); _children.Add(eardrawingvisual); _children.Add(eyedrawingvisual); _children.Add(mousedrawingvisual); } void MyVisualHost_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { System.Windows.Point pt = e.GetPosition((UIElement)sender); HitTestResult hittestresult = VisualTreeHelper.HitTest(this, pt); if (headdrawingvisual == hittestresult.VisualHit) { // MessageBox.Show("擊中了兔子頭部!"); _children.Remove(headdrawingvisual); headdrawingvisual = CreateHeadDrawingVisual(false); _children.Insert(0, headdrawingvisual); } else if (eardrawingvisual == hittestresult.VisualHit) { // MessageBox.Show("擊中了兔子耳朵!"); _children.Remove(eardrawingvisual); eardrawingvisual = CreateEarDrawingVisual(false); _children.Insert(1, eardrawingvisual); } else if (eyedrawingvisual == hittestresult.VisualHit) { // MessageBox.Show("擊中了兔子眼部!"); _children.Remove(eyedrawingvisual); eyedrawingvisual = CreateEyeDrawingVisual(false); _children.Insert(2, eyedrawingvisual); } else if (mousedrawingvisual == hittestresult.VisualHit) { _children.Remove(mousedrawingvisual); mousedrawingvisual = CreateMouseDrawingVisual(false); _children.Insert(3, mousedrawingvisual); } VisualTreeHelper.HitTest(this, null, new HitTestResultCallback(myCallback), new PointHitTestParameters(pt)); } public HitTestResultBehavior myCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { if (headdrawingvisual == result.VisualHit) { _children.Remove(headdrawingvisual); headdrawingvisual = CreateHeadDrawingVisual(false); _children.Insert(0, headdrawingvisual); } else if (eardrawingvisual == result.VisualHit) { _children.Remove(eardrawingvisual); eardrawingvisual = CreateEarDrawingVisual(false); _children.Insert(1, eardrawingvisual); } else if (eyedrawingvisual == result.VisualHit) { _children.Remove(eyedrawingvisual); eyedrawingvisual = CreateEyeDrawingVisual(false); _children.Insert(2, eyedrawingvisual); } else if (mousedrawingvisual == result.VisualHit) { _children.Remove(mousedrawingvisual); mousedrawingvisual = CreateMouseDrawingVisual(false); _children.Insert(3, mousedrawingvisual); } } else { _children.RemoveRange(0, 4); headdrawingvisual = CreateHeadDrawingVisual(true); eardrawingvisual = CreateEarDrawingVisual(true); eyedrawingvisual = CreateEyeDrawingVisual(true); mousedrawingvisual = CreateMouseDrawingVisual(true); _children.Add(headdrawingvisual); _children.Add(eardrawingvisual); _children.Add(eyedrawingvisual); _children.Add(mousedrawingvisual); } // Stop the hit test enumeration of objects in the visual tree. return HitTestResultBehavior.Continue; } private DrawingVisual CreateHeadDrawingVisual(bool change) { DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); GeometryDrawing headdrawing = new GeometryDrawing(); SolidColorBrush solidcolorbrush = this.FindResource("headcolor") as SolidColorBrush; if (solidcolorbrush == null) { drawingContext.Close(); return null; } if (change) { Geometry headgeometry = this.FindResource("geometryhead1") as Geometry; if (headgeometry == null) { drawingContext.Close(); return null; } headdrawing.Brush = solidcolorbrush; headdrawing.Geometry = headgeometry; } else { Geometry headgeometry = this.FindResource("geometryhead2") as Geometry; if (headgeometry == null) { drawingContext.Close(); return null; } headdrawing.Brush = solidcolorbrush; headdrawing.Geometry = headgeometry; } drawingContext.DrawDrawing(headdrawing); drawingContext.Close(); return drawingVisual; } private DrawingVisual CreateEarDrawingVisual(bool change) { DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); GeometryDrawing eardrawing = new GeometryDrawing(); SolidColorBrush solidcolorbrush = this.FindResource("headcolor") as SolidColorBrush; if (solidcolorbrush == null) { drawingContext.Close(); return null; } if (change) { eardrawing.Brush = solidcolorbrush; Geometry eargeometry = this.FindResource("geometryear1") as Geometry; eardrawing.Geometry = eargeometry; } else { eardrawing.Brush = solidcolorbrush; Geometry eargeometry = this.FindResource("geometryear2") as Geometry; eardrawing.Geometry = eargeometry; } drawingContext.DrawDrawing(eardrawing); drawingContext.Close(); return drawingVisual; } private DrawingVisual CreateEyeDrawingVisual(bool change) { DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); if (change) { OuterGlowBitmapEffect bitmapeffect = new OuterGlowBitmapEffect(); bitmapeffect.GlowColor = Colors.Black; bitmapeffect.GlowSize = 10; drawingContext.PushEffect(bitmapeffect, null); // 兔眼眶 DrawingGroup eyedrawing = new DrawingGroup(); GeometryDrawing eyeorbitdrawing = new GeometryDrawing(); eyeorbitdrawing.Brush = new SolidColorBrush(Colors.White); Geometry eyegorbiteometry = this.FindResource("geometryeyeorbit1") as Geometry; eyeorbitdrawing.Geometry = eyegorbiteometry; eyedrawing.Children.Add(eyeorbitdrawing); // 兔眼珠 GeometryDrawing eyeballdrawing = new GeometryDrawing(); eyeballdrawing.Brush = new SolidColorBrush(Colors.Black); Geometry eyeballgeometry = this.FindResource("geometryeyeball1") as Geometry; eyeballdrawing.Geometry = eyeballgeometry; eyedrawing.Children.Add(eyeballdrawing); drawingContext.DrawDrawing(eyedrawing); drawingContext.Pop(); } else { // 兔眼眶 DrawingGroup eyedrawing = new DrawingGroup(); GeometryDrawing eyeorbitdrawing = new GeometryDrawing(); eyeorbitdrawing.Brush = new SolidColorBrush(Colors.White); Geometry eyegorbiteometry = this.FindResource("geometryeyeorbit2") as Geometry; eyeorbitdrawing.Geometry = eyegorbiteometry; eyedrawing.Children.Add(eyeorbitdrawing); // 兔眼珠 GeometryDrawing eyeballdrawing = new GeometryDrawing(); eyeballdrawing.Brush = new SolidColorBrush(Colors.Black); Geometry eyeballgeometry = this.FindResource("geometryeyeball2") as Geometry; eyeballdrawing.Geometry = eyeballgeometry; eyedrawing.Children.Add(eyeballdrawing); drawingContext.DrawDrawing(eyedrawing); } drawingContext.Close(); return drawingVisual; } private DrawingVisual CreateMouseDrawingVisual(bool change) { DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); if (change) { GeometryDrawing mouthdrawing = new GeometryDrawing(); mouthdrawing.Brush = new SolidColorBrush(Colors.Black); mouthdrawing.Pen = new Pen(new SolidColorBrush(Colors.Black), 1); Geometry mouthgeometry = this.FindResource("geometrymouth1") as Geometry; mouthdrawing.Geometry = mouthgeometry; drawingContext.DrawDrawing(mouthdrawing); } else { GeometryDrawing mouthdrawing = new GeometryDrawing(); mouthdrawing.Brush = new SolidColorBrush(Colors.Black); mouthdrawing.Pen = new Pen(new SolidColorBrush(Colors.Black), 1); Geometry mouthgeometry = this.FindResource("geometrymouth2") as Geometry; mouthdrawing.Geometry = mouthgeometry; drawingContext.DrawDrawing(mouthdrawing); GeometryDrawing longuedrawing = new GeometryDrawing(); longuedrawing.Brush = new SolidColorBrush(Colors.Red); longuedrawing.Pen = new Pen(new SolidColorBrush(Colors.Red), 1); Geometry longuegeometry = this.FindResource("geometrylongue2") as Geometry; longuedrawing.Geometry = longuegeometry; drawingContext.DrawDrawing(longuedrawing); } drawingContext.Close(); return drawingVisual; } // Provide a required override for the VisualChildrenCount property. protected override int VisualChildrenCount { get { return _children.Count; } } // Provide a required override for the GetVisualChild method. protected override Visual GetVisualChild(int index) { if (index < 0 || index >= _children.Count) { throw new ArgumentOutOfRangeException(); } return _children[index]; } }
MyVisualHost_MouseLeftButtonUp事件中進行Hit-test的實現,通過VisualTree的一個靜態方法HitTest,直接將當前鼠標點位作為參數傳遞,然后返回一個HitTestResult類型對象,其中有一個屬性VisualHit屬性。如果Visual檢測到鼠標命中,則為該Visual;否則為null。
如果希望一次點擊檢測多個Visual對象(重疊情況,如兔子的頭部和眼睛重疊),那么可以使用回調函數的方法(代碼中的myCallback方法)檢測鼠標是否命中。
最后附上 兔子的代碼(我是兔子).