此主題描述 Windows Presentation Foundation (WPF) 版面配置系統。 了解如何及何時進行版面配置計算,對於在 WPF 中建立使用者介面十分重要。
本主題包含下列幾節:
元素邊界框
在考慮 WPF 中的版面配置時,請務必了解包圍所有元素的周框方塊。 版面配置系統所消耗的每個 FrameworkElement 都可視為佈局中的矩形。 LayoutInformation 類別會傳回元素配置分配或槽位的界限。 矩形的大小取決於計算可用的螢幕空間、任何約束條件的大小、版面配置特有的屬性 (例如邊距和內距),以及父 Panel 元素的個別行為。 處理此資料,版面配置系統就能夠計算特定Panel的所有子元素的位置。 請務必記住,父元素上所定義的調整大小特性,例如Border,會影響其子元素。
下圖顯示簡單的版面配置。
此螢幕擷取畫面顯示典型的網格,未疊加框線。
使用下列 XAML,即可實現此版面配置。
<Grid Name="myGrid" Background="LightSteelBlue" Height="150">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Name="txt1" Margin="5" FontSize="16" FontFamily="Verdana" Grid.Column="0" Grid.Row="0">Hello World!</TextBlock>
<Button Click="getLayoutSlot1" Width="125" Height="25" Grid.Column="0" Grid.Row="1">Show Bounding Box</Button>
<TextBlock Name="txt2" Grid.Column="1" Grid.Row="2"/>
</Grid>
單一 TextBlock 元素被裝載於 Grid 內。 儘管文字只填入第一列的左上角,但分配給 TextBlock 的空間實際上大得多。 您可以使用任何FrameworkElement的GetLayoutSlot方法來取得邊界框。 下圖顯示 TextBlock 元素的邊界框。
螢幕擷取畫面顯示現在可以看見 TextBlock 周框方塊。
如黃色矩形所示,分配給TextBlock 元素的空間實際上遠比看起來大。 將其他元素新增至 Grid 時,此配置可能會縮減或擴充,具體取決於所新增元素的類型和大小。
您可以使用 GetLayoutSlot 方法,將 TextBlock 的佈局槽轉換為 Path。 這種技術可用於顯示元素的邊界框。
private void getLayoutSlot1(object sender, System.Windows.RoutedEventArgs e)
{
RectangleGeometry myRectangleGeometry = new RectangleGeometry();
myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1);
Path myPath = new Path();
myPath.Data = myRectangleGeometry;
myPath.Stroke = Brushes.LightGoldenrodYellow;
myPath.StrokeThickness = 5;
Grid.SetColumn(myPath, 0);
Grid.SetRow(myPath, 0);
myGrid.Children.Add(myPath);
txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString();
}
Private Sub getLayoutSlot1(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim myRectangleGeometry As New RectangleGeometry
myRectangleGeometry.Rect = LayoutInformation.GetLayoutSlot(txt1)
Dim myPath As New Path
myPath.Data = myRectangleGeometry
myPath.Stroke = Brushes.LightGoldenrodYellow
myPath.StrokeThickness = 5
Grid.SetColumn(myPath, 0)
Grid.SetRow(myPath, 0)
myGrid.Children.Add(myPath)
txt2.Text = "LayoutSlot is equal to " + LayoutInformation.GetLayoutSlot(txt1).ToString()
End Sub
版面配置系統
在最簡單的情況下,佈局是一個讓元素被調整大小、被定位並被繪製的遞迴系統。 更具體來說,版面配置描述了測量和排列元素集合成員的過程。 版面配置是一個需要大量運算的過程。 集合Children越大,必須進行的計算次數就越多。 您也可以根據擁有集合的 Panel 元素所定義的佈局行為,增加複雜性。 相較於更複雜的功能,例如 Grid,相對簡單的功能,例如 Canvas,效能明顯更好。
每次子元素變更其位置時,就有可能觸發版面配置系統的新階段。 因此,請務必了解可叫用配置系統的事件,因為不必要的叫用可能會導致不良的應用程式效能。 下列描述在叫用配置系統時所發生的程序。
一個子UIElement在版面配置的過程中首先會測量它的核心屬性。
定義在 FrameworkElement 上的調整大小屬性會被評估,例如 Width、Height 和 Margin。
在Panel中應用特定邏輯,例如 Dock 的排列方向或 Orientation 的堆疊。
測量所有子系之後,會排列內容。
Children 集合會繪製於螢幕上。
如果將其他 Children 新增至集合、套用 LayoutTransform,或是呼叫 UpdateLayout 方法,則會再次執行此流程。
下列各節會詳述定義此程序和其叫用方式。
測量和排列兒童
版面配置系統會為 Children 集合的每個成員完成兩個階段:測量階段和排列階段。 每個子項目Panel提供自己的MeasureOverride和ArrangeOverride方法來實現自己特定的版面配置行為。
在測量階段,會評估 Children 集合的每個成員。 此流程會從呼叫 Measure 方法開始。 此方法於父元素的實作中呼叫,不需要明確呼叫此方法也能進行版面配置。
首先,評估 UIElement 的原生大小屬性,例如 Clip 和 Visibility。 這會產生名為 constraintSize 的值,此值會傳遞至 MeasureCore。
其次,會處理 FrameworkElement 上定義的架構屬性,而這會影響 constraintSize 的值。 這些屬性通常會描述底層 UIElement (部分機器翻譯) 的調整大小特性,例如,其 Height (部分機器翻譯)、Width (部分機器翻譯)、Margin (部分機器翻譯) 和 Style (英文)。 所有這些屬性都可以變更顯示項目所需的空間。
MeasureOverride 接著使用 constraintSize 作為參數來呼叫。
備註
Height 和 Width 的屬性與 ActualHeight 和 ActualWidth 的屬性之間存有差異。 例如,ActualHeight 屬性是根據其他高度輸入和佈局系統所計算出的值。 此值由版面配置系統本身根據實際渲染過程所設定,因此可能會稍微落後於作為輸入變更基礎的屬性(例如 Height,例如高度)的設定值。
由於 ActualHeight 是計算出來的值,您應該注意,版面配置系統的各種作業可能會導致對其的多次或逐漸變化的報告。 配置系統可能會計算子項目所需的測量空間、父項目的條件約束,依此類推。
測量階段的最終目標是讓子物件判斷其 DesiredSize,這會在呼叫 MeasureCore 時發生。 DesiredSize 的值會由 Measure 儲存,以在內容排列階段使用。
此程式安排過程從呼叫Arrange方法開始。 在排列階段期間,父元素生成代表子元素界限的矩形。 此值會傳遞至 ArrangeCore 方法進行處理。
ArrangeCore (英文) 方法會評估子元素的 DesiredSize (英文),並評估可能影響元素轉譯大小的任何其他邊界。
ArrangeCore 會產生 arrangeSize,並以參數形式傳遞至 Panel 的 ArrangeOverride 方法。
ArrangeOverride 產生子物件的 finalSize。 最後,ArrangeCore 方法會執行最終評估偏移屬性(例如邊距和對齊方式),並將子元素放在其版面配置的位置內。 子系不需要 (通常也不會) 填入整個已配置的空間。 控制權接著會傳回到父 Panel,隨即完成版面配置流程。
面板元素與自訂佈局行為
WPF 包含一組衍生自 Panel 的元素。 這些 Panel 元素可啟用許多複雜的布局。 例如,使用 StackPanel 元素可以輕鬆地堆疊其他元素,而使用 Canvas 則可以實現更複雜且靈活的版面配置。
下表摘要說明可用的版面配置 Panel 元素。
| 面板名稱 | 說明 |
|---|---|
| Canvas | 定義一個區域,您可以在該區域內以相對於該區域的座標明確定位子元素。 |
| DockPanel | 定義一個區域,可供您在其中以水平或垂直方式相對排列子元素。 |
| Grid | 定義彈性的格線區域,由欄與列組成。 |
| StackPanel | 將子元素排成單一行,排列方向可以是水平或垂直。 |
| VirtualizingPanel | 提供一個框架,以供 Panel 元素將其子資料集合虛擬化。 這是抽象類。 |
| WrapPanel | 將子元素順序排列在由左至右的位置,當達到包含框的邊緣時,內容就會換行至下一行。 後續順序會依序由上而下或由右而左進行,視 Orientation 屬性的值而定。 |
對於需要使用不是任何預定義的 Panel 元素即可達成的版面配置的應用程式,可以藉由繼承 Panel 並覆寫 MeasureOverride 和 ArrangeOverride 方法來實現自訂的版面配置行為。
版面配置效能考量
布局是遞迴過程。 每次佈局系統的調用期間,Children 集合中的每個子元素都會被處理。 因此,不需要時,應該避免觸發配置系統。 下列考量可協助您達到更佳的效能。
請注意哪些屬性值變更將會由配置系統強制遞迴更新。
使用公用旗標,可標記其值可以初始化配置系統的相依性屬性。 AffectsMeasure 和 AffectsArrange 提供有關哪些屬性值變更將由版面配置系統強制執行遞迴更新的有用線索。 一般而言,任何可能影響元素邊界方塊大小的屬性都應該將 AffectsMeasure 標記設定為 true。 如需詳細資訊,請參閱相依性屬性概觀。
盡可能使用 RenderTransform 而非 LayoutTransform。
LayoutTransform 是影響使用者介面 (UI) 內容的非常有用的方法。 不過,如果轉換的效果不需要影響其他元素的位置,最好改為使用 RenderTransform,因為 RenderTransform 不會叫用版面配置系統。 LayoutTransform 應用其變換,並強制進行遞迴版面配置更新,以考慮受影響元素的新位置。
避免不必要地呼叫 UpdateLayout 。
該 UpdateLayout 方法會強制執行遞迴版面配置更新,但通常不需要。 除非您確定需要完整更新,否則請依賴佈局系統來呼叫此方法。
在處理大型 Children 集合時,請考慮使用 VirtualizingStackPanel,而非一般的 StackPanel。
透過將子集合虛擬化,VirtualizingStackPanel 只會將目前位於父檢視區域內的物件保留在記憶體中。 因此,在大部分情況下,都可以大幅改善效能。
子像素渲染和版面配置捨入
WPF 圖形系統使用與裝置無關的單位來啟用解析度和裝置獨立性。 每個裝置獨立像素都會依系統的 DPI 設定自動進行調整。 這讓 WPF 應用程式針對不同的 DPI 設定進行適當調整,並自動讓應用程式感知 DPI。
不過,這種 DPI 獨立性可能會因抗鋸齒處理而造成不規則的邊緣呈現。 如果邊緣位置落在裝置像素中間,而不是裝置像素之間,則這些成品一般會看起來模糊或具有半透明邊緣。 佈局系統提供一種方式,利用佈局捨入來調整這個問題。 版面配置捨入是配置系統在版面配置階段將任何非整數像素值四捨五入。
預設不啟用版面配置捨入。 若要啟用版面配置圓整,請在任何 FrameworkElement 上將 UseLayoutRounding 屬性設定為 true。 因為這是相依性屬性,所以值將會傳播到視覺樹狀結構中的所有子系。 若要為整個 UI 啟用佈局進位,請在根容器上將 UseLayoutRounding 設定為 true。 如需範例,請參閱 UseLayoutRounding。
後續步驟
了解如何測量和排列項目是了解版面配置的第一個步驟。 如需可用的Panel元素的詳細資訊,請參閱面板概觀。 若要進一步了解會影響版面配置的各種定位屬性,請參閱對齊、邊界和填補概觀。 當您準備好將其全都放置於輕量型應用程式中時,請參閱逐步解說:我的第一個 WPF 桌面應用程式 (部分機器翻譯)。
另請參閱
- FrameworkElement
- UIElement
- 面板概觀
- 對齊、邊界和內距概觀
- 版面配置與設計