Visual->UIElement->FrameworkElement,帶來更多功能的同時也帶來了更多的限制
在 WPF 或 UWP 中,我們平時開發所遇到的那些 UI 控制元件或元件,都直接或間接繼承自 Framework
。例如:Grid
、StackPanel
、Canvas
、Border
、Image
、Button
、Slider
。我們總會自然而然地認為這些控制元件都是有大小的,它們會在合適的位置顯示自己,通常不會超出去。但是,FrameworkElement
甚至是 Control
用得久了,都開始忘記 Visual
、UIElement
帶給我們的那些自由。
閱讀本文將瞭解我們熟知的那些功能以及限制的由來,讓我們站在限制之外再來審視 WPF 的視覺化樹,再來看清 WPF 各種控制元件屬性的本質。
寬度和高度
如果問 Width
/Height
屬性來自誰,只要在 WPF 和 UWP 裡混了一點兒時間都會知道——FrameworkElement
。隨著 FrameworkElement
的寬高屬性一起帶來的還有 ActualWidth
、ActualHeight
、MinWidth
、MinHeight
、MaxWidth
、MaxHeight
。正是這些屬性的存在,讓我們可以直觀地給元素指定尺寸——想設定多少就設定多少。
然而……當你把寬或高設定得比父容器允許的最大寬高還要大的時候呢?我們會發現,控制元件被“切掉”了。
▲ 被切掉的橢圓
然而,因佈局被“切掉”這一特性也是來自於 FrameworkElement
UIElement
佈局時即便空間不夠也不會故意去將超出邊界的部分切掉,這一點從其原始碼就能得到證明:
/// <summary> /// This method supplies an additional (to the <seealso cref="Clip"/> property) clip geometry /// that is used to intersect Clip in case if <seealso cref="ClipToBounds"/> property is set to "true". /// Typcally, this is a size of layout space given to the UIElement. /// </summary> /// <returns>Geometry to use as additional clip if ClipToBounds=true</returns> protected virtual Geometry GetLayoutClip(Size layoutSlotSize) { if(ClipToBounds) { RectangleGeometry rect = new RectangleGeometry(new Rect(RenderSize)); rect.Freeze(); return rect; } else return null; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
只會在 ClipToBounds
設定為 true
的時候進行矩形切割。
然而 FrameworkElement
的切掉邏輯就複雜多了,鑑於有上百行,就只貼出連結 FrameworkElement.GetLayoutClip。其處理了各種佈局、變換過程中的情況。
由於 FrameworkElement
的出現是為了讓我們程式設計中像對待一個有固定尺寸的物體一樣,所以也在切除上模擬了這樣的空間有限的效果。
如果希望不被切掉,有兩種方法修正:
- 確保佈局的時候所需尺寸不大於可用尺寸(一點也不能大於,就算是
double
精度問題導致的細微偏大都不行)MeasureOverride
返回的尺寸不大於引數傳入的尺寸ArrangeOverride
返回的尺寸不大於引數傳入的尺寸
- 重寫
GetLayoutClip
方法,並返回 null(或者寫成UIElement
那樣)
佈局系統
提及 MeasureOverride
、ArrangeOverride
,大家都會認為這是 WPF 佈局系統給我們提供的兩個可供重寫的方法。然而,這兩個方法其實也是 FrameworkElement
才提供的。
真正佈局的方法是 Measure
和 Arrange
,而可供重寫的方法是 MeasureCore
、ArrangeCore
。這兩組方法均來自於 UIElement
,而佈局系統其實是 UIElement
引入的。
那麼 FrameworkElement
做了什麼呢?它密封了 MeasureCore
、ArrangeCore
這兩個佈局的重寫方法,以便能夠處理 Width
、Height
、MinWidth
、MinHeight
、MaxWidth
、MaxHeight
、Margin
這些屬性對佈局的影響。
你覺得 Width
、Height
屬性是元素的最終寬高嗎?我們在 寬度和高度 一節中已經說了不是,前面一段也說了不是——它們真的只是佈局屬性!然而,這真的很容易形成誤解!Width``Height
屬性其實和 MinWidth``MinHeight
、MaxWidth``MaxHeight
是完全一樣的用途,只是在佈局過程中為計算最終尺寸提供的佈局限制而已。只不過 MinWidth``MinHeight
、MaxWidth``MaxHeight
用大於和小於進行尺寸的限制,而 Width``Height
用等於進行尺寸的限制。最終的尺寸依然是 ActualWidth``ActualHeight
,而這個值跟 RenderSize
其實是一個意思,因為內部獲取的就是 RenderSize
。
值得注意的是,ActualWidth``ActualHeight
與 RenderSize
一樣,是佈局結束後才會更新的,開發中需要如果修改了屬性立即獲取這些值其實必然是舊的,拿這些值進行計算會造成錯誤的尺寸資料。
順便吐槽一下:其實微軟是喜歡用 Core
來作為子類重寫方法的字尾的,比如 Freezable
、EasingFunction
都是用 Core
字尾來處理重寫。Override
字尾純屬是因為 UIElement
把這個名字用了而已。
螢幕互動
UIElement
中存在著佈局計算,FrameworkElement
中存在著帶限制的佈局計算,這很容易讓人以為螢幕相關的座標計算會存在於 UIElement
或者 FrameworkElement
中。
然而其實 UIElement
或者 FrameworkElement
只涉及到控制元件之間的座標計算(TranslatePoint
),真正涉及到螢幕座標的轉換是位於 Visual
中的,典型的是這幾個:
TransformToAncestor
TransformToDescendant
TransformToVisual
PointFromScreen
PointToScreen
所以其實如果希望做出非常輕量級的高效能 UI,繼承自 Visual
也是一個大膽的選擇。當然,真正遇到瓶頸的時候,繼承自 Visual
也解決不了多少問題。
樣式和模板
FrameworkElement
開始有了樣式(Style
),Control
開始有了模板(Template
)。而模板極大地方便了樣式定製的同時,也造成了強大的效能開銷,因為本來的一個 Visual
瞬間變成了幾個、幾十個。一般情況下這根本不會是效能瓶頸,然而當這種控制元件會一次性產生幾十個甚至數百個(例如表格)的時候,這種瓶頸就會非常明顯。
總結容易出現理解偏差的幾個點
Width
和Height
屬性其實只是為佈局過程中的計算進行限制而已,跟MinWidth
、MinHeight
、MaxWidth
、MaxHeight
沒有區別,並不直接決定實際尺寸。- 如果發現元素佈局中被切掉了,這並不是不可避免的問題;因為切掉是
FrameworkElement
為我們引入的特性,不喜歡可以隨時關掉。 - 微軟對於子類重寫核心邏輯的方法喜歡使用
Core
字尾,佈局中用了Override
只是因為名字被佔用了。 Visual
就可以計算與螢幕座標之間的轉換。- 模板(
Template
)會額外產生很多個Visual
,有可能會成為效能瓶頸。
參考資料
--------------------- 本文來自 walter lv 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/WPwalter/article/details/78619688?utm_source=copy