WPF/Silverlight Layout 系统概述——Measure(转)
前言
在WPF/Silverlight當中,如果已經存在的Element無法滿足你特殊的需求,你可能想自定義Element,那么就有可能會面臨重寫MeasureOverride和ArrangeOverride兩個方法,而這兩個方法是WPF/SL的Layout系統提供給用戶的自定義接口,因此,理解Layout系統的工作機制,對自定義Element是非常有必要的。那么,究竟WPF/SL的Layout系統是怎么工作的呢?接下來,我簡單的描述一下,然后,在后面的章節具體分析。
簡單來說,WPF的Layout系統是一個遞歸系統,他有兩個子過程,總是以調用父元素的Measure方法開始,以調用Ararnge方法結束,而進入每個子過程之后,父元素又會調用孩子元素的Measure,完成后,又調用孩子元素的Arrange方法,這樣一直遞歸下去。而對兩個子過程的一次調用,可以看作是一次會話,可以理解為下圖所示:
?
這個會話可以用下面一段話描述:
子過程1: 父根據自己的策略給孩子一個availableSize,并發起對話,通過調用孩子的Measure(availableSize)方法,詢問孩子:你想要多大的空間顯示自己?孩子接到詢問后,根據父給的availableSize以及自己的一些限制,比如Margin,Width,等等,孩子回答:我想要XXX大小的空間。父拿到孩子給的期望的空間大小后,根據自己的策略開始真正給孩子分配空間,就進入第二個子過程。
子過程2: 父拿到孩子的期望空間后,再根據自己的情況,決定給孩子分配finalRect大小的矩形區域,然后他發起對話,調用孩子的Arrange(finalRect)給孩子說:我給你了finalRect這么大的空間。孩子拿到這個大小后,會去布置它的內容,并且布置完成后,會告訴父:其實我用了XXX大小的空間來繪制我自己的內容。父知道后,什么也沒說,還是按照分配給他的finalRect去安置孩子,如果孩子最終繪制的區域大于這個區域,就被父裁剪了。Layout過程完成。
通過上面兩個子過程的理解,或多或少對WPF的Layout系統有個初步的了解,接下來的章節,我具體描述Measure過程和Arrange過程具體做了哪些事情,幫助你跟深入的理解Layout系統。
?
?
預設條件?
通過下面的一個預設場景,我們來展開Layout系統的講解。
假定:我們需要自定義一個Panel,類型為 *MyPanel* ,MyPanel的父為 *MyPanelParent* ,也是一個Panel;MyPanel的孩子為 *MyPanelChild* ,也是一個Panel。
切入點1:重寫MyPanelParent的MeasureOverride()和ArrangeOverride(),研究父如何影響孩子MyPanel的Layout;
切入點2:重寫MyPanel.MeasureOverride()和ArrangeOverride方法,研究自身有哪些屬性影響MyPanel的Layout,以及重寫這兩個方法時應該注意的點;
注意:后面的研究,我只基于Element的Width,也就是水平方向的維度,所有的數據都是只設置水平方向的,垂直方向設置的跟水平方向一致,但不做描述。
?
Measure過程概述?
1.?普通基類屬性對Measure過程的影響
請看下面的一些設置:
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1"><Canvas><my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Green" Canvas.Left="10" Canvas.Top="10"><my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" MinWidth="150" Width="200" MaxWidth="250"/><my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" Width="200" MaxWidth="250"/></my:MyPanelParent></Canvas> </Window> public class MyPanelParent:Panel{protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize){foreach (UIElement item in this.InternalChildren){item.Measure(new Size(120, 120));//這里是入口}return availableSize;}protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize){double x = 0;foreach (UIElement item in this.InternalChildren){item.Arrange(new Rect(x, 0, item.DesiredSize.Width, item.DesiredSize.Height));x += item.DesiredSize.Width;}return finalSize;}}public class MyPanel : Panel{protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize){foreach (UIElement item in this.InternalChildren){item.Measure(availableSize);}return new Size(50, 50);//MyPanel 返回它期望的大小}protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize){double xCordinate = 0;foreach (UIElement item in this.InternalChildren){item.Arrange(new Rect(new Point(xCordinate, 0), item.DesiredSize));xCordinate += item.DesiredSize.Width;}return finalSize;}}?
?
在上面的設置之后,應用程序運行起來之后,Window的表現為:
分析一下設置:
MyPanel1.Width = 200, MyPanel1.MinWidth = 150, MyPanel1.MaxWidth = 250, MyPanel1.Margin = Thickness(10)
MyPanel1.Measure()傳入的參數為120*120,MyPanel1.MeasureOverride返回的參數為50*50
分析一下結果:
MyPanel1實際的畫出來的大小(紅色部分)是100*50
從結果可以看出,紅色的部分受多個因素的影響,有人要問,我已經設置了MyPanel.Width=200,可是怎么畫出來的Width卻是100;MyPanel.Height沒設置,可是畫出來的卻是50,為什么不是其他值。接下來我通過Measure的流程圖說明一下這個結果是怎么來的:
看了上圖,有些人可能會看出一些端倪,也可能還不是很清晰,我按照自己的理解總結一下Measure過程究竟想干什么?
1. 第一點很清晰,MyPanelParent調用MyPanel.Measure的過程是想得到MyPanel.DesiredSize,MyPanelParent需要在Arrange孩子MyPanel時,參考孩子的DesiredSize,決定將孩子MyPanel安置多大的空間。
2. MyPanel.DesiredSize是包含Margin以及內容的大小空間
3. MyPanel.MeasureOverride傳入的參數constrainedSize,是基類的實現刨去Margin的大小,然后按照MyPanel對MinWidth,MaxWidth,Width的設置計算的一個MyPanel想要的值,我們自定義時在MeasureOverride當中不需要關心自己的Margin,以及其他基類上影響Layout的屬性,只要考慮在給定參數的范圍類安排自己的內容區域;MyPanel.MinWidth,Width, MaxWidth的設定都是針對內容區域的,不含Margin部分
4. 如果不設定Width,那么可以在MeasureOverride返回的時候返回一個期望的內容區域大小,它會被MinWidth和MaxWidth再調整一下,調整后,還有待于MyPanelParent的衡量(旁白:別瞎折騰,也別玩Layout系統,都設置MinWidth,MaxWidth,就乖乖的呆在這個范圍內。)
5. 不論MyPanel怎么設置自己的Width,MinWidth,MaxWidth,以及在MeasureOverride返回一個大小,來表明自己期望多大的空間顯示自己的內容,但這些都僅僅是期望的,期望是美好的,現實是殘酷的,這一切還必須限定在MyPanel.Measure開始時傳入的參數availableSize刨去MyPanel.Margin后的范圍內,小于這個范圍就滿足,大于這個范圍就被裁斷。(可憐呀,總是受制于父)
6. 影響Measure過程的參數和屬性存在一個優先級的,大概如下所示:
Measure方法參數availableSize>MinWidth,Width,MaxWidth > MeasureOverride返回值
?
2.?Transform對Measure過程的影響
通過上面的過程,我們已經大概了解了Measure過程的工作方式,以及各個屬性是如何影響的。但是還有一個屬性我們沒有提及,但它對Measure的過程也影響甚大,這就是LayoutTransform。通過下面的兩段分析,你會看到這個屬性的具體表現。
設置1:
?
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1"><Canvas><my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Lime" Canvas.Left="10" Canvas.Top="10"><my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" Width="200"><my:MyPanel.LayoutTransform><RotateTransform Angle="90"/></my:MyPanel.LayoutTransform></my:MyPanel><my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" MaxWidth="250"/></my:MyPanelParent></Canvas> </Window>?
public class MyPanelParent:Panel{protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize){foreach (UIElement item in this.InternalChildren){item.Measure(new Size(1000, 800));}return availableSize;}protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize){double x = 0;foreach (UIElement item in this.InternalChildren){item.Arrange(new Rect(x, 0, item.DesiredSize.Width, item.DesiredSize.Height));x += item.DesiredSize.Width;}return finalSize;}?
? public class MyPanel : Panel{protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize){foreach (UIElement item in this.InternalChildren){item.Measure(availableSize);}return new Size(80, 50);}protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize){double xCordinate = 0;foreach (UIElement item in this.InternalChildren){item.Arrange(new Rect(new Point(xCordinate, 0), item.DesiredSize));xCordinate += item.DesiredSize.Width;}return finalSize;}}運行的表現為:
?
分析一下設置:
MyPanel1.LayoutTransform = new RotateTransform(90)//旋轉了90度
MyPanel1.Width = 200
MyPanel1.Margin = Thickness(10)
MyPanel1.Measure()傳入的參數為1000*800,MyPanel1.MeasureOverride返回的參數為80*50.
分析一下結果:
MyPanel1實際的畫出來的大小是50×200,明顯是被旋轉了90度。
運行起來,你會發現最終的MyPanel1.DesiredSize在Measure過程之后為70×220,也就是說,它是被Transform之后的大小,明顯是被旋轉過的。另外,觀察MyPanel.MeasureOverride傳入的參數,為200×980,根據上一節對Measure過程的分析,MeasureOverride傳入的參數寬為200是可預知的,因為我們設置了MyPanel1.Width為200,但Height為980,明顯是MyPanel.Measure傳入的寬1000減去2*10等于980,看來在進入MeasureOverride之前,Layout系統也處理了LayoutTransform對Measure過程的影響,它希望MeasureOverride不要關心自身LayoutTransform的影響。MeasureOverride結束后,返回值為80×50,根據上一節對Measure過程的分析,寬為80被調節為符合自己的設置,為200,由于高沒有設置,這個50肯定會保留,因此最后在沒有Transform之前的DesiredSize應該是220×70,然而基類會將MeasureOverride返回的大小再進行一次Transform,達到最終的DesiredSize的大小,以便Arrange的時候分配合適的空間來容納MyPanel的大小。
如果你將上面例子的MyPanel1.LayoutTransform設置成ScaleTransform:
?
<Window x:Class="WpfApplication1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="MainWindow" Height="522" Width="594" Loaded="Window_Loaded" xmlns:my="clr-namespace:WpfApplication1"><Canvas><my:MyPanelParent x:Name="myPanelParent1" Height="400" Width="400" Background="Lime" Canvas.Left="10" Canvas.Top="10"><my:MyPanel Margin="10" x:Name="myPanel1" Background="Red" Width="200"><my:MyPanel.LayoutTransform><ScaleTransform ScaleX="2" ScaleY="2"/></my:MyPanel.LayoutTransform></my:MyPanel><my:MyPanel Margin="10" x:Name="myPanel2" Background="Red" MinWidth="150" MaxWidth="250"/></my:MyPanelParent></Canvas> </Window>然后再觀察myPanel.MeasureOverride傳入的參數,為200×390,首先200是可預知的,因為設置了Width屬性,而390是怎么回事呢,其實為Measure傳入的1000×800的高800減去Margin為20后得到780,然后根據LayoutTransform將高縮小2倍之后得到的390,因此傳入的參數就是200×390,可見,Layout系統,在進入MeasureOverride之前,他希望,MeasureOverride只關心內容怎么布置,而不需要關心基類屬性的設置對MeasureOverride的影響。由于MeasureOverride的返回值依然是80×50,可推理,80被調節為200,50被保留,沒有Transform之前的值應該是200×50。因為基類還要進行Transform,因此,內容區域的真實的大小應該是400×100,再加上Margin之后,最終的DesiredSize肯定為420*120,你可以嘗試調試給出的代碼。
?
3.?Measure過程的總結
Measure過程的總結
通過上面的過程分析,我相信你或多或少對WPF的Layout系統的Measure過程有了更進一步的了解,其實還有一些因素影響Measure的過程,比如UseLayoutRounding屬性,在進入MeasureOverride之前和之后,基類都被將參數根據DPI進行Rounding,這個過程知道就行了,不需要在自己的MeasureOverride里面關心。我們總結一下哪些屬性和參數會影響Measure的過程:MyPanel.Measure傳入的參數availableSize,MyPanel的MinWidth, Width, MaxWidth,Margin,UseLayoutRounding,LayoutTransform,MeasureOverride的返回值。
?
Measure過程相關問題解答?
Q1:什么是Layout Slot??什么時候能獲取到?在哪里獲取??
Layout Slot就是調用Arrange方法的時候,傳入的參數finalRect,這是父分配給子的容納Margin以及內容區域的矩形空間;
當Arrange過程結束后,你可以拿到;
通過調用靜態類LayoutInformation.GetLayoutSlot(FrameworkElement element)方法可以拿到。
Q2:什么是Layout Clip?什么時候能獲取到?在哪里獲取??
Layout Clip 只的是當內容區域要繪制的大小,大于LayoutSlot刨去Margin區域后的大小,這時候,內容區域就會被Clip,超出的部分會被Clip掉,而剩下的可顯示的部分就是Layout Clip,他是一個Geometry。
Arrange過程結束后,可以拿到;
通過調用靜態類LayoutInformation.GetLayoutClip(FrameworkElement element)方法可以拿到。如果內容區域可以完全顯示
在Layout Slot刨去Margin的區域內,LayoutClip為Null。
Q3:在父的MeasureOverride當中調用孩子的Measure方法時,傳入的參數有沒有什么限制??
有,確保availableSize.Width和Height不是NaN;但可以是Infinity
Q4:在進入自己的MeasureOverride方法后,面對參數我該咋辦??
首先,心里應該明白,傳入的參數已經是基類刨去自己的Margin,并且考慮了基類影響Measure過程的屬性之后的值。
其次,看自身有沒有自定義的,并且影響Layout的屬性,根據自己的內容要求,或者孩子的情況,調用孩子的Measure方法,并傳入希望孩子限定在多大范圍內空間。
最后,返回一個自己期望的Size。
這里應該注意的點:
1. 調用孩子的Measure方法時,傳入的參數,是你限定孩子的最大空間,用來顯示孩子的Margin以及內容區域的,而孩子不管最終期望的大小有多少,都會被你給他的availableSize裁剪。
2. 根據自身的策略返回一個期望的值,這個期望的值應該是在自己的MinWidth,Width,MaxWidth限定的范圍呢,如果沒有,基類還會強行調整。
3. 基類調整后的值還會被父傳入的availableSize再次調整,返回值不能大于父傳入的參數減去Margin之后的值
Q5: MeasureOverride的返回值有沒有什么限制??
有,除了如Q5所說,返回值會被重新調節之外,必須保證自己定義的MeasureOverride的返回值是一個確定的值,不是NaN,也不是Infinity。如果小于0時,基類會強制調節為0.
Q6:DesiredSize究竟是什么??
DesiredSize是Measure過程結束后確定的一個大小,他是孩子期望父在Arrange的時候給他分配的大小,包含孩子的Margin區域以及內容區域。如果父在ArrangeOverride的時候,需要調用孩子的Arrange方法時,如果根據策略他希望滿足孩子的期望大小,那么,調用孩子的Arrange方法應該傳入孩子DesiredSize大小的Rect。
Q7:孩子的DesiredSize確定后,是不是最終就可以得到這么大的空間??
不一定。就像Q7答案所講,根據父的策略而定,如果父期望分配給孩子期望的大小,就在調用孩子的Arrange方法時,傳入DesiredSize大小的Rect,比如Canvas,Canvas的孩子的大小就是孩子的DesiredSize那么大;而如果父是根據自身的設置決定,就不會參考孩子的DesiredSize,傳入的當然是自己只能分配給孩子的空間,比如UniformGrid,他根據自身的可用大小,根據行數列數均分空間,然后,均分后的空間分配給每個孩子,而不考慮孩子的DesiredSize。給孩子分配空間,這個過程是在Arrange階段的。
?
原文地址:http://www.cnblogs.com/powertoolsteam/archive/2011/01/10/1932036.html
轉載于:https://www.cnblogs.com/XzcBlog/p/3972331.html
總結
以上是生活随笔為你收集整理的WPF/Silverlight Layout 系统概述——Measure(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache Hadoop 2.4.1
- 下一篇: Laravel学习笔记(二)