原文: Visual->UIElement->FrameworkElement,帶來更多功能的同時也帶來了更多的限制
版權聲明:本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定進行許可。歡迎轉載、使用、重新釋出,但務必保留文章署名呂毅(包含連結:http://blog.csdn.net/wpwalter/),不得用于商業目的,基于本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯系([email protected])。 https://blog.csdn.net/WPwalter/article/details/78619688
在 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;
}
隻會在
ClipToBounds
設定為
true
的時候進行矩形切割。
然而
FrameworkElement
的切掉邏輯就複雜多了,鑒于有上百行,就隻貼對外連結接
FrameworkElement.GetLayoutClip。其處理了各種布局、變換過程中的情況。
由于
FrameworkElement
的出現是為了讓我們程式設計中像對待一個有固定尺寸的物體一樣,是以也在切除上模拟了這樣的空間有限的效果。
如果希望不被切掉,有兩種方法修正:
- 確定布局的時候所需尺寸不大于可用尺寸(一點也不能大于,就算是
精度問題導緻的細微偏大都不行)double
-
傳回的尺寸不大于參數傳入的尺寸MeasureOverride
-
ArrangeOverride
-
- 重寫
方法,并傳回 null(或者寫成GetLayoutClip
那樣)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