WPF/Silverlight Layout 系統概述——Arrange
Arrange過程概述
普通基類屬性對Arrange過程的影響
我們知道Measure過程是在確定DesiredSize的大小,以便Arrange過程參考這個DesiredSize,確定給MyPanel分配多少空間,但是DesiredSize只是作為參考,在有些用例下,MyPanelParent在呼叫MyPanel.Arrange的時候,會根據父的實際策略指定MyPanel.Arrange方法的引數,而不是直接指定MyPanel.DesiredSize的大小,比如Grid。因此,最終MyPanel應該如何呈現,決定權還是在Layout系統的Arrange過程當中。那麼Arrange過程最終確定哪些資料呢?主要是MyPanel.RenderSize,MyPanel.VisualOffset以及VisualTransform三個屬性。再說的清楚點就是確定Layout Slot以及最終繪製的位置和區域。而LayoutSlot本質上就是MyPanelParent呼叫MyPanel.Arrange時,傳入的引數finalRect.
請看下面的設定:
|
|
根據上面的設定,我們沒有設定一些相關的屬性,比如Width,MinWidth,MaxWidth,另外,呼叫MyPanel.Measure時傳入的值也是比較大,可見MyPanelParent給孩子MyPanel足夠的空間去安排自己的內容,而MyPanel.MeasureOverride返回了一個50×50的大小,根據『普通基類屬性對Measure過程的影響』一節對影響Measure的引數的優先順序的總結,我們推理,目前MyPanel.DesiredSize應該是50×50,因為沒有Margin。另外,Arrange的時候呼叫MyPanel.Arrange傳入的引數也正好是MyPanel.DesiredSize,MyPanel.ArrangeOverride的返回值依然是傳入的引數大小,沒有變化,因此,我們推理,執行起來的MyPanel大大小就應該是50×50的紅色矩形區域,具體請看下圖:
如果你嘗試修改MyPanel.HorizontalAlignment,你會發現,這個屬性不管怎麼設定,MyPanel的位置和大小始終不會改變,這究竟是為什麼呢?
其實,HorizontalAlignment是控制當MyPanel的最終希望的大小,小於父MyPanelParent分配的LayoutSlot(呼叫MyPanel.Measure方法傳入的引數finalRect)時,MyPanel被安置的位置或大小。
如果將上面的例子的MyPanel.Arrange的引數改為100×100:
|
你看到的效果,就是另外一番景象了。這時候MyPanel期望的是50×50,LayoutSlot為100×100,那麼50×50就可以根據HorizontalAlignment或者VerticalAlignmet的設定起作用了。
HorizontalAlignment=Horizontal |
HorizontalAlignment=Stretch |
如果讓LayoutSlot為30×30,整個Panel又會是什麼樣子呢?可以預見的是HorizontalAlign是不會起作用了,因為期望的大小還要大於父分配的layout slot.執行後結果:
接下來,我們再新增修改一下設定,最後給出影響Arrange過程的因素,以及最終Render的結果。
請看程式碼:
|
|
執行效果如下所示:
設定分析:
MyPanel.Width=90,
MyPanel.DesiredSize = 90*50(通過Measure過程之後)
MyPanel.Arrange傳入的finalRect引數為100×100,大於DesiredSize
MyPanel.ArrangeOverride返回80×80
分析結果:
最終繪製的大小是80×80,不是期望的DesiredSize=90×50(根據Measure過程分析得到),而是MyPanel.ArrangeOverride返回的80×80,可見ArrangeOverride的返回值策略跟MeasureOverride返回值的處理是不一樣的,他是多少就是多少,不會受MinWidth,Width,MaxWidth的限制。然而,不受限制這一說法並不完全正確,當MyPanel.Width小於80時,繪製的大小會跟隨Width的設定,也就是說取兩者的最小值。前面說過Arrange過程是在確定RenderSize,那麼Arrange完成後,MyPanel.RenderSize是多少呢?為MyPanel.RenderSize = 80*80,也就是ArrangeOverride的返回值;
如果,你將MyPanel.MinWidth設定為30,Width設定為50,限制MyPanel的大小,繪製區域就變小了,成了50:
跟Measure過程一樣,上面所做的一切必須限制在MyPanel.Arrange傳入的引數finalRect,也就是LayoutSlot當中。可以預見的是,如果父呼叫MyPanel.Arrange方法傳入的不是100×100,而是30×30,那麼,最終的可見繪製區域就是30×30.
!Arrange_Window_5.PNG!
請猜一下上面兩種修改過後,MyPanel.RenderSize變化成多少了?
有人會回答:如果設定了MyPanel.Width為50,顯示的也為50×50,MyPanel.RenderSize就為50×50;傳入MyPanel.Arrange的引數為30×30時,顯示的也是30×30,MyPanel.RenderSize就為30×30.
完全錯誤!上面兩種設定後,MyPanel.RenderSize依然是80×80,也就是ArrangeOverride的返回值。
其實RenderSize是沒有Clip的繪製區域的大小,而實際看見的繪製區域是把RenderSize經過MyPanel.Width以及LayoutSlot進行Clip之後的效果。
通過下面的流程圖,描述一下Arrange過程具體做了哪些事情:
通過上面的流程圖,我們已經瞭解了Arrange過程大體所做的事情。按照對Measure過程的闡述,總結一下Arrange過程的主要點:
1. 確定RenderSize,以及最終MyPanel被安置的空間。RenderSize就是ArrangeOverride的返回值,沒還有被裁剪過的值。
2. 確定Client區域和Ink區域,根據HorizontalAlignment和VerticalAlignment確定Ink區域的在Client區域當中的位置和所佔據的大小。Client區域是LayoutSlot減去Margin;Ink區域是ArrangeOverride返回的值被Width剪下之後,必須是兩者的最小值。
3. 確定Visual基類上面若干跟具體Render相關的設定,比如VisualOffset,VisualTransform,Clip等等。
Transform對Arrange的影響
跟Measure過程類似,Layout系統不希望ArrangeOverride關心自身Transform的影響。所有對RenderTransform以及LayoutTransform設定,在ArrangeOverride退出後,基類會處理,並且根據設定調節VisualTransform。而RenderSize,LayoutSlot都不會受Transform的影響。
Arrange過程的總結
除了上面提到的屬性或者引數對Arrange過程有影響外,其實內容,還有更多屬性影響這個過程,總結一下哪些屬性和引數會影響Arrange過程:MyPanel.Arrange傳入引數finalRect,Mypanel.MinWidth,Width,MaxWidth,Margin,DesiredSize,HorizoantalUseLayoutRounding,ClipToBounds,Clip,LayoutTransform,RenderTransform,RenderTransformOrigin。
Arrange過程相關問題回答
Q1:在父的ArrangeOverride當中呼叫孩子的Arrange方法時,傳入的引數有沒有什麼限制?
除了向Q8所說,如需要根據自己的策略,決定給孩子分配多大的空間之外,還需要保證這個finalRect引數不能為NaN,Infinity。因為,Arrange過程是最終確定孩子的LayoutSlot的時機,必須保證傳入的引數是個確定的值。
Q2:在進入自己的ArrangeOverride方法後,面對引數我該咋辦?
跟Measure過程類似。
首先,心裡應該明白,傳入的引數已經是基類刨去自己的Margin,並且考慮了基類影響Arrange過程的屬性之後的值。
其次,看自身有沒有自定義的,並且影響Layout的屬性,根據自己的內容要求,或者孩子的情況,呼叫孩子的Arrange方法,並傳入希望孩子限定在多大範圍內空間。
最後,返回一個自己期望的Size,這個Size將會是RenderSize的值,不會被調整。
這裡應該注意的點:
1. 呼叫孩子的Arrange方法時,傳入的引數,是你限定孩子的最大空間,用來顯示孩子的Margin以及內容區域的,而孩子不管最終想要的RenderSize有多少,都會被你給他的finalRect裁剪。
2. 根據自身的策略返回一個finalSize,這個期望的值應該是小於等於Width,MaxWidth,如果沒有,基類還會強行調整,總是取最小值。
3. 基類調整後的值還會被父傳入的LayoutSlot再次調整,返回值不能大於父傳入的引數減去Margin之後的值。
4. 只有當ArrangeOverrid的返回值小於LayoutSlot刨去Margin區域之後的空間,HorizontalAlignment和VeriticalAlignment才會起作用,否則不會起作用。因為,ArrangeOverride返回值,相當去內容區域,而LayoutSlot刨去Margin的區域是Client區域,而Alignment就是將內容區域安放在Client區域的過程。
Q3:ArrangeOverride的返回值有沒有什麼限制?
可以為NaN或則PositiveInfinity。
Q4:RenderSize究竟是什麼?
從程式碼實現當中說,RenderSize就是ArrangeOverride的返回值。
從邏輯上講,RenderSize就是沒有被Clip過的繪製區域的大小。最終顯示出來的大小,是RenderSize經過自己的設定,以及父的限制之後,呈現出來的大小。
Q5: ContentControl的MeasureOverride和ArrangeOverride過程有沒有什麼特殊之處?
沒有,他的Layout策略繼承自Control,僅僅是將Content屬性的設定作為Control的Visual孩子而已。
Q6: Control的MeasureOverride和ArrangeOverride過程是什麼樣的?
MeasureOverride:Control的父給多大的空間,Control就將其全部給自己的孩子,並返回孩子的DesiredSize。
ArrangeOVerride:Control的父分配給孩子多大的空間,Control就將其全部給自己的孩子,並返回父給的內容區域作為RenderSize。
總結一點:Control的空間,就是他的孩子的空間。
Q7: ContentPresenter的MeasureOverride和ArrangeOverride是什麼樣子的?
跟Control一樣。
Q8: 能不能在MeasureOverride或者ArrangeOverride當中呼叫孩子的孩子,也就是孫子的Measure或者Arrange方法?
目前來看,WPF/SL是允許這樣做的。但是,通過研究Layout系統之後,發現這個系統是非常龐大和複雜,另外,Arrange的時候, 孩子的位置總是基於自己的父,另外,內容會發一些基於父的事件,比如,ChildDesiredSizeChanged之類的事件。因此,個人覺得,最好不要這樣做,誰也保證不了這樣的做法是否不會出問題,無疑中也增加了程式碼的可讀性。
我們在進行WPF/Silverlight開發時,還可以藉助一些工具來助力開發過程。ComponentOne Studio Enterprise 是一款專注於企業應用的.NET全功能控制元件套包,支援WinForms、WPF、UWP、Xamarin、ASP.NET MVC等多個平臺,幫助在縮減成本的同時,提前交付豐富的桌面、Web和移動企業應用。
影響WPF Layout的屬性總結以及跟Silverlight的不同之處
WPF |
Silverlight |
|
Measure過程 |
Margin,MinWidth,Width MaxWidth,MinHeight,Height, MaxHeight,UseLayoutRounding, LayoutTransform |
Margin,MinWidth,Width, MaxWidth,MinHeight,Height, MaxHeight,UseLayoutRounding |
Arrange過程 |
Margin,MinWidth,Width MaxWidth,MinHeight,Height, MaxHeight,UseLayoutRounding, LayoutTransform,RenderTransform,RenderTransformOrigin HorizontalAlignment,VeritcalAlignment |
Margin,MinWidth,Width MaxWidth,MinHeight,Height, MaxHeight,UseLayoutRounding, RenderTransform,RenderTransformOrigin HorizontalAlignment,VeritcalAlignment,Clip |
本文是由葡萄城技術開發團隊釋出,轉載請註明出處:葡萄城官網
瞭解開放易用的 Web 生成平臺,請前往活字格Web應用生成平臺