WPF之布局流程

文章目錄

    • 1. 概述
    • 2. 布局元素的邊界框
    • 3. 布局系統原理
      • 3.1 布局流程時序圖
    • 4. 測量階段(Measure Phase)
      • 4.1 測量過程
      • 4.2 MeasureOverride方法
    • 5. 排列階段(Arrange Phase)
      • 5.1 排列過程
      • 5.2 ArrangeOverride方法
    • 6. 渲染階段(Render Phase)
    • 7. 布局事件
      • 7.1 主要布局事件
      • 7.2 布局事件示例
    • 8. 自定義面板示例
    • 9. 布局性能優化
      • 9.1 選擇合適的面板
      • 9.2 使用RenderTransform而非LayoutTransform
      • 9.3 避免不必要的UpdateLayout調用
      • 9.4 使用虛擬化
      • 9.5 使用布局舍入
    • 10. 總結
    • 參考鏈接

1. 概述

Windows Presentation Foundation (WPF) 的布局系統是WPF應用程序中的核心部分,它負責計算界面中每個元素的大小和位置,并最終呈現到屏幕上。理解WPF的布局流程對于創建高性能、響應迅速的用戶界面至關重要。

WPF布局系統是一個遞歸系統,它會自頂向下地處理視覺樹中的每個元素。布局過程主要包括三個階段:測量(Measure)、排列(Arrange)和渲染(Render)。除此之外,布局事件在整個流程中也扮演著重要角色。

2. 布局元素的邊界框

在討論WPF布局之前,我們需要了解元素邊界框的概念。在WPF中,每個元素都被定義在一個表示其邊界的矩形內,這個矩形稱為"布局槽(Layout Slot)"。布局槽的實際大小由布局系統在運行時根據屏幕大小、父屬性和元素本身的屬性(如邊框、寬度、高度、邊距和內邊距)計算得出。

當計算元素的布局屬性后,元素的最終可見區域稱為"布局剪輯(Layout Clip)"。可以使用LayoutInformation類來獲取元素的布局槽和布局剪輯信息。

// 獲取元素的布局槽
Rect layoutSlot = LayoutInformation.GetLayoutSlot(myElement);// 獲取元素的布局剪輯
Geometry clipGeometry = LayoutInformation.GetLayoutClip(myElement);

3. 布局系統原理

WPF布局系統的核心思想是"兩段式布局"。在兩段式布局中,父容器和子元素通過協商來確定每個元素的最終尺寸和位置。這個過程涉及到三種尺寸:

  1. 可用尺寸(Available Size): 父元素愿意給子元素的最大空間值。
  2. 期望尺寸(Desired Size): 子元素希望獲得的尺寸。
  3. 實際尺寸(Actual Size): 最終分配給子元素的尺寸。

這三個尺寸通常符合以下不等式:

期望尺寸(Desired Size) ≤ 實際尺寸(Actual Size) ≤ 可用尺寸(Available Size)

3.1 布局流程時序圖

父元素 子元素 開始布局過程 調用Measure(availableSize) MeasureCore處理 MeasureOverride計算期望尺寸 返回DesiredSize 調用Arrange(finalRect) ArrangeCore處理 ArrangeOverride安排內容 返回最終尺寸 繼續處理其他子元素 父元素 子元素

4. 測量階段(Measure Phase)

測量階段是布局流程的第一步,主要目的是確定每個元素希望獲得的大小。在這個階段,父元素會詢問每個子元素它需要多大的空間,子元素會計算并返回它的期望尺寸(DesiredSize)。

4.1 測量過程

測量過程從調用UIElement.Measure方法開始,這個方法會在父面板元素的實現中被調用,通常不需要顯式調用它。測量過程的大致步驟如下:

  1. 首先計算UIElement的基本屬性,如ClipVisibility,生成一個名為constraintSize的值并傳遞給MeasureCore
  2. 處理FrameworkElement上定義的框架屬性,如HeightWidthMarginStyle,這些屬性會影響constraintSize的值。
  3. 調用MeasureOverride方法,傳入constraintSize作為參數。
  4. 子元素確定自己的DesiredSize,并存儲以供排列階段使用。

4.2 MeasureOverride方法

MeasureOverride方法是FrameworkElement類的重要方法,當創建自定義控件或面板時,通常需要重寫這個方法來提供自定義的測量邏輯。

/// <summary>
/// 重寫MeasureOverride方法以自定義測量邏輯
/// </summary>
/// <param name="availableSize">父容器提供的可用尺寸</param>
/// <returns>元素期望的尺寸</returns>
protected override Size MeasureOverride(Size availableSize)
{// 定義期望的尺寸Size desiredSize = new Size();// 遍歷所有子元素進行測量foreach (UIElement child in this.Children){// 測量子元素child.Measure(availableSize);// 根據子元素的期望尺寸更新自身的期望尺寸// 這里的邏輯取決于面板的布局策略desiredSize.Width = Math.Max(desiredSize.Width, child.DesiredSize.Width);desiredSize.Height += child.DesiredSize.Height;}// 返回計算得到的期望尺寸return desiredSize;
}

5. 排列階段(Arrange Phase)

在測量階段完成后,每個元素都知道了自己期望的大小,接下來就進入了排列階段。排列階段的主要目的是確定每個元素的最終位置和大小。

5.1 排列過程

排列過程從調用UIElement.Arrange方法開始。在排列過程中,父面板元素會生成一個表示子元素邊界的矩形,這個值會傳遞給ArrangeCore方法處理。排列過程的大致步驟如下:

  1. ArrangeCore方法評估子元素的DesiredSize以及可能影響元素渲染大小的任何其他邊距。
  2. ArrangeCore生成一個arrangeSize,并作為參數傳遞給面板的ArrangeOverride方法。
  3. ArrangeOverride生成子元素的最終大小finalSize
  4. ArrangeCore方法執行偏移屬性(如邊距和對齊)的最終計算,并將子元素放在其布局槽內。

5.2 ArrangeOverride方法

ArrangeOverride方法是FrameworkElement類的另一個重要方法,用于自定義排列邏輯。

/// <summary>
/// 重寫ArrangeOverride方法以自定義排列邏輯
/// </summary>
/// <param name="finalSize">最終分配給元素的尺寸</param>
/// <returns>實際使用的尺寸</returns>
protected override Size ArrangeOverride(Size finalSize)
{// 初始位置double yPos = 0;// 遍歷所有子元素進行排列foreach (UIElement child in this.Children){// 計算子元素的位置和大小// 這里以垂直堆疊為例Rect rect = new Rect(0, yPos, finalSize.Width, child.DesiredSize.Height);// 排列子元素child.Arrange(rect);// 更新下一個子元素的垂直位置yPos += child.DesiredSize.Height;}// 返回最終使用的尺寸return finalSize;
}

6. 渲染階段(Render Phase)

渲染階段是布局流程的最后一步,它負責將測量和排列后的元素繪制到屏幕上。渲染過程由WPF的渲染引擎負責,通常開發者不需要直接干預這個過程。

渲染階段的主要特點:

  1. 異步執行: 渲染過程通常是異步的,與UI線程分離,以提高性能。
  2. 按需渲染: WPF只會渲染需要更新的部分,以減少不必要的計算。
  3. 硬件加速: WPF利用DirectX進行硬件加速渲染,提高圖形性能。

雖然開發者通常不需要直接操作渲染過程,但可以通過重寫OnRender方法來自定義控件的渲染。

/// <summary>
/// 重寫OnRender方法以自定義渲染邏輯
/// </summary>
/// <param name="drawingContext">繪圖上下文</param>
protected override void OnRender(DrawingContext drawingContext)
{// 調用基類的渲染方法base.OnRender(drawingContext);// 自定義繪制邏輯// 例如繪制一個矩形Rect rect = new Rect(0, 0, ActualWidth, ActualHeight);drawingContext.DrawRectangle(Brushes.LightBlue, new Pen(Brushes.Blue, 1), rect);
}

7. 布局事件

在布局過程中,WPF會觸發一系列事件,這些事件可以幫助開發者了解布局的過程并在適當的時機執行自定義邏輯。

7.1 主要布局事件

  1. LayoutUpdated: 當布局系統完成更新時觸發。
  2. SizeChanged: 當元素的實際大小改變時觸發。
  3. Loaded: 當元素被加載到視覺樹中并完成布局時觸發。

7.2 布局事件示例

public class CustomControl : Control
{public CustomControl(){// 訂閱布局事件this.Loaded += CustomControl_Loaded;this.SizeChanged += CustomControl_SizeChanged;this.LayoutUpdated += CustomControl_LayoutUpdated;}private void CustomControl_Loaded(object sender, RoutedEventArgs e){// 當控件加載完成時執行的邏輯Console.WriteLine("控件已加載完成");}private void CustomControl_SizeChanged(object sender, SizeChangedEventArgs e){// 當控件大小改變時執行的邏輯Console.WriteLine($"控件大小已改變: 舊尺寸={e.PreviousSize}, 新尺寸={e.NewSize}");}private void CustomControl_LayoutUpdated(object sender, EventArgs e){// 當布局更新時執行的邏輯Console.WriteLine("布局已更新");}
}

8. 自定義面板示例

下面是一個自定義面板的完整示例,它實現了一個簡單的"V"形布局,將子元素排列成一個"V"字形。

/// <summary>
/// 自定義V形布局面板
/// </summary>
public class VShapePanel : Panel
{/// <summary>/// 重寫測量方法,計算面板所需尺寸/// </summary>protected override Size MeasureOverride(Size availableSize){Size desiredSize = new Size();// 測量所有子元素foreach (UIElement child in this.InternalChildren){// 讓子元素自行測量所需大小child.Measure(availableSize);// 更新面板所需的寬度和高度desiredSize.Width = Math.Max(desiredSize.Width, child.DesiredSize.Width * 2);desiredSize.Height += child.DesiredSize.Height / 2;}// 確保V形底部有足夠空間if (this.InternalChildren.Count > 0){var lastChild = this.InternalChildren[this.InternalChildren.Count - 1];desiredSize.Height += lastChild.DesiredSize.Height / 2;}return desiredSize;}/// <summary>/// 重寫排列方法,將子元素排列成V形/// </summary>protected override Size ArrangeOverride(Size finalSize){if (this.InternalChildren.Count == 0)return finalSize;int middleIndex = this.InternalChildren.Count / 2;double centerX = finalSize.Width / 2;double currentY = 0;// 排列V形左側的元素for (int i = 0; i <= middleIndex; i++){UIElement child = this.InternalChildren[i];double offsetX = centerX - (middleIndex - i) * (child.DesiredSize.Width * 0.75);// 排列子元素child.Arrange(new Rect(offsetX, currentY, child.DesiredSize.Width, child.DesiredSize.Height));currentY += child.DesiredSize.Height / 2;}// 排列V形右側的元素currentY = 0;for (int i = 0; i < middleIndex; i++){UIElement child = this.InternalChildren[i];UIElement symmetricChild = this.InternalChildren[this.InternalChildren.Count - 1 - i];double offsetX = centerX + (middleIndex - i) * (symmetricChild.DesiredSize.Width * 0.75);// 排列對稱元素symmetricChild.Arrange(new Rect(offsetX, currentY, symmetricChild.DesiredSize.Width, symmetricChild.DesiredSize.Height));currentY += child.DesiredSize.Height / 2;}return finalSize;}
}

使用示例:

<Window x:Class="WpfApp.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp"Title="VShape Layout Demo" Height="450" Width="800"><local:VShapePanel><Button Content="按鈕1" Width="100" Height="40"/><Button Content="按鈕2" Width="100" Height="40"/><Button Content="按鈕3" Width="100" Height="40"/><Button Content="按鈕4" Width="100" Height="40"/><Button Content="按鈕5" Width="100" Height="40"/></local:VShapePanel>
</Window>

9. 布局性能優化

布局是一個遞歸過程,每次調用布局系統時,都會處理子元素集合中的每個子元素。因此,應該避免不必要地觸發布局系統。以下是一些性能優化建議:

9.1 選擇合適的面板

不同類型的面板有不同的布局復雜度。例如,Canvas的布局算法非常簡單,而Grid則復雜得多。如果不需要Grid提供的功能,應該使用性能開銷較小的替代方案,如Canvas或自定義面板。

9.2 使用RenderTransform而非LayoutTransform

LayoutTransform會影響布局系統,而RenderTransform不會。如果變換不需要影響其他元素的位置,最好使用RenderTransform,因為它不會調用布局系統。

9.3 避免不必要的UpdateLayout調用

UpdateLayout方法會強制執行遞歸布局更新,通常是不必要的。除非確定需要完整更新,否則應該依賴布局系統自動調用此方法。

9.4 使用虛擬化

處理大型集合時,考慮使用VirtualizingStackPanel代替常規的StackPanel。通過虛擬化子集合,VirtualizingStackPanel只在內存中保留當前位于父級視區內的對象,從而顯著提高性能。

<ListBox VirtualizingPanel.IsVirtualizing="True"VirtualizingPanel.VirtualizationMode="Recycling"ScrollViewer.IsDeferredScrollingEnabled="True"><ListBox.ItemsPanel><ItemsPanelTemplate><VirtualizingStackPanel/></ItemsPanelTemplate></ListBox.ItemsPanel><!-- 列表項 -->
</ListBox>

9.5 使用布局舍入

WPF圖形系統使用設備無關單位來實現分辨率和設備獨立性。每個設備獨立像素會根據系統的DPI設置自動縮放。但這種DPI獨立性可能會因為抗鋸齒而導致不規則的邊緣渲染。

布局舍入是WPF提供的一種解決方案,它會在布局過程中將非整數像素值舍入為整數。默認情況下,布局舍入是禁用的,可以通過設置UseLayoutRounding屬性為true來啟用它。

<!-- 為整個UI啟用布局舍入 -->
<Window x:Class="WpfApp.MainWindow"UseLayoutRounding="True"...><!-- 窗口內容 -->
</Window>

10. 總結

WPF的布局系統是一個復雜而強大的機制,它通過測量、排列和渲染三個階段來確定UI元素的大小和位置。理解這個過程對于創建高效、響應迅速的WPF應用程序至關重要。

通過重寫MeasureOverrideArrangeOverride方法,開發者可以創建自定義布局行為,滿足特定的UI需求。同時,了解并應用布局性能優化技巧,可以避免不必要的布局計算,提高應用程序的整體性能。

參考鏈接

  1. WPF Layout System - Microsoft Docs
  2. Optimizing Performance: Layout and Design - Microsoft Docs
  3. Understanding WPF Layout - CodeProject
  4. WPF 布局原理 - 博客園

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/80279.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/80279.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/80279.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

uniapp|獲取當前用戶定位、與系統設定位置計算相隔米數、實現打卡簽到(可自定義設定位置、位置有效范圍米數)

基于UniApp闡述移動應用開發中定位功能的實現全流程,涵蓋實時定位獲取、動態距離計算與自定義位置、有效范圍設定等功能。文章提供完整的代碼示例與適配方案,適用于社交簽到、課堂教室打卡等場景。 目錄 引言定位功能在移動應用中的價值(社交、導航、O2O等場景)UniApp跨平臺…

Yii2.0 模型規則(rules)詳解

一、基本語法結構 public function rules() {return [// 規則1[[attribute1, attribute2], validator, options > value, ...],// 規則2[attribute, validator, options > value, ...],// 規則3...]; }二、規則類型分類 1、核心驗證器&#xff08;內置驗證器&#xff0…

數據結構(三)——棧和隊列

一、棧和隊列的定義和特點 棧&#xff1a;受約束的線性表&#xff0c;只允許棧頂元素入棧和出棧 對棧來說&#xff0c;表尾端稱為棧頂&#xff0c;表頭端稱為棧底&#xff0c;不含元素的空表稱為空棧 先進后出&#xff0c;后進先出 隊列&#xff1a;受約束的線性表&#xff0…

SQL Server 存儲過程開發三層結構規范

以下是《SQL Server 存儲過程開發三層結構規范》的正式文檔結構&#xff0c;適用于企業級數據庫應用開發場景&#xff0c;有助于團隊協作、代碼審查與自動化運維&#xff1a; &#x1f4d8; SQL Server 存儲過程開發三層結構規范 一、架構設計總覽 三層結構簡介 層級命名約定…

接上篇,解決FramePack啟動報錯:“httpx.ReadError: [WinError 10054] 遠程主機強迫關閉了一個現有的連接。“的問題

#工作記錄 FramePack部署&#xff08;從PyCharm解釋器創建和使用開始&#xff09;保姆級教程-CSDN博客 上篇我們記錄到FramePack從克隆到啟動調試的保姆級教程&#xff0c;關于啟動時會報以下錯誤的問題&#xff0c;已作出解決&#xff1a; 報錯摘錄&#xff1a; (.venv) PS F…

ping_test_parallel.sh 并行網絡掃描腳本

并行網絡掃描腳本分析&#xff1a;提高網絡探測效率 引言腳本概述核心代碼分析顏色定義與初始化并行處理機制并行執行與進程控制結果處理與統計 技術亮點性能分析結論附錄&#xff1a;完整腳本 引言 在網絡管理和運維過程中&#xff0c;快速檢測網段內主機的在線狀態是一項常見…

leetcode 3342. 到達最后一個房間的最少時間 II 中等

有一個地窖&#xff0c;地窖中有 n x m 個房間&#xff0c;它們呈網格狀排布。 給你一個大小為 n x m 的二維數組 moveTime &#xff0c;其中 moveTime[i][j] 表示在這個時刻 以后 你才可以 開始 往這個房間 移動 。你在時刻 t 0 時從房間 (0, 0) 出發&#xff0c;每次可以移…

關于vue-office在vue3工程中的引用報錯問題

在vue3項目工程中&#xff0c;根據vue-office文檔在vue2中的引用&#xff1a; //引入VueOfficeDocx組件 相關樣式import VueOfficeDocx from vue-office/docx;import vue-office/docx/lib/index.css; 報錯信息&#xff1a; [plugin:vite:import-analysis] Failed to resolve …

【macOS常用快捷鍵】

以下是 macOS 最常用快捷鍵列表&#xff0c;按使用頻率由高到低分類整理&#xff0c;涵蓋日常操作、效率工具及系統控制&#xff0c;助你快速提升使用效率&#xff1a; 一、基礎高頻操作 快捷鍵功能說明Command C復制選中內容Command V粘貼Command X剪切Command Z撤銷上一…

mdadm 報錯: buffer overflow detected

最近跑 blktest (https://github.com/osandov/blktests) 時發現 md/001 的測試失敗了 單獨執行&#xff0c;最后定位到是 mdadm 命令報錯: buffer overflow detected 這個 bug 目前已經修復: https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git/commit/?id827e1870f3205…

查看jdk是否安裝并且配置成功?(Android studio安裝前的準備)

WinR輸入cmd打開命令提示窗口 輸入命令 java -version 回車顯示如下&#xff1a;

STM32智能刷卡消費系統(uC/OS-III)

一、項目概述與開發背景 本系統是一款基于STM32微控制器的智能刷卡消費終端&#xff0c;集成RFID識別、OLED顯示、Flash存儲、藍牙通信等核心模塊。項目采用uC/OS-III實時操作系統實現多任務并發處理&#xff0c;適用于校園一卡通、企業食堂等小額支付場景。系統支持定額扣款、…

[人機交互]以用戶為中心的交互設計

一.以用戶為中心設計的兩個特征 ? 理解和指定產品的使用上下文 &#xff0c;并用于指導設計 ? 用戶參與式開發 ? 參與 評估研究 &#xff08;第十 — 十四章&#xff09; ? 參與 設計過程 &#xff1a;用戶作為合作設計人員 二.用戶參與設計的重要性 ? 需求的獲取主要來源…

Abaqus學習筆記

目錄 Abaqus介紹 學習資源 ?編輯Abaqus/CAE abaqus下載安裝 abaqus基本操作 Abaqus啟動 新建模型 ?編輯 ?編輯修改界面背景 ?編輯?編輯結果信息的顯示與否 ?編輯計算結果信息字體設置 ?編輯允許多繪圖狀態 單位量綱 視圖操作 事前說明 ODB文件 本構關系…

論壇系統開發(0-1) (上 前置知識介紹)

前置知識 1. 軟件的生命周期 生命周期: 對事物進行定義(描述) -> 創建 -> 使用 -> 銷毀的過程 軟件?命周期中以劃分為可?性研究、需求分析、概要設計、詳細設計、實現、組裝(集成)測試、確認測試、使?、維護、退役10個階段&#xff0c;如下圖&#xff1a; a. 可…

架構師面試(三十七):監控系統架構模式

題目 監控是在產品生命周期的運維環節&#xff0c;能對產品的關鍵指標數據進行【實時跟蹤】并對異常數據進行【實時報警】。 一句話描述&#xff0c;監控系統可以幫我們【主動預防和發現】業務系統中的問題。 我們常說&#xff0c;監控系統是 “糧草”&#xff0c;業務系統是…

【面試 · 二】JS個別重點整理

目錄 數組方法 字符串方法 遍歷 es6 構造函數及原型 原型鏈 this指向 修改 vue事件循環Event Loop FormData 數組方法 改變原數組&#xff1a;push、pop、shift、unshift、sort、splice、reverse不改變原屬組&#xff1a;concat、join、map、forEach、filter、slice …

深度學習里程碑:AlexNet 架構解析與核心技術詳解

內容摘要 本文深度解析2012年ILSVRC冠軍模型AlexNet&#xff0c;全面闡述其在深度學習發展中的關鍵突破。從模型架構出發&#xff0c;詳細解析卷積層、池化層、全連接層的數學原理&#xff0c;重點分析ReLU激活函數、LRN局部歸一化、重疊池化等創新技術的數學表達與工程價值。…

第5章 深度學習和卷積神經網絡

深度學習是人工智能的一種實現方法。本章我們將考察作為深度學習的代表的卷積神經網絡的數學結構。 5-1小惡魔來講解卷積神經網絡的結構 深度學習是重疊了很多層的隱藏層&#xff08;中間層&#xff09;的神經網絡。這樣的神經網絡使隱藏層具有一定的結構&#xff0c;從而更加…

JVM——JVM是怎么實現invokedynamic的?

JVM是怎么實現invokedynamic的&#xff1f; 在Java 7引入invokedynamic之前&#xff0c;Java虛擬機&#xff08;JVM&#xff09;在方法調用方面相對較為“僵化”。傳統的Java方法調用主要依賴于invokestatic、invokespecial、invokevirtual和invokeinterface這四條指令&#x…