《WPF編程寶典(第2版)》第3章 佈局

第3章 佈局

在任意用戶界面設計中,有一半的工作量是以富有吸引力、靈活實用的方式組織內容。但真正的挑戰是確保界面佈局能夠恰到好處地適應不同的窗口尺寸。

WPF用不同的容器(container)安排佈局。每個容器有各自的佈局邏輯——有些容器以堆棧方式佈置元素,另一些容器在網格中不可見的單元格中排列元素,等等。在WPF中非常抵制基於座標的佈局,而是注重創建更靈活的佈局,使佈局能夠適應內容的變化、不同的語言以及各種窗口尺寸。遷移到WPF的許多開發人員會覺得新佈局系統令自己倍感驚奇——這也是開發人員面臨的第一個真正挑戰。

本章將介紹WPF佈局模型的工作原理,並且將開始使用基本的佈局容器。爲了學習WPF佈局的基礎知識,本章將介紹幾個通用的佈局示例——從基本的對話框乃至可改變尺寸的拆分窗口。

3.1 理解WPF中的佈局

在Windows開發人員設計用戶界面的方式上,WPF佈局模型是一個重大改進。在WPF問世之前,Windows開發人員使用刻板的基於座標的佈局將控件放到正確的位置。在WPF中這種方式雖然可行,但已經極少使用。大多數應用程序將使用類似於Web的流(flow)佈局;在使用流佈局模型時,控件可以擴大,並將其他控件擠到其他位置,開發人員能創建與顯示分辨率和窗口大小無關的、在不同的顯示器上正確縮放的用戶界面;當窗口內容發生變化時,界面可調整自身,並且可以自如地處理語言的切換。要利用該系統的優勢,首先需要進一步理解WPF佈局模型的基本概念和假設。

3.1.1 WPF佈局原則

WPF窗口只能包含單個元素。爲在WPF窗口中放置多個元素並創建更貼近實用的用戶界面,需要在窗口上放置一個容器,然後在這個容器中添加其他元素。

注意:

造成這一限制的原因是Window類繼承自ContentControl類,在第6章中將進一步分析ContentControl類。

在WPF中,佈局由您使用的容器來確定。儘管有多個容器可供選擇,但“理想的”WPF窗口需要遵循以下幾條重要原則:

  • 不應顯式設定元素(如控件)的尺寸。元素應當可以改變尺寸以適合它們的內容。例如,當添加更多的文本時按鈕應當能夠擴展。可通過設置最大和最小尺寸來限制可以接受的尺寸範圍。
  • 不應使用屏幕座標指定元素的位置。元素應當由它們的容器根據它們的尺寸、順序以及(可選的)其他特定於具體佈局容器的信息進行排列。如果需要在元素之間添加空白空間,可使用Margin屬性。

提示:

以硬編碼方式設定尺寸和位置是極其不當的處理方式,因爲這會限制本地化界面的能力,並且會使界面更難處理動態內容。

  • 佈局容器的子元素“共享”可用的空間。如果空間允許,佈局容器會根據每個元素的內容儘可能爲元素設置更合理的尺寸。它們還會向一個或多個子元素分配多餘的空間。
  • 可嵌套的佈局容器。典型的用戶界面使用Grid面板作爲開始,Grid面板是WPF中功能最強大的容器,Grid面板可包含其他佈局容器,包含的這些容器以更小的分組排列元素,比如帶有標題的文本框、列表框中的項、工具欄上的圖標以及一列按鈕等。

儘管對於這幾條原則而言也有一些例外,但它們反映了WPF的總體設計目標。換句話說,如果創建WPF應用程序時遵循了這些原則,將會創建出更好的、更靈活地用戶界面。如果不遵循這些原則,最終將得到不是很適合WPF的並且難以維護的用戶界面。

3.1.2 佈局過程

WPF佈局包括兩個階段:測量(measure)階段和排列(arrange)階段。在測量階段,容器遍歷所有子元素,並詢問子元素它們所期望的尺寸。在排列階段,容器在合適的位置放置子元素。

當然,元素爲必總能得到最合適的尺寸——有時容器沒有足夠大的空間以適應所含的元素。在這種情況下,容器爲了適應可視化區域的尺寸,就必須剪裁不能滿足要求的元素。在後面可以看到,通常可通過設置最小窗口尺寸來避免這種情況。

注意:

佈局容器不能提供任何滾動支持。相反,滾動是由特定的內容控件——ScrollViewer——提供的,ScrollViewer控件幾乎可用於任何地方。在第6章中將學習ScrollViewer控件的相關內容。

3.1.3 佈局容器

所有WPF佈局容器都是派生自System.Windows.Controls.Panel抽象類的面板(見圖3-1)。Panel類添加了少量成員,包括三個公有屬性,表3-1列出了這三個公有屬性的詳情。

注意:

Panel類還包含了幾個內部屬性,如果希望創建自己的容器,就可以使用它們。最特別的是,可重寫繼承自FrameworkElement類的MeasureOverride()和ArrangeOverride()方法,以修改當組織子元素時面板處理測量階段和排列階段的方式。第18章將介紹如何創建自定義面板。

就Panel基類本身而言沒有什麼特別的,但它是其他更多特殊類的起點。WPF提供了大量可用於安排佈局的繼承自Panel的類,表3-2中列出了其中幾個最基本的類。與所有WPF控件和大多數可視化元素一樣,這些類位於System.Windows.Controls名稱空間中。

除這些核心容器外,還有幾個更專業的面板,在各種控件中都可能遇到它們。這些容器包括專門用於包含特定控件子元素的面板——如TabPanel面板(在TabPanel面板中包含多個選項卡)、ToolbarPanel面板(工具欄中的多個按鈕)以及ToolbarOverflowPanel面板(Toolbar控件的溢出菜單中的多個命令)。還有VirtualizingStackPanel面板,數據綁定列表控件使用該面板以大幅降低開銷;還有InkCanvas控件,該控件和Canvas控件類似,但該控件支持處理平板電腦(TablePC)上的手寫筆(stylus)輸入(例如,根據選擇的模式,InkCanvas控件支持使用指針繪製範圍,以選擇屏幕上的元素。也可通過普通計算機和鼠標使用InkCanvas控件,儘管這有點違反直覺)。本章將介紹InkCanvas,第19章將詳細介紹VirtualizingStackPanel,在本書其他地方談到相關控件時,將介紹其他專門的面板。

3.2 使用StackPanel面板進行簡單佈局

StackPanel面板是最簡單地佈局容器之一。該面板簡單地在單行或單列中以堆棧形式佈置其子元素。

例如,分析下面的窗口,該窗口包含4個按鈕:

<Window
    x:Class="StackPanel.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:StackPanel"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="Layout" Width="354" Height="223"
    mc:Ignorable="d">
    <StackPanel>
        <Label>A Button Stack</Label>
        <Button>Button 1</Button>
        <Button>Button 2</Button>
        <Button>Button 3</Button>
        <Button>Button 4</Button>
    </StackPanel>
</Window>

圖3-2顯示了最終得到的窗口。

在Visual Studio中添加布局容器

在Visual Studio中使用設計器創建這個示例要比較容易。首先刪除Grid根元素(如果有的話)。然後將一個StackPanel面板拖動到窗口上。接下來將其他元素以所希望的自上而下的順序(標籤和4個按鈕)拖放到窗口上。如果想重新排列StackPanel面板中的元素,可以簡單地將它們拖動到新的位置。

雖然本書不會佔用大量的篇幅來討論Visual Studio的設計時支持特性,但實際上,自從推出首個WPF版本以來,Visual Studio已經做了很大的改進。例如,Visual Studio不再爲添加到設計器中的每個新控件指定名稱;而且除非您手動調整大小,Visual Studio不再添加硬編碼的Width值和Height值。

默認情況下,StackPanel面板按自上而下的順序排列元素,使每個元素的高度適合它的內容。在這個示例中,這意味着標籤和按鈕的大小剛好足夠適應它們內部包含的文本。所有元素都被拉伸到StackPanel面板的整個寬度,這也是窗口的寬度。如果加寬窗口,StackPanel面板也會邊框,並且按鈕也會拉伸自身以適應變化。

通過設置Orientation屬性,StackPanel面板也可用於水平排列元素:

<StackPanel Orientation="Horizontal">

現在,元素指定它們的最小寬度(足以適合它們所包含的文本)並拉伸至容器面板的整個高度。根據窗口的當前大小,這可能導致一些元素不適應,如圖3-3所示。

顯然,這並未提供實際應用程序所需的靈活性。幸運的是,可使用佈局屬性對StackPanel面板和其他佈局容器的工作方式進行精細調整,如稍後所述。

3.2.1 佈局屬性

儘管佈局由容器決定,但子元素仍有一定的決定權。實際上,佈局面板支持一小組佈局屬性,以便與子元素結合使用,在表3-3中列出了這些佈局屬性。

所有這些屬性都從FrameworkElement基類繼承而來,所以在WPF窗口中可使用的所有圖形小組件都支持這些屬性。

注意:

您在第2章中已學習過,不同的佈局容器可以爲它們的子元素提供附加屬性。例如,Grid對象的所有子元素可以獲得Row和Column屬性,以便選擇容納它們的單元格。通過附加屬性可爲特定的佈局容器設置其特有的信息。然而,在表3-3中列出的佈局屬性是可以應用於許多佈局面板的通用屬性。因此,這些屬性被定義爲FrameworkElement基類的一部分。

這個屬性列表就像它所沒有包含的屬性一樣值得注意。如果查找熟悉的與位置相關的屬性,例如Top屬性、Right屬性以及Location屬性,是不會找到它們的。這是因爲大多數佈局容器(Canvas控件除外)都使用自動佈局,並未提供顯式定位元素的能力。

3.2.2 對齊方式

爲理解這些屬性的工作原理,可進一步分析圖3-2中顯示的簡單StackPanel面板。在示例中——有一個垂直方向的StackPanel面板——VerticalAlignment屬性不起作用,因爲所有元素的高度都自動地調整爲剛好滿足各自需求。但HorizontalAlignment屬性非常重要,它決定了各個元素在行的什麼位置。

通常,對於Label控件,HorizontalAlignment屬性的值默認爲Left;對於Button控件,HorizontalAlignment屬性的值默認爲Stretch。這也是爲什麼每個按鈕的寬度被調整爲整列的寬度的原因所在。但可以改變這些細節:

<StackPanel>
    <Label HorizontalAlignment="Center">A Button Stack</Label>
    <Button HorizontalAlignment="Left">Button 1</Button>
    <Button HorizontalAlignment="Right">Button 2</Button>
    <Button>Button 3</Button>
    <Button>Button 4</Button>
</StackPanel>

圖3-4顯示了最終結果。現在前面兩個按鈕的尺寸是它們應當具有的最小尺寸,並進行了對齊,而底部兩個按鈕被拉伸至整個StackPanel面板的寬度。如果改變窗口的尺寸,就會發現標籤保持在中間位置,而前兩個按鈕分別被粘貼到兩邊。

注意:

StackPanel面板也有自己的HorizontalAlignment和VerticalAlignment屬性。這兩個屬性默認都被設置爲Stretch,所以StackPanel面板完全充滿它的容器。在這個示例中,這意味着StackPanel面板充滿整個窗口。如果使用不同設置,StackPanel面板的尺寸將足夠寬以容納最寬的控件。

3.2.3 邊距

3.2.4 最小尺寸、最大尺寸以及顯式地設置尺寸

3.2.5 Border控件

Border控件不是佈局面板,而是非常便於使用的元素,經常與面板一起使用。所以,在繼續介紹其他佈局面板之前,現在先介紹一下Border控件是有意義的。

Border類非常簡單。它只能包含一段嵌套內容(通常是佈局面板),併爲其添加背景或在其周圍添加邊框。爲了深入理解Border控件,只需要掌握表3-4中列出的屬性就可以了。

下面是一個具有輕微圓角效果的簡單邊框,該邊框位於一組按鈕的周圍,這組按鈕包含在一個StackPanel面板中:

<Border Margin="5" Padding="5" VerticalAlignment="Top" Background="LightYellow" BorderBrush="SteelBlue" BorderThickness="3,5,3,5" CornerRadius="3">
    <StackPanel>
        <Button Margin="3">One</Button>
        <Button Margin="3">Two</Button>
        <Button Margin="3">Three</Button>
        <Button Margin="3">Four</Button>
    </StackPanel>
</Border>

圖3-8顯示了該例的結果。

第6章將介紹有關畫刷和顏色的詳情,它們可用於設置BorderBrush和Background屬性。

注意:

從技術角度看,Border是裝飾元素(decorator),裝飾元素是特定類型的元素,通常用於在對象周圍添加某些種類的圖形裝飾。所有裝飾元素都繼承自System.Windows.Controls.Decorator類。大多數裝飾元素設計用於特定控件。例如,Button控件使用ButtonChrome裝飾元素,以獲取其特有的圓角和陰影背景效果;而ListBox控件使用ListBoxChrome裝飾元素。還有兩個更通用的裝飾元素,當構造用戶界面時它們非常有用:在此討論的Border元素以及將在第12章中研究的Viewbox元素。

3.3 WrapPanel和DockPanel面板

顯然,只使用StackPanel面板還不能幫助您創建出實用的用戶界面。要設計出最終使用的用戶界面,StackPanel面板還需要與其他更強大的佈局容器協作。只有這樣才能組裝成完整的窗口。

最複雜的佈局容器是Grid面板,稍後將分析該面板。在介紹Grid面板之前,有必要首先看一下WrapPanel和DockPanel面板,它們是WPF提供的兩個更簡單地佈局容器。這兩個佈局容器通過不同的佈局行爲對StackPanel面板進行補充。

3.3.1 WrapPanel面板

WrapPanel面板在可能的空間中,以一次一行或一列的方式佈置控件。默認情況下,WrapPanel.Orientation屬性設置爲Horizontal;控件從左向右進行排列,再在下一行中排列。但可將WrapPanel.Orientation屬性設置爲Vertical,從而在多箇中放置元素。

提示:

與StackPanel面板類似,WrapPanel面板實際上主要用來控制用戶界面中一小部分的佈局細節,並非用於控制整個窗口布局。例如,可能使用WrapPanel面板以類似工具欄控件的方式將所有按鈕保持在一起。

下面的示例中定義了一系列具有不同對齊方式的按鈕,並將這些按鈕放到一個WrapPanel面板中:

<WrapPanel Margin="3">
    <Button VerticalAlignment="Top">Top Button</Button>
    <Button MinHeight="60">Tall Button 2</Button>
    <Button VerticalAlignment="Bottom">Bottom</Button>
    <Button>Stretch</Button>
    <Button VerticalAlignment="Center">Centered Button</Button>
</WrapPanel>

圖3-9顯示瞭如何對這些按鈕進行換行以適應WrapPanel面板的當前尺寸(WrapPanel面板的當前尺寸是由包含它的窗口的尺寸決定的)。正如這個示例所演示的,WrapPanel面板水平的創建了一系列假想的行,每一行的高度都被設置爲所包含元素中最高元素的高度。其他控件可能被拉伸以適應這一高度,或根據VerticalAlignment(垂直對齊)屬性的設置進行對齊。在圖3-9的左圖中,所有按鈕都在位於較高的行中,並被拉伸或對齊以適應該行的高度。在右圖中,有幾個按鈕被擠到第二行中。因爲第二行沒有包含特別高的按鈕,所以第二行的高度保持爲最小按鈕的高度。因此,在該行中不必關心各按鈕的VerticalAlignment屬性的設置。

注意:

WrapPanel面板是唯一一個不能通過靈活使用Grid面板代替的面板。

3.3.2 DockPanel面板

DockPanel面板是更有趣的佈局選項。它沿着一條外邊緣來拉伸所包含的控件。理解該面板最簡便的方法是,考慮一下位於許多Windows應用程序窗口頂部的工具欄。這些工具欄停靠到窗口頂部。與StackPanel面板類似,被停靠的元素選擇它們佈局的一個方面。例如,如果將一個按鈕停靠在DockPanel面板的頂部,該按鈕會被拉伸至DockPanel面板的整個寬度,但根據內容和MinHeight屬性爲其設置所需的高度。而如果將一個按鈕停靠到容器左邊,該按鈕的高度將被拉伸以適應容器的高度,而其寬度可以根據需要自由增加。

這裏很明顯的問題是:子元素如何選擇停靠的邊?答案是通過Dock附加屬性,可將該屬性設置爲Left、Right、Top或Bottom。放在DockPanel面板中的每個元素都會自動捕獲該屬性。

下面的示例在DockPanel面板的每條邊上都停靠一個按鈕:

<DockPanel LastChildFill="True">
    <Button DockPanel.Dock="Top">Top Button</Button>
    <Button DockPanel.Dock="Bottom">Bottom Button</Button>
    <Button DockPanel.Dock="Left">Left Button</Button>
    <Button DockPanel.Dock="Right">Right Button</Button>
    <Button>Remaining Space</Button>
</DockPanel>

該例還將DockPanel面板的LastChildFill屬性設置爲true,該設置告訴DockPanel面板使最後一個元素佔滿剩餘空間。圖3-10顯示了結果。

顯然,當停靠控件時,停靠順序很重要。在這個示例中,頂部和底部按鈕充滿了DockPanel使最後一個元素佔滿剩餘空間。圖3-10顯示了結果。

顯然,當停靠控件時,停靠順序很重要。在這個示例中,頂部和底部按鈕充滿了DockPanel面板的整個邊緣,這是因爲這兩個按鈕首先被停靠。接着停靠左邊和右邊的按鈕時,這兩個按鈕將位於頂部按鈕和底部按鈕之間。如果改變這一順序,那麼左邊和右邊的按鈕將充滿整個面板的邊緣,而頂部和底部的按鈕則變窄一些,因爲它們將在左邊和右邊的兩個按鈕之間進行停靠。

可將多個元素停靠到同一邊緣。這種情況下,元素按標記中聲明的順序停靠到邊緣。而且,如果不喜歡空間分割或拉伸行爲,可修改Margin屬性、HorizontalAlignment(水平排列)屬性以及VerticalAlignment(垂直排列)屬性,就像使用StackPanel面板進行佈局時所介紹的那樣。下面是前面演示的程序的修改版本:

<DockPanel LastChildFill="True">
    <Button DockPanel.Dock="Top">A Stretched Top Button</Button>
    <Button HorizontalAlignment="Center" DockPanel.Dock="Top">A Centered Top Button</Button>
    <Button HorizontalAlignment="Left" DockPanel.Dock="Top">A Left-Aligned Top Button</Button>
    <Button DockPanel.Dock="Bottom">Bottom Button</Button>
    <Button DockPanel.Dock="Left">Left Button</Button>
    <Button DockPanel.Dock="Right">Right Button</Button>
    <Button>Remaining Space</Button>
</DockPanel>

停靠行爲保持不變。首先停靠頂部按鈕,然後是底部按鈕,頂部和底部按鈕之間剩餘的空間會被分割,並且最後一個按鈕在中間。圖3-11顯示了最終窗口。

3.7 小結

本章詳細介紹了WPF佈局模型,並討論瞭如何以堆棧、網格以及其他排列方式放置元素。可使用嵌套的佈局容器組合創建更復雜的佈局,可結合使用GridSplitter對象創建可變的分割窗口。本章一直非常關注這一巨大變化的原因——WPF佈局模型在保持、加強以及本地化用戶界面方面所具有的優點。

佈局內容遠不止這些。接下來的幾章還將列舉更多使用佈局容器組織元素分組的示例,還將學習允許在窗口中排列內容的幾個附加功能:

  • 特殊容器。可以使用ScrollViewer、TabItem以及Expander控件滾動內容、將內容放到單獨的選項卡中以及摺疊內容。與佈局面板不同,這些容器只能包含單一內容。不過,可以很容易地組合使用這些容器和佈局面板,以便準確實現所需的效果。第6章將嘗試使用這些容器。
  • Viewbox。需要一種方法來改變圖形內容(如圖像和矢量圖形)的尺寸嗎?Viewbox是另一種特殊容器,可以幫助您解決這一問題,而且Viewbox控件內置了縮放功能。在第12章中,您將首次接觸到Viewbox容器。
  • 文本佈局。WPF新增了用於確定大塊格式化文本佈局的新工具。可使用浮動圖形和列表,並且可以使用分頁、分列以及更復雜、更智能的換行功能來獲得非常完美的結果。第28章將介紹文本佈局的方式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章