【WPF學習】第三十九章 理解形狀

  在WPF用戶界面中,繪製2D圖形內容的最簡單方法是使用形狀(shape)——專門用於表示簡單的直線、橢圓、矩形以及多變形的一些類。從技術角度看,形狀就是所謂的繪圖圖元(primitive)。可組合這些基本元素來建立更復雜的圖形。數據庫

  關於WPF中形狀的重要細節是,它們都繼承自FrameworkElement類。所以,形狀是元素。這樣會帶來許多重要的結果:app

  •   形狀繪製自身。不須要管理無效的狀況和繪圖過程。例如,當移動內容、改變窗口尺寸或改變形狀屬性時,不須要手動從新繪製形狀。
  •   使用與其餘元素相同的方式組織形狀。換句話說,可在前面學過的任何佈局容器中放置形狀(儘管Canvas明顯是最有用的容器,由於它容許在特定的座標位置放置形狀,當構建複雜的具備多個部分的圖畫時,這很重要)。
  •   形狀支持與其餘元素相同的事件。這意味着爲了處理焦點、按下鍵盤、移動鼠標以及單擊鼠標等,沒必要執行任何額外工做。可以使用用於其餘元素的相同事件集,並一樣支持工具提示、上下文菜單和拖放操做。

1、Shape類ide

  每一個形狀都繼承自抽象類System.Windows.Shapes.Shape。下圖顯示了形狀類的繼承層次。工具

 圖 WPF形狀類佈局

  正如上面看到的,相對來講,只有不多一部分類繼承自Shape類。Line、Ellipse以及Rectangle都很直觀,Polyline是一系列相互鏈接的直線,Polygon是由一系列相互鏈接的直線造成的閉合圖形。最後,Path類功能強大,能將多個基本形狀組合成單獨的元素。字體

  儘管Shape類自身不能執行任何工做,但它定義了少許的重要屬性。下表列出了這些屬性。編碼

表 Shape類的屬性spa

 2、矩形和橢圓設計

  矩形和橢圓是兩個最簡單的形狀。爲建立矩形或橢圓,須要設置你們熟悉的Height和Width屬性(這兩個屬性繼承自FrameworkElement類)來定義形狀的尺寸,而後設置Fill或Stroke屬性(或同時設置這兩個屬性)使形狀可見。還可使用MinHeigth、MinWidth、HorizontalAlignment、VerticalAlignment以及Margin等屬性。3d

  下面舉一個簡單示例,該例在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

 3、改變形狀的尺寸和放置形狀

  正如前面所知,贏編碼尺寸一般不是建立用戶界面的理想方法。它們會限制處理動態內容的能力,並會使應用程序本地化到其餘語言變得更加困難。

  當繪製形狀時,再也不老是關心這些問題。一般,須要更嚴格地控制形狀的位置。然而,在許多狀況下仍須要靈活一點設計。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容器,對於在可自由流動的動態用戶界面中鎖定一小部分繪圖邏輯,這是一種很是好的方法。

4、使用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元素根據可用空間和佈局容器按比例改變尺寸。

5、直線

  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座標)。經過這種方法,可根據須要將整個圖畫移到新的位置。

6、折線

  能夠經過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。

7、多邊形

  實際上,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規則的複雜問題在於填充設置依賴於形狀的繪製,而不是形狀自身的外觀。

8、直線線帽和直線交點

  當繪製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倍距離。如上圖的最後一條直線使用了更高的銳化範圍,從而具備更狹長的拐角。

9、點劃線

  除了爲形狀的邊框繪製乏味的實線外,還可繪製點劃線(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等值。請記住,全部這些設置都會在點劃線的端點增長直線寬度的一半長距離。若是沒有考慮這一額外的距離,最終可能會使點劃線相互重疊。解決方法是增長額外的空白以進行補償。

10、像素對齊

  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

相關文章
相關標籤/搜索