WPF:為什麼需要Measure和Arrange兩步?

來源:互聯網
上載者:User

目錄

  • 溫習Measure和Arrange過程
  • 現有控制項對Measure和Arrange的使用
  • 自訂控制項示範Measure和Arrange

 

 

返回目錄
溫習Measure和Arrange過程

首先UIElement.Measure的參數是可用的空間(Size對象),這個空間通常代表著父控制項留給你顯示的可用空間,接著UIElement內部的MeasureCore會被調用,該方法會去決定調用MeasureOverride的大小參數。具體過程是先判斷大小屬性值是否被顯示設定,如果是的話,直接使用設定的值,這就是為什麼如果你強行設定一個控制項的Width和Height後,它總會保持設定的大小(當然如果超出了父控制項的規劃大小,WPF會根據IsClipToBounds屬性來決定是否裁剪它)。當然如果沒有被顯示設定,那麼MeasureCore會根據可用大小和當前控制項的屬性(比如Margin)來決定最終傳入MeasureOverride的可用大小參數。

 

接著MeasureOverride被調用,這個可以被子控制項改寫,返回的只會影響UIElement.DesiredSize屬性。

這個DesiredSize是計算後的包括Margin的結果,而且被三個條件所制約(優先順序由上到下):

  • Measure中傳入的可用大小參數
  • 顯示設定的屬性大小
  • MeasureOverride返回的大小

 

讓我們做一些樣本:

<Grid Width="200" Height="200">

    <TextBlock Margin="50">A</TextBlock>

</Grid>

 

顯然LayoutSlot大小為200*200。如果我們把TextBlock的Width和Height都設定成300,TextBlock的DesiredSize還會是200*200,顯然他被Measure中的可用大小最先制約。

 

如果第一條限制通過的話,那麼後續限制會被應用,比如我們把TextBlock的大小設定成10*10,那麼此時TextBlock的DesiredSize會成為:110*100。此時DesiredSize是控制項顯示設定的大小加上Margin大小。10+50*2=110。

 

最後如果TextBlock的大小不被設定,那麼DesiredSize就會使MeasureOverride的結果加上Margin,在我這裡結果是:107.74*115.96。

 

 

Arrange的過程則是決定UIElement.RenderSize屬性,整個過程和Measure類似但有不一樣的地方。首先,傳入Arrange的參數是LayoutSlot的位置和大小,是Rect對象。接著ArrangeCore做類似MeasureCore的工作去決定傳入ArrangeOverride大小參數,這個參數是Size,這個Size代表著最終需要顯示的可用大小。

 

ArrangeOverride返回的大小會影響RenderSize,但是注意不同於MeasureOverride那樣,ArrangeOverride影響的RenderSize不會受到Arrange方法的參數的限制,也就是說RenderSize只會受到顯示設定的屬性大小和ArrangeOverride傳回值這兩個限制的制約。至於MeasureOverride和ArrangeOverride的返回結果都要受到顯示設定的屬性大小影響,這個也是情理之中的。想象一下,如果你設定了元素的Width和Height但是最終他有可能不會按照規定大小去顯示,那將是多麼瘋狂的一件事啊。

 

 

返回目錄
現有控制項對Measure和Arrange的使用

為什麼要用Measure和Arrange兩個過程?我們從現有控制項的執行就已找到諸多答案。

 

Canvas是基於座標的控制項容器,因此他不會因為自身的大小而限制子容器的大小。看這樣一個代碼:

<Canvas Width="30">

    <Button Width="300" Height="50"/>

</Canvas>

 

VS設計器很給力的顯示出這個複雜情況:

 

Canvas只有30寬度,但是Button則需要300,同時Canvas不應該限制Button大小。那麼此時在Measure階段,Canvas並沒有把MeasureOverride中的父控制項可用大小傳給Button,而是用Double.PositiveInfinity(代表無窮大)作為參數調用Button的Measure方法。然後Button設定好了自己的DesiredSize後,Canvas在Arrange過程中同樣沒有傳入自己ArrangeOverride的參數,而是直接把Button的DesiredSize作為Button的Arrange方法參數,這樣滿足Button的任何大小要求。

 

類似的控制項還有ScrollViewer。

 

還有Canvas的MeasureOverride總是返回0*0。也就是說如果你沒有直接設定Canvas的大小的話,他的DesiredSize總會是0*0。因此如果你放在StackPanel中,他不會被顯示,因為StackPanel會根據方向的不同只顯示控制項DesiredSize(另一個方向無限制),顯然0*0的DesiredSize會使Canvas不可見。如果你把Canvas放在Grid中,那麼Canvas的大小就是Grid所給的大小,因為Grid會向已有大小都交給子成員,如果你把Canvas放在ScrollViewer中,那麼Canvas也會無限大,因為ScrollViewer本身就是無限大。

也可以通過改寫MeasureOverride來建立一個DesiredSize返回最小值的Canvas,可以參考這篇文章:WPF:一個估量最小大小的Canvas

 

還有TextBlock總會已單行的形式延伸,只有設定了TextWrapping屬性後,TextBlock才會根據父控制項的可用空間限制來執行換行規劃。

 

這些成果都是Measure和Arrange配合的結果。

 

當然絕大多數情況,如果你不是寫自訂的Panel控制項的話,是不需要太多關心Measure和Arrange(這也使許多人都不是很瞭解Measure和Arrange)。同時WPF的諸多內建類型都有MeasureOverride和ArrangeOverride的執行,可以參考這篇文章:

WPF中內建類型的MeasureOverride和ArrangeOverride表現

 

比如Control類型的執行都是對第一個Visual Child進行測量。那麼我們可以直接建立一個類型繼承自Control,然後把一個Visual加入到VisualChildren中,不用改寫MeasureOverride和ArrangeOverride,一個顯示另一個控制項的自訂Control就實現了:

class ContentButton : Control

{

    Button btn;

 

    public ContentButton()

    {

        btn = new Button() { Content = "Mgen" };

        base.AddVisualChild(btn);

    }

 

    protected override Visual GetVisualChild(int index)

    {

        return btn;

    }

 

    protected override int VisualChildrenCount

    {

        get

        {

            return 1;

        }

    }

}

 

把這個自訂控制項放在StackPanel中,背後的Button會直接顯示出來:

 

 

 

返回目錄
自訂控制項示範Measure和Arrange

下面自訂一個控制項示範有趣的Measure和Arrange。

我們建立一個在MeasureOverride中總返回50*50。然後在Arrange中總返回100*100的控制項:

class MyControl : Control

{

    protected override void OnRender(DrawingContext drawingContext)

    {

        base.OnRender(drawingContext);

        drawingContext.DrawRectangle(Brushes.Red, null, new Rect(new Point(), RenderSize));

    }

 

    protected override Size ArrangeOverride(Size arrangeBounds)

    {

        base.ArrangeOverride(arrangeBounds);

        return new Size(100, 100);

    }

 

    protected override Size MeasureOverride(Size constraint)

    {

        base.MeasureOverride(constraint);

        return new Size(50, 50);

    }

}

 

在StackPanel中,控制項會被顯示成100*50的。因為橫向StackPanel不做大小限制,所以DesiredSize被應用,而縱向對大小進行限制,RenderSize被應用。

在Grid或者其他不限制大小的容器中,大小會是100*100的,同時控制項不會根據可用控制項的改變而放大或者縮小,因為ArrangeOverride沒有使用arrangeBounds參數,而總是返回100*100的Size。

 

 

還可以建立一個縱向的StackPanel,但是不嘗試展開子成員的橫向寬度,效果如下:

 

代碼:

class MyPanel : Panel

{

    protected override Size MeasureOverride(Size availableSize)

    {

        var retSize = new Size();

        foreach (UIElement ui in InternalChildren)

        {

            ui.Measure(new Size(availableSize.Width, availableSize.Height));

            retSize.Height += ui.DesiredSize.Height;

            retSize.Width = Math.Max(retSize.Width, ui.DesiredSize.Width);

        }

        return retSize;

    }

 

    protected override Size ArrangeOverride(Size finalSize)

    {

        var next = new Point();

        foreach (UIElement ui in InternalChildren)

        {

            ui.Arrange(new Rect(next, ui.DesiredSize));

            next.Y += ui.RenderSize.Height;

 

        }

        return finalSize;

    }

}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.