圖形時繪圖的基礎,代表用戶界面樹的元素。WPF支持多種不同的形狀,並為 它們每一個都提供了元素類型。
7.2.1基本圖形類
在這一節列出的所有元素,派生於一個共同的抽象基類Shape。雖然你不能直 接使用這個類,知道它還是有幫助的,因為它定義了一組共同的特性——你可以 在任何形狀上使用。這些共同的屬性都被連接到形狀的內部和外部被繪制的地方 。
Fill屬性詳細指出了Brush要用於填充內部。Line和Polyline這些類沒有內部 ,所以它們沒有Fill屬性。(這比通過有獨立的Shape和FilledShape基類使繼承 層次復雜化的發式要簡單的多)Stroke屬性詳細指出了用來畫形狀輪廓的Brush 。
如果為你的形狀沒有詳細指出它的Fill或Stroke屬性,這將是不可見的,因 為這兩種屬性默認都是透明的。
這看起來特殊——Stroke屬性是Brush類型。正如我們早時看到的,WPF定義 了Pen類來詳細指明一個線條的厚度、dsah樣式以及樣子,因此如果Stroke屬性 是Brush類型的,這將是更有意義的。WPF實際上確實在內部使用了Pen來繪制形 狀的邊框。Stroke屬性為Brush類型主要是因為它的便利。所有的Pen樣式通過獨 立的Shape屬性對外暴露,正如表7-1所示。這詳細指明了該場景的標記——在你 樂於使用默認的鋼筆設置的地方,你不需要提供一個完整的Pen定義僅僅是設置 邊框顏色。
Table 7-1. Shape Stroke properties and Pen equivalents
筆刷和鋼筆都詳細描述在“Brushes and Pens”一節,在本章的後面。
7.2.2矩形
Rectangle實現了它的名稱所示的。無論任何形狀,它可以被填充來繪制,作 為一個邊框。或者全部。不但繪制一個正常的矩形,它還能畫一個圓角矩形。
Rectangle不提供任何屬性用於設置它的大小和位置。它依賴於同樣的外觀機 制如其它UI元素。位置由面板容器決定。長和寬都有它的父一級自動設置,或者 使用標准外觀屬性來顯示地設置Width和Height。
示例7-6顯示了Canvas面板上的一個Rectangle。這裡,Width和Height都被顯 示地設置,以及外觀被詳細的指定——通過使用附屬的Canvas.Left和 Canvas.Top屬性。
示例7-6
<Canvas>
<Rectangle Fill="Yellow" Stroke="Black"
Canvas.Left="30" Canvas.Top="10"
Width="100" Height="20" />
</Canvas>
示例7-7顯示了另一種方法。沒有一個矩形有其顯示設置的外觀和大小。取代 的,它們依賴於Grid容器。圖7-10顯示了結果。
圖7-10
示例7-7
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Rectangle Grid.Column="0" Grid.Row="0" Fill="LightGray" />
<Rectangle Grid.Column="1" Grid.Row="0" Fill="Black" />
<Rectangle Grid.Column="0" Grid.Row="1" Fill="DarkGray" />
<Rectangle Grid.Column="1" Grid.Row="1" Fill="White" />
</Grid>
Rectangle通常使用其父面板的坐標系統來排列。這意味著它的邊緣將通常是 垂直的或水平的,即使父一級面板被旋轉過了,Rectangle也當然會隨著它一起 旋轉。如果你想要旋轉一個Rectangle相對於它的容器面板,你可以使用有效的 RenderTransform屬性在所有的用戶界面元素上,正如示例7-8所證明的,這個示 例說明了RenderTransform的使用來旋轉一系列矩形。結果如圖7-11所示。
示例7-8
<Canvas>
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Indigo" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Violet" RenderTransform="rotate 45" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Blue" RenderTransform="rotate 90" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Cyan" RenderTransform="rotate 135" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Green" RenderTransform="rotate 180" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Yellow" RenderTransform="rotate 225" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Orange" RenderTransform="rotate 270" />
<Rectangle Canvas.Left="50" Canvas.Top="50" Width="40" Height="10"
Fill="Red" RenderTransform="rotate 315" />
</Canvas>
圖7-11
為了繪制一個圓角矩形,使用RadiusX和RadiusY屬性,正如示例7-9所示。表 7-12顯示了結果。
示例7-9
<Rectangle Width="100" Height="50" Fill="Black" RadiusX="30" RadiusY="40" />
圖7-12
7.2.3橢圓
Ellipse類似於Rectangle。明顯地,它繪制了一個橢圓而不是一個矩形,但 是Ellipse的大小、位置、填充和邊框,都以與Rectangle同樣的方式控制,正如 示例7-10顯示。結果顯示在7-13。
示例7-10
<Ellipse Width="100" Height="50" Fill="Yellow" Stroke="Black" />
圖7-13
7.2.4 線條
Line元素繪制了一條直線,從一點到另一點。它有四個屬性來控制起點和終 點的位置:X1、Y1、X2、Y2。這些坐標是相對於父面板選擇放置Line的位置。考 慮示例7-11。
示例7-11
<StackPanel Orientation="Vertical">
<TextBlock Background="LightGray">Foo</TextBlock>
<Line Stroke="Green" X1="20" Y1="10" X2="100" Y2="40" />
<TextBlock Background="LightGray">Bar</TextBlock>
<Line Stroke="Green" X1="0" Y1="10" X2="100" Y2="0" />
</StackPanel>
示例7-11使用了垂直的StackPanel來排列TextBlock和Line元素的交錯序列。 TextBlock元素有灰色的背景使之易於看到每個元素垂直的區域。結果如圖7-14 所示。
圖7-14
正如你在圖7-14看到的,Line元素像其它元素一樣被放置在棧中。 StackPanel被分配充分的高度以容納Line。第一條Line是有趣的——在這條線上 的TextBlock底部和該線段的起點間的一些空白。這是因為這條線段的Y1屬性已 經被設置為10,表明這條線段應該開始於放置Line元素位置的頂部的下面一點。 第二個Line元素與上一條線段完全一致,因為它的Y2屬性被設置為0,再次說明 了這條線段終點的坐標系統是面板容器相對於放置Line的區域的。
這裡沒有辦法自動設置線段的終點作為外觀的一部分(你能依賴外觀系統來 定位Ellipse和Rectangle元素,唯一的原因是它們的尺寸由矩形來決定,以及外 觀系統本質上處理了矩形的排列。)例如,你不能通知Line確切的寬度和父一級 面板放置的位置一樣。如果你想這麼做,只需取代地使用Rectangle,你可以通 過創建一個細矩形來繪制一條線段,如果需要就旋轉它。或者你可以使用一個 DrawingBrush或VisualBrush,當這些可以自動伸縮圖形來填充可利用的空間。
7.2.5 折線
Polyline使你繪制一系列連接的線段。取代現有的開始點和終結點的屬性, Polyline有一個Points屬性,包含了一個坐標對的清單,正如示例7-12所示。 WPF簡單地繪制了一條線段,按順序通過每一個點,正如圖7-15所示。
示例7-12
<Polyline Stroke="Blue"
Points="0,30 10,30 15,0 18,60 23,30 35,30 40,0 43,60 48,30 160,30" />
圖7-15
由於使用Line類,Polyline中點的坐標是相對於面板容器選擇來放置 Polyline的位置。
7.2.6多邊形
Polygon非常相似於Polyline。它有一個Points屬性,工作方式與Polyline相 同。唯一的區別是Polyline一直繪制一個敞開的形狀,而Polygon則總是繪制一 個封閉的形狀。為了說明這個不同,示例7-13包含了一個Polyline和一個 Polygon。所有同樣的屬性都設置為一樣的。
示例7-13
<StackPanel Orientation="Horizontal">
<Polyline Fill="Yellow" Stroke="Blue" Points="40,10 70,50 10,50" />
<Polygon Fill="Yellow" Stroke="Blue" Points="40,10 70,50 10,50" />
</StackPanel>
正如你在圖7-16中看到的,Polyline忽略了Fill屬性。這個形狀並沒有它的 內部,它被置為敞開的。Polygon,另一方面,通過在首尾兩條線段之間繪制一 條額外的線段而關閉了形狀,同時,它繪制了形狀的內部。
圖7-16
因為我們可以自由的添加點到Polygon中的任何地方,這就易於以一個自交的 形狀而告終,它的邊穿越了它自身。就是這樣一個形狀,這可能使含糊的關於有 多少數量作為形狀的內部。圖7-17顯示了這樣一個形狀以及兩個可能的填充方式 。
圖7-17
Polygon類提供了一個FillRule屬性來選擇一種潛在的處理任意區域的方式。 (在一些繪圖系統,這被描述為拓撲規則。)WPF支持兩種填充規則。示例7-14 是圖7-17的標記,顯示了使用中的填充規則。
示例7-14
<StackPanel Orientation="Horizontal">
<Polygon Fill="Yellow" Stroke="Blue" FillRule="EvenOdd"
Points="50,30 13,41 36,11 36,49 14,18" />
<Polygon Fill="Yellow" Stroke="Blue" FillRule="Nonzero"
Points="50,30 13,41 36,11 36,49 14,18" />
</StackPanel>
默認規則是EvenOdd,這用於圖7-17中的左邊的Polygon。這是能理解的最簡 單規則。為了決定一個特定的圍繞區域是在形狀的內部和外部,EvenOdd規則對 線段的數量進行計數——你不得不跨越從一個端點到另一個完全在形狀外的端點 。如果這個數量是奇數的,這個點就是在形狀中;反之,就是在外面。
第二個填充規則,Nonzero,是非常精細的。從圖7-17中,你可以已經想到任 何密閉的區域被認為是在圖形中的,但是這並不是那樣十分簡單的。Nonzero規 則表現了一個和Nonzero類似的過程,而不是簡單的對線段的數量計數。考慮到 線段運行位置的方向——它增長或減少的每條線段跨越的數量,依賴於這個方向 。如果在末端總計為nonzero,這個點被認為是在形狀內部。
在圖7-17中右邊的Polygon,Nonzero規則導致了所有的密閉區域作為內部的 一部分。然而,如果形狀的輪廓沿著一條較輕微盤旋的路徑,結果可能有一點更 混合,如示例7-15所示。
示例7-15
<Polygon Fill="Yellow" Stroke="Blue" FillRule="Nonzero"
Points="10,10 60,10 60,25 20,25 20,40 40,40 40,18 50,18 50,50 10,50" />
示例7-15的結果顯示在圖7-18中。這說明了Nonzero規則並不是像第一次看到 的那樣直接。
圖7-18
Nonzero規則有點怪。它是由PostScript普及的,因此大多數繪圖系統都支持 它,但是這並不總是容易的從一個帶有填充規則的Polygon中獲取有用的結果。 在Path元素的上下文中更加有意義——支持多重配置在一個單獨的形狀中。
7.2.7 路徑
Path是目前為止最強大的形狀。所有的形狀——到目前為止我們已經看到的 ——已經被便利地提供,因為使用Path繪制所有這些形狀是可能的。Path還使繪 制相當復雜的形狀成為可能——比我們之前看到的形狀更復雜。
就像Polygon,Path有一個FillRule屬性來控制填充規則。代替Points屬性, Path有一個Data屬性,它的類型是Geometry,這是一個抽象的基類。一個 Geometry對象表示一個特定的形狀。這裡有大量的具體類來表示不同類型的形狀 。其中三種聽起來相當熟悉:RectangleGeometry、EllipseGeometry和 LineGeometry。這些表示了我們先前見到的相同形狀。因此這個Rectangle:
<Rectangle Fill=”Blue” Width=”40” Height=”80” >
是有效的Path速記:
<Path Fill=”Blue”>
<Path.Data>
<RectangleGeometry Rect=”0, 0, 40, 80” />>
</Path.Data>
</Path>
在這一點,你可能想知道什麼時候你要使用RectangleGeometry、 EllipseGeometry或LineGeometry在Path中,來取代更簡單的Rectangle、 Ellipse和Line。原因是Path使你可以使用一種特殊類型的幾何對象,稱為 GeometryGroup,來創建一個帶有多個幾何體的形狀。
這裡有一個顯著的不同在使用多個明顯的形狀和有一個單獨的帶有多個幾何 題的形狀之間。讓我們看一下示例7-16。
示例7-16
<Canvas>
<Ellipse Fill="Blue" Stroke="Black" Width="40" Height="80" />
<Ellipse Canvas.Left="10" Canvas.Top="10" Fill="Blue" Stroke="Black"
Width="20" Height="60" />
</Canvas>
這繪制了兩個橢圓,一個在另一個的上面。他們都有一個黑色的輪廓,因此 你可以看到更簡單的一個在更大的另一個中,如圖7-19所示。
圖7-19
既然Ellipse形狀只是創建一個EllipseGeometry的簡單方式,示例7-16中的 代碼與示例7-17中的代碼是等價的。(正如你能看到的,使用Path是相當地更加 冗長。這是為什麼要提供Ellipse和其它簡單的形狀。)
示例7-17
<Canvas>
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<EllipseGeometry Center="20, 40" RadiusX="20" RadiusY="40" />
</Path.Data>
</Path>
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<EllipseGeometry Center="20, 40" RadiusX="10" RadiusY="30" />
</Path.Data>
</Path>
</Canvas>
因為示例7-17中的代碼與示例7-16中的代碼是等價的,它導致了准確的相同 輸出,正如圖7-19所示。到目前為止,使用幾何體取代形狀,並沒有在生成的結 果中制造出不同,這是因為我們仍然使用多個形狀。因此,我們現在將顯示如何 把所有的橢圓放進一個單獨的Path以及看到這是如何影響到這些結果。示例7-18 顯示了改動後的標記。
示例7-18
<Canvas Canvas.Left="100">
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center="20, 40" RadiusX="20" RadiusY="40" />
<EllipseGeometry Center="20, 40" RadiusX="10" RadiusY="30" />
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>
這個版本正好有一個單獨的路徑。它的Data屬性包含了一個GeometryGroup。 這就允許任何數量的幾何體對象被添加到同樣的路徑。這裡我們已經添加了兩個 EllipseGeometry元素——先前在兩個獨立的路徑中。結果如圖7-20所示,明顯 不同於圖7-19。在形狀中間有一個洞。因為默認的奇偶填充規則在使用中,較小 的橢圓在較大的橢圓中生成一個洞。
圖7-20
帶洞的形狀可以僅通過結合多個圖形到一個單獨的形狀中來創建。你可以試 著通過繪制一個內部的填充為白色的Ellipse,來得到類似的效果,如圖7-20所 示,但是這個“騙局”並不能工作——當你繪制一個形狀在其他事物之上的時候 ,如圖7-21所示。
圖7-21
你可能想知道如果使用可以用transparent透明色繪制內部的橢圓,但是這也 不能運行。繪圖時使用全部的透明色等效於,根本繪制不出任何東西。這就是透 明的含義。只有在形狀中釘進一個洞,我們才能識破它。
為了理解為什麼,想一想繪圖的過程。當它生成我們的元素到屏幕時,WPF繪 制這些元素一個接著一個。在這種情形中,它以文字開始——而無論什麼在後面 ,接著它繪制了文字之上的形狀——這將有效的刪除形狀後面的文字(當然它仍 然在元素樹中存在,因此WPF可以總是在以後重繪它,一旦你改變或者移除了圖 形。)由於你僅在文字上繪制,你不能繪制另一個形狀在其上來“恢復繪制”一 個洞到第一個形狀。因此如果你想要一個洞在形狀上,在你繪制這個形狀之前, 你最好確定這個洞在那裡。
我們還沒有看到最有彈性的幾何體:PathGeometry。它可以繪制任意Polygon 和Polyline能夠表示的形狀,除此之外,還可以繪制更多的形狀。
PolyGeometry包含了一個或更多的PathFigure對象,以及每一個PathFigure 代表了一個單獨的開放或密閉的形狀在路徑中。為了定義每個圖形的輪廓形狀, 你使用一個PathSegment對象的序列。
PathGeometry包含多個圖形的能力有點交疊於Path包含多個幾何體的能力。 這恰恰是為了便利,如果你需要生成一個每一部分都是一個PathGeometry對象的 形狀,這對一個單獨的帶有多個PathFigures的PathGeometry有更多的影響。如 果你只是想使用更簡單的幾何體,如LineGeometry或RectangleGeometry,比較 簡單的辦法是使用GeometryGroup以及同時要避免使用PathGemeetry。
示例7-19顯示了一個簡單的路徑。它僅包含了一個單獨的圖形和繪制了一個 矩形。
示例7-19
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<StartSegment Point="0,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
<LineSegment Point="0,50" />
<CloseSegment />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
結果顯示在圖7-22,這看起來是做了巨大的努力為獲取一個簡單的結果。我 們使用了17個線段標記來達到我們看到的效果——一個單獨的矩形。這是為什麼 WPF提供一些類為這些較簡單的形狀和幾何體。你不用嚴格地需要任何東西—— 因為你可以取代的使用Path和PathGeometry,但是它們要求更少的努力。正規的 ,你會為比較復雜的形狀而使用Path。
圖7-22
即使示例7-19生成了一個非常簡單的結果,它說明了帶有PathGeometry屬性 的Path大多重要的樣式。對比之前的所有示例,幾何體在路徑的Data屬性中。 PathGeometry是一個PathFigures的集合,因此所有有趣的數據都在他的Figures 屬性中。這個示例僅包含了PathFigure,但是你可以添加任意多你想要的。 PathFigure的形狀由它的Segments屬性中的項決定。
這些片斷的序列總是以StartSegment開始,這個標記決定了路徑的起始點。 接下來,它會沿著一個或多個決定圖形的形狀的分割而行。在示例7-19中,這些 全都是LineSeqment,因為圖形只有直邊,但是也提供一些圓弧的類型。最後, 這個示例以CloseSegment終止,指明這是一個密閉的形狀。如果我們想創建一個 開放的形狀(如Polyline),我們可以簡單的忽略最後的CloseSegment。
你可能想知道為什麼LineSegment不能像Line一樣工作。對於Line,我們可以 指出它的起點和終點,是示例7-11所示。這看起來比LineSegment簡單,後者需 要我們以StartSegment開始。
盡管如此,PathFigure中的線段不能工作,在圖形的輪廓沒有任何間隙。使 用Line元素,每一個Line都是截然不同的獨自形狀,而使用PathFigure,每個片 斷都是形狀輪廓的一部分。為了充分明確地定義一個圖形,每個片斷必須開始於 先前結束的那個片斷。這是為什麼LineSegment只要為線段詳細指明終止點。所 有的片斷類型以它們的方式工作。當然,圖形需要開始於某處,這是為什麼我們 總是開始於一個StartSegment。這是不合理的使用StartSegment在其它任何地方 而不是在圖形的起點。
示例7-19並不是非常令人激動的。它只是使用了直線片斷。替代的,我們可 以創建更多有趣的形狀,通過使用弧形片斷類型中的一種。表7-2顯示了所有的 片斷類型。
Table 7-2. Segment types
ArcSegment使你可以添加橢圓圓弧到形狀的邊緣。ArcSegment的使用比一個 簡單的LineSegment有一點更復雜。不僅要指出片段的終止點,我們必須還要通 過Size屬性指出橢圓的兩個半徑。
橢圓大小和線段的起始點終結點並未通過足夠的信息來明確地定義圓弧,因 為有很多種方式來在給定地約束下繪制一個橢圓形的圓弧。考慮一個帶有特定起 點和終點地片斷,以及一個給定的大小和定位的橢圓。
為了這種片斷,這裡將通常有兩種方式用來定位橢圓,從而它的起點和終點 都在橢圓的邊界上,正如圖7-23所示。換句話說,有兩種用一條特定的線段“切 割”一個橢圓的方法。
圖7-23
作為兩種分割橢圓式的任一種,就會有兩種分割結果,一個小的和一個大的 。這意味著有四種方式可以在兩點間繪制一條弧線。
ArcSegment提供了兩種標記,支持你選擇你需要那種弧線。LargeArc決定你 是選取較小的還是較大的那個分割片大小。SweepFlag選擇在線段的哪一邊繪制 的分割。示例7-20顯示了標記語法,說明所有的四種帶有這些標記的組合。它同 樣顯示了整個的橢圓。
示例7-20
<Canvas>
<Ellipse Fill="Cyan" Stroke="Black" Width="140" Height="60" />
<Path Fill="Cyan" Stroke="Black" Canvas.Left="180">
<Path.Data>
<PathGeometry>
<PathFigure>
<StartSegment Point="0,11" />
<ArcSegment Point="50,61" Size="70,30"
SweepFlag="False" LargeArc="False" />
<CloseSegment />
</PathFigure>
<PathFigure>
<StartSegment Point="30,11" />
<ArcSegment Point="80,61" Size="70,30"
SweepFlag="True" LargeArc="True" />
<CloseSegment />
</PathFigure>
<PathFigure>
<StartSegment Point="240,1" />
<ArcSegment Point="290,51" Size="70,30"
SweepFlag="False" LargeArc="True" />
<CloseSegment />
</PathFigure>
<PathFigure>
<StartSegment Point="280,1" />
<ArcSegment Point="330,51" Size="70,30"
SweepFlag="True" LargeArc="False" />
<CloseSegment />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
你可能想知道為什麼Ellipse的寬度為140和高度為60,這是每個ArcSegment 的Size屬性值的兩倍。這是因為ArcSegment解釋了Size為橢圓的兩個半徑,而橢 圓上的Width和Height屬性指出了全部的大小。
圖7-24顯示了結果,以及,正如你能看到的,每一個形狀都有一條徑直的對 角線以及一條橢圓的弧形。在所有的四種情形中,直線的邊緣具有相同的長度和 方向,圓弧的邊緣來自同一個橢圓的不同部分。
在圖7-24中,橢圓的軸是垂直和水平的。有時你可能想要使用一個橢圓,而 軸並沒有對齊到你的主軸。ArcSegment提供了Xrotation屬性,允許你詳細指出 需要旋轉的度數。
圖7-24
圖7-25顯示了3中橢圓軸。它們使用了和圖7-24相同的起點和終點以及相同的 橢圓大小。唯一的不同是詳細指出了XRocation為45度,在分割前對其進行了旋 轉。
圖7-25
切割橢圓的方式不只有兩種,這裡有兩種退化的情形。第一種是將橢圓從中 間切為相同的兩半。在這種情形種LargeArc標記就是不相關的了,因為兩個片斷 是相同的大小。
另一種情形是當橢圓非常的小,如果在橢圓可以被切割的最寬的點比片斷的 窄,這是沒有辦法正確繪制這個片段的。你應該試著避免這樣。(如果你確實使 橢圓非常小,WPF看起來按比例增加了橢圓使之足夠大,在X軸和Y軸按比例保持 它的樣子。)
其余的四種圓弧類型來自表7-2。BezierSegment、PolyBezierSegment、 QuadraticBezierSegment和PolyQuadraticBezierSegment是同一個主題的四種變 體。它們都繪制了貝賽爾曲線。
7.2.7.1貝賽爾曲線
貝賽爾曲線是基於一個特定的數學公式的連接兩點的曲線片斷。詳細理解這 個公式對於使用貝賽爾曲線是不必要的。它有用的地方在於為曲線圖提供了相當 程度的彈性。這使得它非常流行,大多數向量繪圖程序都提供貝賽爾曲線。圖7 -26顯示了各種貝賽爾曲線的片斷。
圖7-26
圖7-26中顯示的五條線中的每一條都是一個簡單的BezierSegment 。貝賽爾曲線在圖形系統得到了非常廣的使用——因為形狀中廣泛的變體,即使 一個單獨的片斷也能夠提供。它們還可以相當直接的使用。
作為所有片斷類型,BezierSegment開始於上一個片斷停止以及定義一個新的 終結點的位置。它還需要兩個控制點,來決定曲線的形狀。圖7-27再次顯示了相 同的曲線,但是曲線上帶有控制點。它還顯示了連接控制點到終止點片斷的線段 ,因為這使得更加容易看到:控制點如何影響曲線形狀。
圖7-27
控制點影響曲線形狀的最明顯方式是它們決定切線。在每個片斷 的起始點和終止點,曲線在那個點上運行的方向與連接起始點到相應的控制點的 線段的方向是一樣的。
這裡還有一個次要的不太明顯的控制點工作的方式。在開始點(或結束點) 和它相應的控制點間的距離也會產生影響。這就從本質上決定了曲率的極端程度 。
圖7-28顯示了一組貝賽爾曲線類似於圖7-27。這些結束與直線的切線是保持 同樣的,但是,在每種情形中,開始點和第一個控制點間的距離被減少到原先的 四分之一,爾另一個和之前是一樣的。正如你看道德,這減少了第一個控制點的 影響,曲線的形狀由遠離終點的控制點支配。
圖7-28
示例7-21顯示了圖7-27中第二個曲線片斷的標記,Point1屬性決 定了第一個控制點的位置,這個控制點聯合了起始點。Point2定位了第二個控制 點。Point3是終結點。
示例7-21
<StartSegment Point="0,50" />
<BezierSegment Point1="60,50" Point2="100,0" Point3="100,50" />
雖然貝賽爾曲線是彈性的,你很少使用到一個如此簡單的曲線。當定義帶 有彎曲邊緣的形狀時,對於一個形狀而言這是普通的——使用貝賽爾曲線定義它 的邊緣。WPF因此提供了一個PolyBezierSegment屬性,這是一個Point結構的數 組。每個貝賽爾曲線三個實體在這個數組中:兩個控制點和一個終結點。(通常 的,每條曲線開始於前一條曲線終結的位置。)示例7-22顯示了一個帶有兩個曲 線的片斷。圖7-29顯示了結果。
示例7-22
<StartSegment Point="0,0" />
<PolyBezierSegment>
<PolyBezierSegment.Points>
<Point X="0" Y="10"/>
<Point X="20" Y="10"/>
<Point X="40" Y="10"/>
<Point X="60" Y="10"/>
<Point X="120" Y="15"/>
<Point X="100" Y="50"/>
</PolyBezierSegment.Points>
</PolyBezierSegment>
圖7-29
這個標記有點不太便利,相較於使用一個BezierSegment元素序列 。幸運的是,你可以以字符串的形式提供所有點數據。這等價於示例7-22:
<PolyBezierSegment Points="0,10 20,10 40,10 60,10 120,15 100,50" />
同樣,如果你從代碼中生成坐標,處理一個單獨的PolyBezierSegment和傳遞 給它一個Point數據的數組,這比處理一些獨立的片斷要容易的多。
貝賽爾曲線在曲線的形狀上提供了許多控件。然而,你可能不總是想要彈性 的等級。QuadraticBezierSegment使用了一個較簡單的等式——僅使用了一個指 向定義了曲線形狀的控件。這沒有提供同樣范圍的曲線形狀,作為一個立方體的 貝賽爾曲線,但是如果所有你想要的只是一個簡單的形狀,這就減少了三分之一 你需要提供的坐標對數量。
QuadraticBezierSegment使用上與正常的BezierSegment類似。唯一的不同是 他每有一個Point3屬性,只有Point1和Point2。Point1是共享控制點,Point2是 終結點。QuadraticBezierSegment是一個多曲線的裝備。你以與 PolyBezierSegment相同的方式使用它,除了你只需要為每個片斷提供兩個點。
7.2.7.2結合形狀
Path自有它的玄機——我們至今沒有檢查到的。它有聯合若干幾何體以形成 一個新幾何體的能力。這是不同於添加到兩個幾何體到一個Path中,它聯合了成 對的幾何體在某種程度上形成了一個單獨的具有完整形狀的新幾何體。
示例7-23和示例7-24各自定義了路徑,它們全都使用了同樣的 RectangleGeometry和EllipseGeometry。區別是示例7-23把它們都放入了一個 GeometryGroup中,而示例7-24把它們都放入了一個CombinedGeometry中
示例7-23
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<GeometryGroup>
<RectangleGeometry Rect="0,0,50,50" />
<EllipseGeometry Center="50,25" RadiusX="30" RadiusY="10" />
</GeometryGroup>
</Path.Data>
</Path>
示例7-24
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<CombinedGeometry CombineMode="Exclude">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0,50,50" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="50,25" RadiusX="30" RadiusY="10" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
圖7-30顯示了示例7-23和示例7-24的結果。CombinedGeometry導致了帶有多 個圖形的一個形狀,CombinedGeometry生成了一個單獨的圖形。橢圓幾何體在矩 形幾何體咬進去一口。這只是聯合若干幾何體方式中的一種。CombineMode屬性 決定了使用哪一個,圖7-31顯示了所有的五種可利用的類型。
圖7-30
圖7-31
聯合模式有以下的效果:
Union
建立了一個形狀,任意的在原始的兩個形狀中任一個的點,都在這個新的形 狀中。
Intersect
建立了一個形狀,任意的同時在原始的兩個形狀中的點,都在這個新的形狀 中。
Xor
建立了一個形狀,任意的只在原始的兩個形狀中其中一個的點,都在這個新 的形狀中。
Execlude
建立了一個形狀,任意的只在第一個而不在第二個形狀中的點,都在這個新 的形狀中。
Complement
建立了一個形狀,任意的只在第二個而不在第一個形狀中的點,都在這個新 的形狀中。
7.2.7.3數據屬性文本格式
我們已經看到Path提供的所有的樣式。正如你看到的,我們可以終結某些相 當冗長的標記。幸運的是,Path元素提供了一個速記機制——允許大多數我們看 到過的樣式可以被開發而不用敲擊太多代碼。
到目前位置,我們已經設置了Data屬性,使用了xaml的屬性元素語法(見附 錄A獲取更多語法的細節)。然而,我們可以替代的提供一個字符串。示例7-25 顯示了這兩種技術。正如你看到的,使用字符串的形式只有16行這麼短。
示例7-25
<!-- Longhand -->
<Path Fill="Cyan" Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<StartSegment Point="0,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
<LineSegment Point="0,50" />
<CloseSegment />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<!-- Shorthand -->
<Path Fill="Cyan" Stroke="Black" Data="M 0,0 L 50,0 50,50 0,50 Z" />
文本形式的Path.Data屬性的語法是簡單的。這個字符串包含一個命令序列 。命令是一個依照某些數字化參數的字母。需要的參數數量由被選擇的命令決定 。線條僅需要一對坐標。曲線則需要更多的數據。
如果你忽略了這個字母,同樣的命令將會被作為上一次使用。例如,示例7- 25使用了L命令,這是“Line”的簡寫,代表了LineSegment。這只要求2個數字 ,線段終結點的坐標。以及在我們的示例中,這裡有6個數字。這簡單的指明了 每行有3條線。表7-3列出了這些命令,它們的等價片斷類型,以及它們的用法。
M命令有特殊的待遇。在任何地方使用StartSegment而不是第一個PathFigure片 斷是合理的。如果你使用M命令在區域中間,這就意味著你將要開始一個新的 PathFigure。這支持多個圖形在這個復雜文本格式中表示。
注意到有兩種方式詳細指明一個BezierSegment。C命令讓你提供所有的控制 點。S命令為你的外觀在第一個片斷生成第一個控制點,以及生成指向前一個片 斷鏡像的第一個控制點。這就確保了片斷的切線與前一個片斷對齊,導致了以一 種平滑的方式連接線段。
所有這些命令都可以兩種方式使用。你可以詳細指出命令以它的大寫或小寫 形式。以大寫的形式,坐標是相於Path元素的位置。如果命令是小寫的,坐標是 相對於路徑中前一個片斷的終結點。
現在我們已經檢查到當前提供的形狀,但是到目前位置,我們已經相當沒有 危險的在我們的為這些形狀填充和輪廓的選擇中。我們已經使用命名的顏色和簡 單的輪廓樣式。WPF允許我們通過它的筆刷和鋼筆類,使用更多不同種類的繪制 樣式。