在WPF使用者界面中,繪制2D圖形内容的最簡單方法是使用形狀(shape)——專門用于表示簡單的直線、橢圓、矩形以及多變形的一些類。從技術角度看,形狀就是所謂的繪圖圖元(primitive)。可組合這些基本元素來建立更複雜的圖形。
關于WPF中形狀的重要細節是,它們都繼承自FrameworkElement類。是以,形狀是元素。這樣會帶來許多重要的結果:
- 形狀繪制自身。不需要管理無效的情況和繪圖過程。例如,當移動内容、改變視窗尺寸或改變形狀屬性時,不需要手動重新繪制形狀。
- 使用與其他元素相同的方式組織形狀。換句話說,可在前面學過的任何布局容器中放置形狀(盡管Canvas明顯是最有用的容器,因為它允許在特定的坐标位置放置形狀,當建構複雜的具有多個部分的圖畫時,這很重要)。
- 形狀支援與其他元素相同的事件。這意味着為了處理焦點、按下鍵盤、移動滑鼠以及單擊滑鼠等,不必執行任何額外工作。可使用用于其他元素的相同僚件集,并同樣支援工具提示、上下文菜單和拖放操作。
一、Shape類
每個形狀都繼承自抽象類System.Windows.Shapes.Shape。下圖顯示了形狀類的繼承層次。

圖 WPF形狀類
正如上面看到的,相對來說,隻有很少一部分類繼承自Shape類。Line、Ellipse以及Rectangle都很直覺,Polyline是一系列互相連接配接的直線,Polygon是由一系列互相連接配接的直線形成的閉合圖形。最後,Path類功能強大,能将多個基本形狀組合成單獨的元素。
盡管Shape類自身不能執行任何工作,但它定義了少量的重要屬性。下表列出了這些屬性。
表 Shape類的屬性
二、矩形和橢圓
矩形和橢圓是兩個最簡單的形狀。為建立矩形或橢圓,需要設定大家熟悉的Height和Width屬性(這兩個屬性繼承自FrameworkElement類)來定義形狀的尺寸,然後設定Fill或Stroke屬性(或同時設定這兩個屬性)使形狀可見。還可以使用MinHeigth、MinWidth、HorizontalAlignment、VerticalAlignment以及Margin等屬性。
下面舉一個簡單示例,該例在StackPanel面闆上放置了一個橢圓和一個矩形,效果圖如下所示:
<StackPanel>
<Ellipse Fill="Yellow" Stroke="Blue" Height="50" Width="100" Margin="5" HorizontalAlignment="Left"></Ellipse>
<Rectangle Fill="Yellow" Stroke="Blue" Height="50" Width="100" Margin="5" HorizontalAlignment="Left"></Rectangle>
</StackPanel>
Ellipse類沒有增加任何屬性。Rectangle類隻增加了兩個屬性:RadiusX和RadiusY。如果将這兩個屬性的值設為非零值,就可以建立出美觀的圓形拐角。
可認為RadiusX和RadiusY屬性是用于填充矩形拐角的橢圓。例如,如果将這兩個屬性都設為10,WPF會使用10個機關寬的圓形邊緣繪制拐角。随着半徑的增大,矩形拐角的更多部分會被替換。如果增加RadiusY屬性的值,使其大于RadiusX屬性的值,矩形拐角的左邊和右邊會更平緩,而頂部和底邊的邊緣會更尖銳。如果增大RadiusX屬性的值,使其等于矩形寬度,并增加RadiusY屬性的值,使其等于矩形的寬度,矩形最後會變成普通的橢圓。如下圖所示:
<Window x:Class="Drawing.RoundedRectangles"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="RoundedRectangles" Height="447.744" Width="300">
<StackPanel>
<TextBlock Margin="5,5,0,0">Corner radius of 5.</TextBlock>
<Rectangle Fill="Yellow" Stroke="Blue" RadiusX="5" RadiusY="5"
Width="100" Height="60" Margin="5" HorizontalAlignment="Left">
</Rectangle>
<TextBlock Margin="5,5,0,0">Corner radius of 10.</TextBlock>
<Rectangle Fill="Yellow" Stroke="Blue" RadiusX="10" RadiusY="10"
Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle>
<TextBlock Margin="5,5,0,0">Corner radius of 10 (X) and 25 (Y).</TextBlock>
<Rectangle Fill="Yellow" Stroke="Blue" RadiusX="10" RadiusY="25"
Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle>
<TextBlock Margin="5,5,0,0">Corner radius of 100 (X) and 60 (Y).</TextBlock>
<Rectangle Fill="Yellow" Stroke="Blue" RadiusX="100" RadiusY="60"
Width="100" Height="60" Margin="5" HorizontalAlignment="Left"></Rectangle>
</StackPanel>
</Window>
RoundedRectangles
三、改變形狀的尺寸和放置形狀
正如前面所知,赢編碼尺寸通常不是建立使用者界面的理想方法。它們會限制處理動态内容的能力,并會使應用程式本地化到其他語言變得更加困難。
當繪制形狀時,不再總是關心這些問題。通常,需要更嚴格地控制形狀的位置。然而,在許多情況下仍需要靈活一點設計。Ellipse和Rectangle為了适應可用的空間,都能自動改變自身。
如果為提供Height和Width屬性,形狀會根據它們的容器來設定自身的尺寸。在上一個示例中,如果删除Height和Width值(并且不設定MinHeight和MinWidth值),就會導緻形狀縮小到看不見,因為StackPanel面闆為了适應其内容改變了尺寸。然而,如果強制StackPanel面闆的寬度為整個視窗的寬度(通過将HorizontalAlignment屬性設定為Stretch),并将橢圓的HorizontalAlignment屬性設定為Stretch,删除橢圓的Width屬性值,這時橢圓的寬度就是整個視窗的寬度。
可使用Grid容器構造更好的示例。如果使用按比例改變行尺寸的行為(預設行為),就可使用下面更精簡的标記建立填滿視窗的橢圓:
<Grid>
<Ellipse Fill="Yellow" Stroke="Blue"></Ellipse>
</Grid>
在上面的标記中,Grid面闆填滿了整個視窗。Grid面闆包含了一個按比例改變尺寸的行,該行填滿了整個Grid面闆。最後,橢圓填滿了整行。
改變形狀尺寸的行為依賴于Stretch屬性的值(該屬性在Shape類中定義)。預設情況下,該屬性被設定為Fill。如果改變指定明确的尺寸,這一設定會拉伸形狀,使其填滿容器。下表列出了Stretch屬性的所有可能值。
表 Stretch枚舉值
下圖顯示了Fill、Uniform、UniformToFill枚舉值之間的差別.
<Window x:Class="Drawing.FillModes"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="FillModes" Height="270" Width="477"
>
<Grid ShowGridLines="True" Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Ellipse Fill="Yellow" Stroke="Blue"></Ellipse>
<Ellipse Fill="Yellow" Stroke="Blue" Grid.Column="1" Stretch="Uniform"></Ellipse>
<Ellipse Fill="Yellow" Stroke="Blue" Grid.Column="2" Stretch="UniformToFill "></Ellipse>
<TextBlock Grid.Row="1" TextAlignment="Center">Fill</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" TextAlignment="Center">Uniform</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="2" TextAlignment="Center">UniformToFill</TextBlock>
</Grid>
</Window>
FillModes
通常,将Stretch的值設定為Fill相當于将HorizontalAlignment和VerticalAlignment屬性設定為Stretch。但如果選擇為形狀設定固定的寬度和高度,二者就有差別了。對于這種情況,會簡單地忽略HorizontalAlignment和VerticalAlignment值。而Stretch設定仍然起作用——該屬性決定如何在給定的範圍内改變形狀内容的尺寸。
到目前位置,已看到如何改變Rectangle和Ellipse形狀的尺寸,但如何準确地将它們放到期望的位置呢?WPF形狀與其他元素使用相同的布局系統。然而,有些布局容器是不合适的。例如,通常不希望使用StackPanel、DockPanel以及WrapPanel面闆,因為它們都被設計為獨立的元素。Grid面闆更靈活一些,因為它允許在同一個單元格中放置任意多個元素(盡管不能在單元格中的不同部分定位矩形和橢圓)。理想容器是Canvas,該容器要求使用Left、Top、Right或Bottom附加屬性,為每個形狀指定坐标。這樣可以完全控制形狀如何互相重疊:
<Canvas>
<Ellipse Fill="Yellow" Stroke="Blue" Height="50" Width="100" Canvas.Top="50" Canvas.Left="100"></Ellipse>
<Rectangle Fill="Yellow" Stroke="Blue" Height="50" Width="100" Canvas.Top="40" Canvas.Left="30"></Rectangle>
</Canvas>
如果使用Canvas容器,标簽的順序是很重要的。在上面的示例中,矩形疊加在橢圓之上,因為在标簽清單中首先出現的是橢圓,是以首先繪制橢圓。
請記住,Canvas容器不在需要占據整個視窗。例如,完全可以建立一個Grid面闆,并在該Grid面闆的某個單元格中使用Canvas容器,對于在可自由流動的動态使用者界面中鎖定一小部分繪圖邏輯,這是一種非常好的方法。
四、使用Viewbox控件縮放形狀
使用Canvas控件的唯一限制是圖形不能改變自身的尺寸以适應更大或更小的視窗。對于按鈕這非常合理(在這些情況下,按鈕不改變尺寸),但是對于其他類似的圖形内容,情況就未必如此了。
對于此類情況,WPF提供了簡便的解決方法。如果希望聯合Canvas控件的精确控制功能和友善的改變尺寸功能,可使用Viewbox元素。
Viewbox是繼承自Decorator的簡單類。該類隻接受一個子元素,并拉伸或縮小子元素以适應可用的空間。當然,這個單一的子元素可以是布局容器,其中包含大量形狀(或其他元素),這些元素将同步地改變尺寸。然而,Viewbox更長用于矢量圖像而不是普通控件。
盡管可在Viewbox元素中放置單個形狀,但這并不能提供任何實際的優點。反而,當需要封裝構成一幅圖畫(drawing)的一組形狀時,Viewbox元素才有用處。通常,将在Viewbox控件中放置Canvas,并在Canvas面闆中放置形狀。
下面的示例在Grid控件的第二行中放置了一個包含Canvas面闆的Viewbox元素。Viewbox元素占用改行的整個高度和寬度。該行占用繪制自動改變尺寸的第一行剩餘的所有空間,下面是标記:
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock> The first row of a grid</TextBlock>
<Viewbox Grid.Row="1" HorizontalAlignment="Left" MaxHeight="500">
<Canvas Width="200" Height="150">
<Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="10" Canvas.Top="50"
Width="100" Height="50" HorizontalAlignment="Left"></Ellipse>
<Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30" Canvas.Top="40"
Width="100" Height="50" HorizontalAlignment="Left"></Rectangle>
</Canvas>
</Viewbox>
</Grid>
下圖顯示了當改變視窗尺寸時,Viewbox控件如何調整自身。第一行沒有變化。然而為填滿額外控件,第二行進行了擴充。正如看到的,Viewbox控件中的形狀也根據視窗增大的比例改變了他們的大小。
預設情況下,Viewbox元素按比例地執行縮放,保持它所包含内容的縱橫比。在目前示例中,這意味着即使包含行的形狀發生了變化(變寬或變高),内部形狀也不會變形。相反,Viewbox元素使用适應可用空間内部的最大縮放系數。然而,可使用Viewbox.Stretch屬性改變該行為。預設情況下,将該屬性設定為Uniform。可将其改變為Fill,Viewbox元素中的内容會在兩個方向上被拉伸以完全适應可用空間,即使可能會破壞原來的繪圖也會如此。還可通過使用StretchDirection屬性獲得更大的控制權。預設情況下,該屬性被設定為Both,但可使用UpOnly值建立隻會增長而不會收縮超過其原始尺寸的内容,并且可以使用DownOnly建立隻會縮小而不會增長的内容。
為時Viewbox元素執行其縮放工作,需要能夠确定兩部分資訊:(如果不放在Viewbox元素中)内容應當具有的原始尺寸和希望内容具有的新尺寸。
第二個細節——新尺寸——非常簡單。Viewbox元素根據Stretch屬性,讓其内部的内容使用所有可用空間,這意味着Viewbox元素越大,其内部的内容就越大。
第一個細節——原始尺寸,不使用Viewbox空間時的尺寸——隐含在定義嵌套内容的方式中。在前面的示例中,Canvas的尺寸被明确設定為200X150機關大小。是以,Viewbox從該開始點縮放圖像。例如,橢圓最初是100機關寬,這意味着它占用Canvas面闆一半的繪圖空間。随着Canvas控件的增大,Viewbox元素會遵循這些比例,并且橢圓繼續占用一半的可用控件。
然而,如果删除Canvas控件的Width和Height屬性,分析會發生什麼情況。現在,Canvas控件的尺寸被設定為0X0機關大小,是以Viewbox控件不能改變它的尺寸,并且嵌套在其中的内容不會顯示(這與隻使用Canvas控件時的行為不同。因為盡管Canvas控件的尺寸仍設定為0X0,但隻要Canvas.ClipToBounds屬性沒有被設定為true,就仍然允許在Canvas控件之外的區域繪制形狀。而Viewbox控件不能容忍這一錯誤)。
現在分析一下,如果在按比例改變尺寸的Grid面闆的單元格中封裝Canvas面闆,并且沒有指定Canvas面闆的尺寸,情況又會怎樣。如果沒有使用Viewbox元素,該方法可工作得很好——拉伸Canvas面闆以填充單元格,并且内部的内容是可見的。但如果将所有内容放在Viewbox元素中,這種方法就會失效。Viewbox控件不能确定最初尺寸,是以不能響應地改變Grid面闆的尺寸。
可通過直接在能自動改變尺寸的容器(如Grid面闆)中放置特定的形狀(如Rectangle和Ellipse)來避免這個問題。然後Viewbox控件就能評估Grid面闆為了适合其内容所需的最小尺寸,并且縮放Grid面闆以适應可用空間。然而,在ViewBox元素中擷取真正所希望的尺寸的最簡單方法,是在具有固定尺寸的元素中封裝内容,可以是Canvas面闆、按鈕或其他控件。這樣,固定尺寸就變成了Viewbox控件進行計算所需要的原始尺寸。以這種方式寫死尺寸不會限制布局的靈活性,因為Viewbox元素根據可用空間和布局容器按比例改變尺寸。
五、直線
Line形狀表示連接配接一個點和另一個點的一條直線。起點和重點由4個屬性設定:X1與Y1(用于第一個點)和X2與Y2(用于第二個點)。例如,下面是一條從點(0,0)伸展到點(10,100)的直線:
<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"></Line>
對于直線,Fill屬性不起作用,必須設定Stroke屬性。
在直線中使用的坐标是相對于放置直線的矩形區域左上角的坐标。例如,如果在StackPanel面闆上放置上面的直線,坐标(0,0)指向在StackPanel面闆上放置該矩形區域的位置,這可能是視窗的左上角,也可能不是。如果StackPanel面闆的Margin屬性值不為0,或直線在其他元素之後,直線的開始點(0,0)與視窗頂部會有一定的距離。
然而,在直線中使用負坐标值是非常合理的。實際上,可為直線使用能超出為直線保留的空間的坐标,進而在視窗的其他任意部分繪制直線。對于到目前位置介紹的Rectangle和Ellipse形狀;這是不可能的。然而,這一模型也有缺點,直線不能使用流内容模型。這意味着為直線設定Margin、HorizontalAlignment以及VerticalAlignment屬性是沒有意義的,因為它們沒有任何效果。對于Polyline和Polygon形狀具有相同的限制。
如果在Canvas面闆上放置了Line形狀,那麼仍應用附加的位置屬性(如Top和Left)。它們決定直線的開始位置。換句話說,兩個直線坐标被平移一定的距離。分析下面的直線:
<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"
Canvas.Left="5" Canvas.Top="100"/>
這條直線從點(0,0)伸展到點(10,100),使用的坐标系統将Canvas控件上的點(5,100)作為點(0,0)。這相當于下面不使用Top和Left屬性的直線:
<Line Stroke="Blue" X1="5" Y1="100" X2="15" Y2="200"/>
當在Canvas面闆上放置Line形狀時,是否使用位置屬性由自己決定。通常,可通過選擇好的開始點簡化直線的繪制,還可以使移動部分圖畫變得容易。例如,如果在Canvas面闆的特定位置繪制幾條直線和其他形狀,相對于附近的點繪制它們是不錯的主意(通過使用相同的Top和Left坐标)。通過這種方法,可根據需要将整個圖畫移到新的位置。
六、折線
可以通過Polyline類繪制一系列互相連接配接的直線。隻需要使用Points屬性提供一系列X和Y坐标。從技術角度看,Points屬性需要使用PointCollection對象,但在XAML中使用基于簡單字元串的文法填充該集合。隻需要提供點的清單,并在每個坐标之間添加空格或逗号。
Polyline形狀可能隻有兩個點。例如下面的Polyline形狀,從點(5,100)伸展到點(15,200):
<Polyline Stroke="Blue" Points="5 100 15 200"/>
為便于閱讀,可在每個X和Y坐标之間使用逗号:
<Polyline Stroke="Blue" Points="5,100 15,200"/>
下面是繪制的更複雜Polyline形狀。點不斷右移,并在更高的Y值——比如(50,160),和更低的Y值——比如(70,130)之間擺動:
<Canvas>
<Polyline Stroke="Blue" StrokeThickness="5"
Points="10,150 30,140 50,160 70,130 90,170 110,120 130,180 150,110 170,190 190,100 210,240" >
</Polyline>
</Canvas>
下圖顯示了最終繪制的線條。
對于這個示例,通過代碼使用各種相應地自動增加X和Y值的循環填充Point集合可能更容易。如果需要建立高度動态的圖形,事實卻是如此——例如,根據從資料庫中提取的資料集改變其外觀的圖示。但是,如果隻是希望建構固定的圖形内容,就根本不需要形狀的具體坐标。相反,可使用另一個工具,如Express Design,繪制恰當的圖形,然後到處到XAML。
七、多邊形
實際上,Polygon和Polyline是相同的。和Polyline類一樣,Polygon類也有包含一系列坐标的Points集合。唯一的差別是:Polygon形狀添加最後一條線段,将最後一個點連接配接到開始點(如果最後一個點就是第一個點,Polygon類和Polyline類就沒有差別了)。可使用Fill畫刷填充該形狀的内部區域。通過修改上一節的示例,顯示Polygon:
<Canvas>
<Polygon Stroke="Blue" StrokeThickness="5" Fill="Yellow"
Points="10,150 30,140 50,160 70,130 90,170 110,120 130,180 150,110 170,190 190,100 210,240" >
</Polygon>
</Canvas>
最終效果圖如下所示:
對于線條從不相交的簡單形狀,填充其内部很容易做到。但有時會遇到更複雜的Polygon形狀,哪些部分屬于内部(并且應當被填充)以及哪些部分屬于外部并不明顯。
下面一個示例,該形狀的特點是一條線段和其他多條線段相交,可能希望填充也希望不填充中央的不規則區域。顯然,可通過将該圖像分割成更小的形狀來準确地控制填充區域。但不需要這麼做。
<Polygon Stroke="Blue" StrokeThickness="1" Fill="Yellow"
Points="15,200 68,70 110,200 0,125 135,125" >
</Polygon>
每個Polygon和Polyline形狀都有FillRule屬性,該屬性用于從兩種填充方法中選擇一種來填充區域。預設情況下,FillRule屬性被設定為EventOdd。為了确定是否填充區域,WPF計算為了到達形狀的外部必須穿過的直線的數量。如果是奇數,就填充區域;如果是偶數,就不填充區域。對于上圖顯示的中央區域,為了到達形狀外部就必須經過兩條直線,是以不會填充該區域。
WPF還遵循NonZero填充規則,該規則更加複雜。本質上,當使用NonZero填充規則時,WPF使用和EventOdd填充規則相同的方法計算穿過的直線的數量,但是會考慮經過的每條直線的防線。如果在經過的直線中,在某個方向上(比如從左項右)直線的數量等于相反方向(從右向左)上直線的數量,就不會填充區域。如果這兩個直線數量的差不為0,就填充區域。對于上個示例,如果将FillRule屬性設定為NonZero,J就會填充内部區域。
<Polygon Stroke="Blue" StrokeThickness="1" Fill="Yellow"
FillRule="Nonzero"
Points="15,200 68,70 110,200 0,125 135,125" >
</Polygon>
有關NonZero規則的複雜問題在于填充設定依賴于形狀的繪制,而不是形狀自身的外觀。
八、直線線帽和直線交點
當繪制Line和Polyline形狀時,可使用StartLineCap和EndLineCap屬性選擇如何繪制直線的開始端和結束端(這些屬性不影響其他形狀,因為其他形狀都是閉合的)。
StartLineCap和EndLineCap屬性通常都設為Flat,這意味着直線在它的最後坐标處立即終止。其他選擇包括Round(該設定會平滑地繪制拐角),Triangle(繪制直線的兩條側邊最後交于一點)以及Square(該設定使直線端口具有尖銳邊緣)。這兩個設定都會增加直線的長度——換句話說,它們使直線超出了其他情況下的結束位置。額外的距離是直線寬度的一半。下圖顯示了直線端口處不同線帽之間的差別。
<Window x:Class="Drawing.LineCaps"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LineCaps" Height="333" Width="376">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Polyline Stroke="Blue" StrokeThickness="15" StrokeEndLineCap="Flat" SnapsToDevicePixels="True"
Points="10,10 30,0 50,20 90,10 200,10" >
</Polyline>
<TextBlock Grid.Column="1">Flat Line Cap</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="1" StrokeEndLineCap="Square" SnapsToDevicePixels="True"
Points="10,10 30,0 50,20 90,10 200,10" >
</Polyline>
<TextBlock Grid.Row="1" Grid.Column="1">Square Line Cap</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="2" StrokeEndLineCap="Round" SnapsToDevicePixels="True"
Points="10,10 30,0 50,20 90,10 200,10" >
</Polyline>
<TextBlock Grid.Row="2" Grid.Column="1">Round Line Cap</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="15" Grid.Row="3" StrokeEndLineCap="Triangle" SnapsToDevicePixels="True"
Points="10,10 30,0 50,20 90,10 200,10" >
</Polyline>
<TextBlock Grid.Row="3" Grid.Column="1">Triangle Line Cap</TextBlock>
</Grid>
</Window>
LineCaps
除Line形狀外,所有形狀都運作使用StrokeLineJoin屬性扭曲它們的拐角,有4中選擇。Miter值(預設值)使用尖銳的邊緣,Bevel值切掉點邊緣,Round值平滑地過渡邊緣,Triangle值顯示尖點。下圖顯示StrokeLineJoin效果圖。
<Window x:Class="Drawing.LineJoins"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LineJoins" Height="431" Width="303"
>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Polyline Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Bevel" SnapsToDevicePixels="True"
Points="10,60 30,10 50,70 90,40" >
</Polyline>
<TextBlock Grid.Column="1" VerticalAlignment="Center">Bevel Line Join</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="14" Grid.Row="1" StrokeLineJoin="Round" SnapsToDevicePixels="True"
Points="10,60 30,10 50,70 90,40" >
</Polyline>
<TextBlock Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">Round Line Join</TextBlock>
<Polyline Grid.Row="2" Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Miter" StrokeMiterLimit="1"
SnapsToDevicePixels="True"
Points="10,60 30,10 50,70 90,40" >
</Polyline>
<TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Miter Line Join</TextBlock>
<Polyline Grid.Row="3" Stroke="Blue" StrokeThickness="14" StrokeLineJoin="Miter" StrokeMiterLimit="3"
SnapsToDevicePixels="True"
Points="10,60 30,10 50,70 90,40" >
</Polyline>
<TextBlock Grid.Row="3" Grid.Column="1" VerticalAlignment="Center">Miter Line Join With Limit of 3</TextBlock>
</Grid>
</Window>
LineJoins
當為較寬并且角度非常小的直線拐角使用尖銳的邊緣時,尖銳的拐角會不切實際地延伸很長一段距離。對于這種情況,可使用Bevel或Round設定修剪拐角。也可使用StrokeMiterLimit屬性,當達到特定的最大長度時,該屬性自動地剪切邊緣。StrokeMiterLimit屬性是一個系數,該系數是用于銳化拐角的長度和直線寬度的一半的比值。如果将該屬性設定為1(這是預設值),就允許拐角延長直線寬度的一半距離。如果設定為3,就允許拐角延長直線寬度的1.5倍距離。如上圖的最後一條直線使用了更高的銳化範圍,進而具有更狹長的拐角。
九、點劃線
除了為形狀的邊框繪制乏味的實線外,還可繪制點劃線(dashed line)——根據指定的模式使用空白斷開的直線。當在WPF中建立一條點劃線時,不限制進行特定的預先設定。相反,可通過設定StrokeDashArray屬性來選擇實線段的長度和斷開空白(空白)的長度。例如,分析下面的這條直線:
<Polyline Stroke="Blue" StrokeThickness="10"
StrokeDashArray="1 2"
Points="10,30 60,0 90,40 120,10 350,10" SnapsToDevicePixels="True">
</Polyline>
這條點劃線的實線段長度值為1,空白長度值為2.這些值都是相對于直線寬度的。是以,如果直線寬度是10個機關(本例中設定的寬度),實線部分的長度就為10個機關,後面跟着20個機關的空白部分。直線在整個長度中重複該模式。
另一方面,如果像下面這種交換這兩個值:
StrokeDashArray="2 1"
直線的實作部分就是20個機關長,空白部分為10個機關長。下圖顯示了這着兩條直線。正如将會注意到得,當一條非常粗的線段位于拐角處時,它會被不均勻地割斷。
<Window x:Class="Drawing.DashedLines"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DashedLines" Height="401" Width="589"
>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Polyline Stroke="Blue" StrokeThickness="10"
StrokeDashArray="1 2"
Points="10,30 60,0 90,40 120,10 350,10" SnapsToDevicePixels="True">
</Polyline>
<TextBlock Grid.Column="1" VerticalAlignment="Center">Dash Pattern "1 2"</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="1"
StrokeDashArray="2 1" SnapsToDevicePixels="True"
Points="10,30 60,0 90,40 120,10 350,10" >
</Polyline>
<TextBlock Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">Dash Pattern "2 1"</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="2"
StrokeDashArray="5 0.2 3 0.2" SnapsToDevicePixels="True"
Points="10,30 60,0 90,40 120,10 350,10" >
</Polyline>
<TextBlock Grid.Row="2" Grid.Column="1" VerticalAlignment="Center">Dash Pattern "5 0.2 3 0.2"</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="3" SnapsToDevicePixels="True"
StrokeDashArray="3 0.5 2"
Points="10,30 60,0 90,40 120,10 350,10" >
</Polyline>
<TextBlock Grid.Row="3" Grid.Column="1" VerticalAlignment="Center">Uneven Dash Pattern "2 0.5 2"</TextBlock>
<Polyline Stroke="Blue" StrokeThickness="10" Grid.Row="4" SnapsToDevicePixels="True"
StrokeDashArray="1 2" StrokeDashCap="Round"
Points="10,30 60,0 90,40 120,10 350,10" >
</Polyline>
<TextBlock Grid.Row="4" Grid.Column="1" VerticalAlignment="Center">Dash Pattern with Rounded Caps</TextBlock>
</Grid>
</Window>
DashedLines
不見得非要使用整數值。例如,下面的StrokeDashArray屬性設定完全合理:
StrokeDashArray="5 0.2 3 0.2"
這樣的設定提供了更複雜序列——5X10機關長的點劃線,然後是0.2X15機關長的空白,接下來是3X10機關長的實線和0.2X10機關長的空白。在該序列的尾部,直線從頭開始重複該模式。
如果為StrokeDashArray屬性提供的數值的個數是奇數,将發生一個有趣的現象。分析下面的示例:
StrokeDashArray="3 0.5 2"
當繪制該直線時,WPF首先繪制3倍直線寬度長的實線,然後是0.5倍直線寬度長的空白,在接下來時2呗直線寬度長的實線。但當在從頭開始重複該模式時,首先是3倍直線寬度長的空白,接着是0.5被直線寬度長的實線,依次類推。本質上,點劃線線上段和空白之間交替其模式。
如果希望從中間開始繪制模式,可使用StrokeDashOffset屬性,該屬性是一個從0開始的索引,該索引指向StrokeDashArray中的某個值。例如,在上一個示例中,如果将StrokeDashOffset屬性設定為1,直線将從0.5倍直線寬度長的空白開始。如果設定為2,直線将會從2倍直線寬度長的線段開始。
最後,可控制如何為直線的斷開邊緣添加線帽。通常是一條平直的邊緣,但可将StrokeDashCap屬性設定為Bevel、Square以及Triangle等值。請記住,所有這些設定都會在點劃線的端點增加直線寬度的一半長距離。如果沒有考慮這一額外的距離,最終可能會使點劃線互相重疊。解決方法是增加額外的空白以進行補償。
十、像素對齊
WPF使用與裝置無關的繪圖系統。為字型和形狀等内容指定的數值使用“虛拟”像素,在通常的96dpi顯示器上,“虛拟”像素和正常像素的大小相同,但是在更高dpi的顯示器上其尺寸會被縮放。換句話說,繪制50像素寬的矩形,根據裝置的不同,實際上可能使用更多或更少的像素進行渲染。裝置無關機關和實體像素之間的轉換會自動進行,并且通常根本不需要考慮這個問題。
不同dpi設定之間的像素比很少是整數。例如, 在96dpi顯示器上的50個像素,在120dpi顯示器上會變為62.4996個像素(這不是一種錯誤的情況——實際上,當以裝置無關機關提供數值時,WPF始終運作使用非整數的雙精度值)。顯然,無法在像素之間的點上放置一條邊緣。WPF使用反鋸齒特性進行補償。例如,當繪制一條62.4992個像素長的紅線,WPF可正常填充前62個像素,然後使用直線顔色(紅色)和背景色之間的顔色為第63個像素着色。但在此存在一個問題。如果正在繪制直線、矩形或具有直角的多邊形,這種自動反鋸齒特性會在形狀邊緣導緻一片模糊區域。
可能會認為,僅在顯示分辨率不是96dpi的顯示器上運作應用程式時,才會出現這個問題。然而,情況未必如此,因為所有形狀都可以都可以使用小數值得長度和坐标設定尺寸,這會引起相同的問題。在繪制形狀時,盡管可能沒有使用小數值,但可以改變尺寸的形狀——那些因為尺寸依賴于容器或被放在Viewbox元素中而被拉伸的形狀——尺寸通常幾乎總是小數。類似地,奇數機關寬的直線在兩側的像素數也是小數值。
模糊邊緣問題未必是問題。實際上,根據正在繪制的圖像類型,它可能看起來很正常。然而,如果不希望這種行為,可告訴WPF不要在特定形狀使用反鋸齒特性進行處理,反而WPF會将尺寸舍入到最近的裝置像素。可通過将UIElement類的SnapsToDevicePixels屬性設定為true來啟動這個稱為像素對齊(pixel Snapping)的特性。
為檢視兩者之間的差別,可觀察下圖中被放大的視窗,該視窗比較兩個矩形。底部的矩形使用了像素對齊特性,而頂部的矩形沒有使用。如果仔細觀察,就會發現在未使用像素對齊特性的矩形的頂部和左部有一條很細的淡色邊緣。
<Window x:Class="Drawing.PixelSnapping"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PixelSnapping" Height="300" Width="300">
<Grid Margin="7">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center">Not Snapped:</TextBlock>
<Rectangle SnapsToDevicePixels="False" Grid.Column="1"
Margin="10" Height="10" Fill="Red"></Rectangle>
<TextBlock VerticalAlignment="Center" Grid.Row="1">Snapped:</TextBlock>
<Rectangle SnapsToDevicePixels="True" Grid.Column="1" Grid.Row="1"
Margin="10" Height="10" Fill="Red"></Rectangle>
</Grid>
</Window>
PixelSnapping
作者:Peter Luo
出處:https://www.cnblogs.com/Peter-Luo/
本文版權歸作者和部落格園共有,歡迎轉載,但必須給出原文連結,并保留此段聲明,否則保留追究法律責任的權利。