天天看點

Visual->UIElement->FrameworkElement,帶來更多功能的同時也帶來了更多的限制

原文: 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

。正是這些屬性的存在,讓我們可以直覺地給元素指定尺寸——想設定多少就設定多少。

然而……當你把寬或高設定得比父容器允許的最大寬高還要大的時候呢?我們會發現,控件被“切掉”了。

Visual->UIElement->FrameworkElement,帶來更多功能的同時也帶來了更多的限制

▲ 被切掉的橢圓

然而,因布局被“切掉”這一特性也是來自于

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

的出現是為了讓我們程式設計中像對待一個有固定尺寸的物體一樣,是以也在切除上模拟了這樣的空間有限的效果。

如果希望不被切掉,有兩種方法修正:

  1. 確定布局的時候所需尺寸不大于可用尺寸(一點也不能大于,就算是

    double

    精度問題導緻的細微偏大都不行)
    • MeasureOverride

      傳回的尺寸不大于參數傳入的尺寸
    • ArrangeOverride

  2. 重寫

    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

瞬間變成了幾個、幾十個。一般情況下這根本不會是性能瓶頸,然而當這種控件會一次性産生幾十個甚至數百個(例如表格)的時候,這種瓶頸就會非常明顯。

總結容易出現了解偏差的幾個點

  1. Width

    Height

    屬性其實隻是為布局過程中的計算進行限制而已,跟

    MinWidth

    MinHeight

    MaxWidth

    MaxHeight

    沒有差別,并不直接決定實際尺寸。
  2. 如果發現元素布局中被切掉了,這并不是不可避免的問題;因為切掉是

    FrameworkElement

    為我們引入的特性,不喜歡可以随時關掉。
  3. 微軟對于子類重寫核心邏輯的方法喜歡使用

    Core

    字尾,布局中用了

    Override

    隻是因為名字被占用了。
  4. Visual

    就可以計算與螢幕坐标之間的轉換。
  5. 模闆(

    Template

    )會額外産生很多個

    Visual

    ,有可能會成為性能瓶頸。

參考資料