為了在屏幕上繪制一個圖形,WPF需要知道你想要為圖形填充什麼顏色以及如何繪制它的邊框。WPF提 供了一些Brush類型支持各種繪圖樣式。Pen類增加這些筆刷以提供邊框的厚度和樣子。
在這一章,我們將要看一下各種類型的筆刷和鋼筆類。可是,由於所有的筆刷和鋼筆類最終是關於指 出在哪裡使用哪一種顏色,以及如何將它們聯合在一起,我們必須首先看一下眼色是如何被表示的。
7.3.1 顏色
WPF在System.Windows.Media命名空間中使用了Color結構來表示一種顏色。注意到如果你以往工作於 Windows Forms、ASP.NET或GDI+,Color機構是不同於那些技術使用的機構的——它們使用了 System.Drawing命名空間的Color結構。WPF引進了這種新的Color結構,是因為它可以工作於浮點形式的 顏色值——支持更高的顏色精度,以及更好的彈性。
Color顏色結構使用了四個數字,或者說是通道,來表示一種顏色。這些通道是紅、綠、藍以及Alpha 。紅、綠、藍通道是計算機圖形學中傳統的表示顏色的方式。(這是因為顏色屏幕通過將這三種基本顏色 混合在一起來工作。)一個0值表示顏色部分完全不存在;三個通道都是0對應黑色。Alpha通道表示顏色 不透明度的等級。Color可以是不透明的、完全不透明的、以及在這兩種極限值之間的任意值。WPF的合成 引擎充分支持透明度,因此任何圖形都可以被繪制為帶有透明等級。0值用來表示完全透明。
Windows是傳統的使用24位顏色信息,每個通道8位顏色,以表示“真彩色”;同時還有32位帶有透明 度的真彩色。這僅僅是充分的關於計算機屏幕的平均水平。常規計算機表現的顏色和亮度范圍是,24位顏 色對於大多數用途總是足夠的。然而,對於很多圖形化應用程序,這是不夠的。例如,電影,相比於計算 機屏幕,可以提供一個更寬范圍的亮度,還有24位顏色對於將電影作為輸出媒介的圖形化工作,是完全不 足夠的,同樣也不適用於很多醫療影像應用程序。即使對於計算機和視頻影像,24位顏色也會引起一些問 題。如果圖像需要經過很多階段的處理,這些增強24位原材料的限制。
WPF因此在它的顏色表示中支持一個相當高等級的細節。每個顏色通道使用16位代替8位。這種Color結 構仍然在需要的地方支持8位通道的使用,因為大多數圖像軟件依賴於這樣的表示。Color通過A、R、G、B 屬性暴露了這些8位通道,這些屬性接受0到255間的值。這種更高級的定義表示法——通過ScA、ScR、ScG 、ScB屬性——也是有效的,這些屬性代表了0到1范圍內的單精度的浮點值。
ScA、ScR、ScG和ScB屬性中的”Sc”涉及了這樣的事實,它們支持標准的“Extended RGB colour spacescRGB”,顏色空間定義在IEC61966-2-2規范中。”sc”是“scene”的縮寫,因為這通常是一個 scenereferred的顏色空間。這意味著scRGB空間的顏色值代表了原始圖像的顏色。這是不同於計算機圖像 通常是如何存儲的。傳統上說,我們已經使用了outputreferred顏色空間,顏色值不需要在顯示於目標設 備之前映射到那裡。
使用Outputreferred顏色空間可以有效地工作,只要它們恰好設定了輸入設備的目標。然而, scenereferred顏色空間保護了所有的可利用信息在捕獲或生成圖像時。為了更高精度的顏色表示, scenereferred模型因此是更清晰的。即使它們工作起來有點低效率。
這裡還有一個Color類,它提供了一組標准的命名顏色,包括所有舊有的喜好,如PapayWhip、 BurlyWood、LightGoldenrodYellow和Brown。
7.3.2 SolidColorBrush
SolidColorBrush是最簡單的筆刷。它使用一種顏色給整個區域上色。它只有一個屬性,Color。注意 到這個顏色允許使用透明度,盡管在單詞中使用了Solid。
我們已經看到廣泛使用SolidColorBrush,即使我們並未提供名稱涉及它。這是因為WPF創建這種類型 的筆刷,一旦你詳細指出了標記中顏色的名稱。如果你
大都在標記中工作,你會很少需要指出你需要一個SolidColorBrush,因為你會得到一個默認值。(通 常你會詳細指明它以完整的詞,唯一的原因是你想使用筆刷屬性的數據綁定)。考慮下面這個示例:
<Rectangle Fill=”Yellow” Width=”100” Height=”20” />
xaml編譯器會認出Yellow為Color類中一個標准命名的顏色,它會提供一個合適的SolidColorBrush。 (參見附錄A獲取更多xaml映射字符串到屬性值的信息)。這就不需要創建一個筆刷,因為存在一個 Brushes類,在Colors中為每一個命名顏色提供了一組筆刷。
如果你的標記使用了數字顏色值,你還可以被提供一個SolidColorBush。示例7-26顯示了各種各樣帶 有數字顏色值的示例。它們都開始於一個#記號,並包含16進制的數字。一個由3個數字組成的號碼,每一 個數字分別表示紅、黃、藍。四個數字的號碼解釋為alpaa、紅、黃、藍。這些是緊湊的格式,但是僅為 每個通道提供了4位。6到8個數字的號碼允許RGB或ARGB各自為每個通道8位。(為了開發完全的16位精確 度的scRGB,你需要使用屬性-元素語法來設置屬性。這裡沒有簡化的文本。參見附錄A獲取更多關於xaml 屬性-元素的語法信息)
示例7-26
<Rectangle Fill="#8f8" Width="100" Height="20" />
<Rectangle Fill="#1168ff" Width="50" Height="40" />
<Rectangle Fill="#8ff0" Width="130" Height="10" />
<Rectangle Fill="#72ff8890" Width="70" Height="30" />
SolidColorBrush是輕量的和直接的。然而,它導致了相當flat-looking的可視化。WPF提供更多有趣 的筆刷,如果你想制作自己的用戶界面——看起來更吸引人的。
7.3.3 LinearGradientBrush
使用LinearGradientBrush,被繪制的區域從一種顏色過渡為另一種顏色,或者即使一個顏色的序列。 圖7-32顯示了一個簡單的示例。
圖7-32
這個筆刷從黑色淡出為白色,開始於左上角,結束於右下角。這種淡出總是以直線進行,你不能進行 曲線的過渡,因此命名為“Linear”。示例7-27顯示了圖7-32的標記。
示例7-27
<Rectangle Width="80" Height="60">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
StartPoint和EndPoint屬性指明了顏色過渡的開始和終結位置。這些坐標是相對於被填充區域的,因 此(0,0)是左上角,(1,1)是右下角,如圖7-33所示。(注意到如果筆刷繪制或寬或窄的區域,坐標 系統從而是積壓的)
圖7-33
示例7-27使用了屬性-元素語法來初始化筆刷。在這個特定的示例中,這不是嚴格必要的,因為xaml支 持更復雜的語法。這嚴密地等價於示例7-27:
<Rectangle Width=”80” Height=”60” Fill=”LinearGradient 0, 0 1,1 Black White ” >
這種緊湊地語法允許設置最重要的方面。兩對數字對應到StartPoint和EndPoint屬性,以及剩余地兩 個條目是顏色名稱。如果你想這種漸變是垂直的或水平的,你可以使用更簡單的語法。
<Rectangle Width=”80” Height=”60” Fill=”VerticalGradient Black White” />
<Rectangle Width=”80” Height=”60” Fill=”HorizontalGradient Black White ” />
在所有這些字符串表示中,開始和結束顏色都是詳細指定的。這些符合示例7-27中的GradientStop元 素。注意到在充分延伸的復合屬性的版本中,每個GradientStop都有像Color的一個Offset屬性。這支持 了更詳細的樣式來完成字符表達式不能做的事情。它允許填充來傳遞多個顏色。示例7-28顯示了帶有多個 顏色的LinearGradientBrush。
示例7-28
<Rectangle Width="80" Height="60">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Orange" Offset="0.2" />
<GradientStop Color="Red" Offset="0.4" />
<GradientStop Color="Black" Offset="0.6" />
<GradientStop Color="Blue" Offset="0.8" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
結果顯示在圖7-34中。注意到早期顯示的速記字符串的語法,並不支持多個顏色值。如果你想要這個 效果,你不得不充分地使用屬性-元素語法。
圖7-34
LinearGradientBrush經常用於添加深度感到用戶界面。示例7-29顯示了一個典型的例子。它僅使用了 兩個形狀——一對圓角矩形元素。(Grid不直接對外觀產生影響。這使得調整圖形的大小更容易——改變 grid的Width和Height將會引起所有矩形的適當地調整大小。)第二個矩形是漸變填充的,從部分透明的 白色漸變到完全透明的顏色,這將提供一個有趣的可視化效果。
示例7-29
<Grid Width="80" Height="26">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"
Fill="VerticalGradient Green DarkGreen"
Stroke="VerticalGradient Black LightGray" />
<Rectangle Margin="3,2" RadiusX="8" RadiusY="12"
Fill="VerticalGradient #dfff #0fff" />
</Grid>
圖7-35顯示了結果。這是極其簡單的一個圖形,只包括兩個形狀。漸變填充的使用增加了深度,否則 這些形狀是不會傳輸的。
圖7-35
7.3.4 RadialGradientBrush
RadialGradientBrush非常類似於LinearGradientBrush。它們都允許轉變經歷一系列的顏色。但是當 LinearGradientBrush用直線繪制這些轉變時,RadialGradientBrush漸變從一個外部的開始點,到一個橢 圓的邊界。這就展開了更多的機會使你的用戶界面看起來不是很單調,如實例7-30所示。
示例7-30
<Rectangle Width="200" Height="150">
<Rectangle.Fill>
<RadialGradientBrush Center="0.5,0.4" RadiusX="0.3" RadiusY="0.5"
GradientOrigin="0.25,0.4">
<RadialGradientBrush.GradientStops>
<GradientStop Color="White" Offset="0" />
<GradientStop Color="DarkBlue" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
RadialGradientBrush使用一個GradientStop列表對象來決定填充進行的顏色,正如 LinearGradientBrush。這個示例使用了RadiusX和RadiusY屬性來決定橢圓邊界的大小,以及Center屬性 來設置橢圓的位置。這裡選擇的值使得填充邊界完全的適合這個形狀,正如示例7-36所示。這個形狀落在 了邊界的外面,它的區域由最後的GradientStop的顏色填充。注意到填充的焦點在左邊。這是因為 GradientOrigin已經被設置了。(默認的焦點在橢圓中間。)
圖7-36
示例7-30使得看到RadialGradientBrush屬性的效果更容易,但這不是一個激動人心的例子。示例7-31 顯示了有點挑戰的東西。這類似於示例7-29,它使用了少量的帶有漸變填充的形狀,來傳輸深度感和反射 感,但是這次使用的是放射性填充。
示例7-31
<Grid Width="16" Height="16" Margin="0,0,5,0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="20*" />
<RowDefinition Height="6*" />
</Grid.RowDefinitions>
<Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" Margin="0.5">
<Ellipse.Fill>
<RadialGradientBrush Center="0.5,0.9" RadiusX="0.7" RadiusY="0.5">
<RadialGradientBrush.GradientStops>
<GradientStop Color="PaleGreen" Offset="0" />
<GradientStop Color="Green" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Grid.Row="1" Grid.Column="1">
<Ellipse.Fill>
<RadialGradientBrush Center="0.5,0.1" RadiusX="0.7" RadiusY="0.5">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#efff" Offset="0" />
<GradientStop Color="Transparent" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3"
Stroke="VerticalGradient Gray LightGray" />
</Grid>
這次,使用了三種橢圓,其中兩種使用了RadialGradientBrush填充,另一種使用了 LinearGradientBrush繪制輪廓。第一個橢圓的填充,創建了發光體在圖中的下方。第二個橢圓的填充添 加了一個反光在圖形的上方。第三個橢圓繪制了一條貝賽爾曲線在橢圓的外圍。結果如圖7-37所示。放射 性填充建議使用一個曲面以及給這個圖形一個些微透明的外觀。
圖7-37
7.3.5 ImageBrush和VisualBrush、DrawingBrush
使用某種類型的樣式或者圖片填充形狀,這經常是有用的。WPF提供了三種筆刷,從而允許我們使用無 論什麼圖形作為筆刷。ImageBrush讓我們使用一張圖片進行繪制。而DrawingBrush允許我們使用可伸縮的 圖形。VisualBrush允許我們使用任意的標記作為筆刷的圖像。我們可以,有效地,使用我們的用戶界面 的一部分,來繪制另一部分。
7.3.5.1 TiteBrush
ImageBrush和VisualBrush、DrawingBrush都使用源圖片的某種形式來進行繪制。它們的基類, TiteBrush,決定了如何延展源圖片來填充有效的空間,是否要重復圖像,以及如何定位形狀中的一個圖 像。(TiteBrush是一個抽象的基類,因此你可以直接使用它。它存在用來為ImageBrush和VisualBrush、 DrawingBrush定義公共的API)
圖7-38顯示了默認的TiteBrush行為。這個圖像顯示了3個矩形,為了你可以看到當筆刷變寬或變窄時 發生了什麼,同樣的效果當筆刷形狀匹配目標區域的形狀。所有這三個矩形都使用ImageBrush繪制的,明 確指出了所需的圖像。
圖7-38
當圖7-38用一個ImageBrush繪制的時候,這個行為將會是和任何TiteBrush一樣。不久我們將要看到 ImageBrush更多的細節,但是目前示例7-32顯示了基本的用法。
示例7-32
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Images\Moggie.jpg" />
</Rectangle.Fill>
</Rectangle>
筆刷伸展了源圖像來填充有效的區域。我們可以通過修改筆刷的Stretch屬性來改變這個行為。默認值 為Fill,但是我們可以通過指定為None以原始大小顯示圖像,正如示例7-33所示。
示例7-33
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Images\Moggie.jpg" Stretch="None" />
</Rectangle.Fill>
</Rectangle>
這就保持了屏幕的寬高比,但是如果圖像很大,它就會簡單的裁剪以適應有效的空間,正如示例7-39 。
圖7-39
為了顯示這些圖片,你可能更想伸展圖像以匹配有效的空間,而不用扭曲屏幕的寬高比。TiteBrush支 持Uniform伸展模式,顯示如圖7-40。縮減源圖像從而使它完全在有效的空間裡。
圖7-40
Uniform伸展模式典型地導致了圖像小於被填充的區域。默認的行為是繪制圖像,無論對齊屬性指明在 哪裡,以及使余下的區域為透明的。然而,你可以為多余的空間選擇其他的行為——通過TiteMode屬性。 默認為None,但是如果你指定了Tite,如示例7-34,這個圖像就會被重復填充多余的空間。
示例7-34
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Images\Moggie.jpg"
Stretch="Uniform" TileMode="Tile" />
</Rectangle.Fill>
</Rectangle>
圖7-41顯示了Uniform伸展模式和Tite圖塊模式的效果。這裡有一個潛在的關於圖塊的問題。這經常使 非常明顯的在每一個圖塊開始重復的地方。如果你的目標使簡單地用一種紋理填充一個區域,這種不連續 可能有點刺眼。為了減輕這個問題,TiteBrush提供了3種其它圖塊模式:FlipX、FlipY和FlipXY。這些模 式交替鏡像圖形,如圖7-42所示。雖然鏡像可以減少圖塊間地不連續,對於一些源文件而言,它可以非常 充分地改變筆刷的外觀。
圖7-41
圖7-42
一個折中的使用圖塊的方法是,伸縮圖片從而它完全填充有效的空間,同時保持了屏幕的寬高比,以 及如果需要就在一個方向上裁減。UniformToFill伸展模式實現了這個方法,顯示如圖7-43。
如果你要使用某種不重復樣式的模式來填充一個區域,UniformToFill是最恰當的,因為它保證了可以 繪制整個區域。這可能是不太恰當的——如果你的目標是簡單的顯示一個圖像,如圖7-43所示,這種伸縮 模式會在必要的時候裁剪圖像。如果你想要顯示整張圖片,Uniform是最好的選擇。
圖7-43
除Fill之外,所有的伸展模式存在一個額外的問題:如何定位這個圖像。使用None和UniformToFill, 裁剪動作發生了,因此WPF需要決定要顯示哪一部分。使用Uniform,圖片可能比需要填充的空間小,因此 WPF需要決定把它放在哪裡。
圖片默認是居中的。在示例中(圖7-39和圖7-43),最中間的部分顯示在圖片被裁剪的位置。在 Uniform的情形中,在圖像比被繪制區域小的地方,圖像被放置在區域中間位置(圖7-40)。你可以使用 AlignmentX和AlignmentY屬性來改變這一點。這兩個屬性可以分別設置為Left、Middle或Right和Top、 Middle或Bottom。示例7-35再次顯示了UniformToFill模式,但是折椅此使用了Left和Bottom的對齊方式 。圖7-44顯示了結果。
示例7-35
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Images\Moggie.jpg" Stretch="UniformToFill"
AlignmentX="Left" AlignmentY="Bottom" />
</Rectangle.Fill>
</Rectangle>
圖7-44
伸展和對齊屬性是方便於使用的,但是它們不允許你選擇在圖形的任意部分設置焦點或詳細指出縮放 因素。TileBrush通過ViewBox、Viewport和ViewportUnits來支持這些樣式。
ViewBox屬性選擇顯示圖像的一部分。默認的,這個屬性設置為環繞整張圖片,但是你可以改變它的焦 點在一個特定的部分。圖7-45顯示了UniformToFill模式,但是使用一個ViewBox設置放大車子的前面部分 。
圖7-45
如示例7-36所示,ViewBox被詳細指定為4個數字。前面兩個是ViewBox左上角的坐標,後兩個是 ViewBox的寬度和高度。這些坐標是相對於圓圖像的。在這種情形下,因為使用了一個ImageBrush,這些 就都是源圖像中的坐標。在DrawingBrush和VisualBrush的情形下,ViewBox會使用繪制源的坐標系統。
示例7-36
<ImageBrush Stretch="UniformToFill" Viewbox="593,250,200,200"
ImageSource="Images\Moggie.jpg" />
Viewbox使你可以選擇定焦到源圖像的哪個部分,而Viewport使你選擇使用筆刷的圖像結束在哪個位置 。它的功能與對齊屬性交疊,但是Viewport允許更多的控制。
圖7-46說明了ViewBox和Viewport的聯系。在左邊,我們看到這種情形的源圖像,但是它可能還是一個 繪圖或者可視化樹。Viewbox詳細指出了源圖像的區域。在右邊,我們看到了筆刷。Viewport詳細指出了 筆刷所在的區域。WPF將會伸縮和定位源圖像,從而在Viewbox中詳細指出的區域終結於被Viewport在被繪 制區域詳細指出區域。
Viewport並沒有詳細指出筆刷的范圍。正如示例7-46顯示,筆刷並不需要和Viewport的大小一樣。筆 刷不可以被裁剪到Viewport的大小。所有Viewport和Viewbox做的是確定源圖像用目標筆刷繪制的比例和 位置。示例7-37顯示了Viewport和Viewbox的設置,對應到圖7-46中的高亮區域。
圖7-46
示例7-37
<ImageBrush
Viewbox="380,285,308,243"
Viewport="0.1,0.321,0.7, 0.557"
ImageSource="Images\Moggie.jpg" />
注意到示例7-37中,當Viewbox的坐標是相對於源圖像的,Viewport適用0到1間的數值。默認的, Viewport坐標系統是基於筆刷的完全大小,(0,0)是在左上角,而(1,1)是在右下角。這意味著由 Brush顯示的圖像區域總是相同的,不管筆刷的大小。這就導致了一個扭曲的行為——相似於默認的 StretchMode的Fill,如圖7-47所示。(事實上,Fill伸展模式等價於Viewbox設置為源圖像的大小,以及 Viewpoint為“0,0,1,1”。)
你可以使用ViewportUnit屬性為Viewport詳細指定不同的單位。默認為RelativeToBoundingBox,但是 一旦你將它改為Absolute,這個Viewport使用用戶界面的坐標系統來測量。
記住所有的伸縮和定位功能,對於所有派生於TileBrush的筆刷都是公共的。我們現在將來看一下詳細 指出獨立的筆刷的樣式。
7.3.5.2 ImageBrush
ImageBrush使用一個圖片來繪制屏幕上的區域。ImageBrush用於在前面的章節創建所有圖片。這個筆 刷是直接了當的,你可以簡單地告訴它使用哪一個圖片在ImageSource屬性上,正如示例7-38所示。
圖7-47
示例7-38
<Rectangle>
<Rectangle.Fill>
<ImageBrush ImageSource="Images\Moggie.jpg" />
</Rectangle.Fill>
</Rectangle>
為了使圖片文件對ImageBrush是有效的,簡單地在VS2008中把它添加到你的工程中。示例7-38中這個 文件在這個工程的名為Images的子目錄中。這個圖片必須作為一個資源嵌入到工程中。為了做到這一點, 在VS2008中的“解決方案”面板中選擇一個圖片,接著在“屬性”面板中,確保它的BuildAction屬性被 設置為Resource。這就嵌入這個圖片到可執行程序中,支持ImageBrush在運行時找到它。(參見第6章獲 取更多關於管理二進制資源的信息。)可選擇性的,你可以為這個屬性詳細指明一個絕對路徑,例如,你 可以顯示一個來自網站的圖像。
ImageBrush非常樂於處理帶有透明通道的圖像(又被稱為“alpha”通道)。並不是所有的圖像格式支 持局部透明,但是,一些是可以的,如PNG和BMP格式。(其次,GIF也是可以的。它們也支持完全透明和 完全不透明的像素。這時有效的1位alpha通道。)ImageBrush將給它特殊的權利,在alpha通道出現的地 方。
7.3.5.3 DrawingBrush
ImageBrush是便利的,如果你有一個繪制時需要的圖片。然而,圖像並不非常適合於獨立的分辨率。 ImageBrush將為你的屏幕分辨率正確地縮放圖像,但是在縮放時,圖片易於變得模糊。DrawingBrush不會 為這個問題所累,因為你通常提供一個可縮放的向量圖作為它的資源。這就支持一個DrawingBrush在任意 大小和分辨率保持清晰的和銳利的。
這個向量圖由一個Drawing對象表示。這時一個抽象的基類。形狀可以用GeometryDrawing繪制,這就 允許你使用所有相同的由Path支持的幾何體元素來構造繪圖。你還可以通過ImageDrawing和VideoDrawing 來使用圖片和視頻。文本由GlyphRunDrawing支持。最後,你可以使用DrawingGroup將這些聯合在一起。
即使你只使用形狀,你仍然可能想要通過DrawingGroup來分組這些形狀。每個GeometryDrawing都有效 的等同於一個單獨的Path,因此如果你想使用不同的鋼筆和筆刷來繪制,或者如果你想你的形狀是交疊的 而不是聯合的,你將需要使用多個GeometryDrawing元素。示例7-39顯示了使用一個Fill為DrawingBrush 的Rectangle。
示例7-39
<Rectangle Width="80" Height="30">
<Rectangle.Fill>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="VerticalGradient Green DarkGreen">
<GeometryDrawing.Pen>
<Pen Thickness="0.02"
Brush="VerticalGradient Black LightGray" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<RectangleGeometry RadiusX="0.2" RadiusY="0.5"
Rect="0.02,0.02,0.96,0.96" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="VerticalGradient #dfff #0fff">
<GeometryDrawing.Geometry>
<RectangleGeometry RadiusX="0.1" RadiusY="0.5"
Rect="0.1,0.07,0.8,0.5" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
示例7-39繪制了與早期圖7-35同樣的可視化。因為組成繪圖的每一個矩形元素,使用了不同的線性漸 變填充,它們都有自己的GeometryDrawing,嵌入到DrawingGroup中。
使用DrawingBrush,Viewbox默認為(0,0,1,1)。示例7-39中所有的坐標和大小都相對於坐標系統。如 果你想要在一個更廣范圍的坐標上工作,你可以簡單地設置Viewbox為你需要的范圍。我們已經看到在示 例7-36中如何使用Viewbox。使用DrawingBrush的唯一區別是,你使用它表示一個繪圖區域,而不是一個 圖片。
注意到,我們可以使用Viewbox在圖片的一小部分上設定焦點,正如我們早期使用ImageBrush一樣。在 示例7-39中,我們可以修改DrawingBrush以使用一個更小的Viewbox,正如圖7-40所示。
示例7-40
<DrawingBrush Viewbox="0.5,0,0.5,0.25">
結果是大多數的繪圖現在都在Viewbox的外面,因此這個筆刷只顯示了全部繪圖的一部分,如圖7-48顯 示的那樣。
圖7-48
DrawingBrush是極其強大的,正如它讓你或多或少使用任意你喜歡的圖形,如筆 刷,以及因為它是基於向量的,結果在任意比例都保持為易碎的。它由一個缺點,如果你從標記中使用它 ,盡管:它有點笨重的——從xaml中使用。考慮一下示例7-39,生成了和示例7-29同樣的外觀,但是這些 示例分別是28條線段長和10條線段長。
Drawing是非常麻煩的,因為它需要我們與幾何體對象一起工作,而不是高級別結構如在示例7-29中使 用的Grid或Rectangle。(注意到這麼一個不是很敏感的問題,當在代碼中使用這個筆刷時,這裡高級別 對象不再比幾何體使用便利)。因此這確實是一個xaml的問題。幸運的是,VisualBrush運行我們使用這 些高級別對象來繪制。
7.3.5.4 VisualBrush
VisualBrush可以使用任意派生自Visual的元素的內容來繪制。由於Visual是所有WPF用戶界面元素的 基類,這就意味著實際上你可以插入任何你想要的標記在一個VisualBrush中。示例7-41顯示了使用 VisualBrush填充的一個Rectangle。
示例7-41
<Rectangle Width="80" Height="30">
<Rectangle.Fill>
<VisualBrush Viewbox="0,0,80,26">
<VisualBrush.Visual>
<Grid Width="80" Height="26">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.RowSpan="2" RadiusX="13" RadiusY="13"
Fill="VerticalGradient Green DarkGreen"
Stroke="VerticalGradient Black LightGray" />
<Rectangle Margin="3,2" RadiusX="8" RadiusY="12"
Fill="VerticalGradient #dfff #0fff" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
在示例7-41中,筆刷的可視化是直接從示例7-29復制過來的,導致了一個更簡單的等同於 DrawingBrush的筆刷。(這個結果看起來非常像圖7-35。VisualBrush的要點在於,它繪制區域就像它包 裝的可視化。)
你可能想知道究竟為什麼要使用DrawingBrush,既然VisualBrush是非常簡單的?一個原因是 DrawingBrush更加有效。一個繪圖不為每一個基礎繪圖承載一個完整的FrameworkElement性能消耗。雖然 創建一個DrawingBrush可以更有效,它在運行時消費了比較少的資源。如果你想你的用戶界面有獨特的復 雜的可視化,DrawingBrush將支持你以很少的消耗這麼做。如果你打算使用動畫,這種低消耗可能轉變為 更加平滑外觀的動畫。
DrawingBrush使得創建一個筆刷——像你的用戶界面的某一部分——是非常容易的。你可能使用它來 創建反光效果,或使用戶界面看起來是在三維中旋轉的。(這就超越了本書的范圍。)
7.3.6 Pen
筆刷用於填充形狀的內部。為了繪制形狀的輪廓,WPF需要更多一些的信息,不僅需要一個筆刷為屏幕 的區域著色,還需要知道繪制的線段有多厚以及你是否需要一個虛線樣式和一個密封蓋。Pen類提供了這 些信息。
Pen總是基於筆刷的,意味著到目前為止所有我們看到的繪圖效果,在我們繪制輪廓的時候,都可以使 用它。筆刷使用Brush屬性來設置。
#記住如果你與任何高級別的形狀元素一起工作,你將不會直接和Pen一起工作。Pen用於蓋子下面,但 是你間接地設置所有屬性。表7-1顯示了Shape屬性是如何對應到Pen屬性的。你可以典型地直接處理Pen, 僅在你工作在比較低的級別時,正如DrawingBrush中的GeometryDrawing。
線段的寬度由Thickness屬性設置。為了簡單的輪廓,這和Brush可能是你設置的僅有的屬性。然而, Pen可以提供更多。例如,你可以設置一個虛線樣式在DashArray屬性上。這是一個簡單的數字數組。每個 數字對應到虛線框中特別的部分的長度。示例7-42說明了最簡單的可能的樣式。
示例7-42
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="1" />
這說明了虛線樣式的第一個片段是一個長度。虛線樣式進行了重復,以及由於僅有的一個長度片段已 經被詳細指定了,每一個片段都是長度1。示例7-49顯示了結果。
圖7-49
示例7-43顯示了兩個略微比較有趣的樣式序列。注意到第二個例子提供了一個奇數的序列 。這意味著第一次環繞,實心片段為6以及間隙大小為1,但是當重復一個序列時,實心片段將會是長度1 以及間隙大小為6。因此,虛線樣式的有效長度被加倍了。這兩種樣式的結果都在圖7-50中顯示。
示例7-43
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="10 1 3 1" />
<Rectangle Stroke="Black" StrokeThickness="5" StrokeDashArray="6 1 6" />
圖7-50
拐角處被繪制成三種不同的方式。LineJoin屬性可以被設置為Miter、Bevel或者Round,從左到右顯示 在圖7-51中。
圖7-51
對於開放的形狀如Line或PolyLine,你可以詳細指出形狀的開始線段和結束線段——通過 StartLineCap和EndLineCap屬性。這些屬性支持四種樣式的洋蔥皮:Round、Triangle、Flat和Squate, 在圖7-52中從上到下顯示。Flat和Square在線段的終端都是方方正正的。區別在於Flat,扁平的終端交叉 了線段的終點,但是對於Square,它的擴展超越了其自身。它跨越線段的數量等同於線段厚度的一半。
圖7-52