這一篇先講排版的一部分。文章的順序是按照理解GacUI的順序來編寫的,所以那些難的、不常用的東西都會放到最後。顯然一個GUI庫,使用者最關心的就是三個部分:排版、換膚和繫結。但是由於篇幅限制,排版也要分開幾篇來寫。
GacUI 排版的基本用法
如果不首先了解GacUI的排版機制的話,直接硬上通常都會遇到,怎麼我建立出來的控制元件的大小是0這樣的事情。GacUI不鼓勵你hard code大小,所以所有控制元件都沒有尺寸屬性。不過我還是留了一個小後門,當你就是想要這麼做的時候,還是可以做。而且我的設計剛好使得,如果你大規模hard code大小,那用起來就會很苦逼。
GuiGraphicsComposition
之前我們已經瞭解到,GacUI的圖形都是由Composition和Element組成的。Composition的基類是 GuiGraphicsComposition 。在這裡有幾個重要的屬性:
MinSizeLimitation 屬性
首先要提出的是,雖然在XML上面可以直接使用這個屬性,但是當你使用C++的時候,這個屬性是通過 GetMinSizeLimitation 和 SetMinSizeLimitation 來訪問的。MinSizeLimitation屬性控制的是,這個Composition的大小如何被他的Children所影響。屬性的型別是一個列舉型別:
enum MinSizeLimitation
{
NoLimit,
LimitToElement,
LimitToElementAndChildren,
};
其中:
-
NoLimit:Composition的大小跟他的Children完全沒有關係。
-
LimitToElement:Composition的大小僅跟繫結在Composition上面的這個Element有關係。舉個例子,如果你的Composition使用了 GuiSolidLabelElement ,那麼,Composition最小的大小就是Element裡面的文字所決定的大小。當然這個Element裡面也有一些設定,譬如說能否換行啊,能否顯示省略號等等,這些都會影響最小大小的計算。
-
LimitToElementAndChildren:Composition的大小同時受到Element和Children的影響。
那麼Children是如何影響Composition的大小的呢?
Margin 和 InternalMargin 屬性
使用CSS的朋友們應該都很熟悉,Margin和InternalMargin分別類似於margin和padding的概念。在這裡需要特別指出,Children使用的座標空間是由InternalMargin所決定的,原點剛好是InternalMargin劃出來的矩形的左上角。這首先就限制了所有的Children都必須顯示在InternalMargin的範圍內,超出的部分都會被直接剪裁掉,哪怕他實際上並沒有超出Composition的範圍。
而Margin屬性則是一個建議,他會告訴上一級的Composition在計算Children的大小的時候,要把Margin考慮進去。
PreferredMinSize 屬性
PreferredMinSize屬性跟上面的屬性可以同時使用。你可以通過給一個值,告訴一個Composition,不管計算出來的最小大小是多少,總之不能小於PreferredMinSize的大小。
GuiBoundsComposition
GuiGraphicsComposition是個抽象類,他是不能直接使用的。如果你需要放一個Composition進你的樹裡面,通常你會選擇 GuiBoundsComposition 或者他的子類。如果一個Composition不繼承自GuiBoundsComposition,那麼通常意味著,他的尺寸是完全受到上一級Composition的控制的,譬如Table控制Cell,Stack控制StackItem,Flow控制FlowItem,Document控制DocumentItem這樣。
GuiBoundsComposition多出了兩個屬性,分別是:
AlignmentToParent 屬性
AlignmentToParent屬性分別強行設定了一個Composition的四個方向,Margin的外面跟上一級Composition的InternalMargin的裡面的距離是多少。設定為-1就意味著不規定這個距離。只要你設定了AlignmentToParent,那麼這將直接影響到上一級Composition的尺寸的計算。在這裡我舉個例子:
假設A包含B,然後分別有下面的屬性:
A.InternalMargin = {left:1 top:2 right:3 bottom:4}
B.Margin = {left:5 top:5 right:5 bottom:5}
B.PreferredMinSize = {x:100 y:50}
B.AlignmentToParent = {left:-1 top:-1 right:10 bottom:20}
首先我們可以看出,由於B設定了這一個AlignmentToParent的值,所以無論A的尺寸如何改變,B永遠都位於A的左下角,而且B的Margin和A的InternalMargin在右下角的距離分別是10和20。我們可以很清楚的知道,B的最小尺寸就是100和50,那麼A的最小尺寸是多少呢?
首先,A的InternalMargin決定了,A的橫向大小不小於left+right=4,縱向大小不小於top+bottom=6;
其次,B的Margin和PreferredMinSize決定了,B佔用的A的空間的橫向大小不小於left+right+x=110,縱向大小不小於top+bottom+y=60;
再者,B的AlignmentToParent規定了,B的Margin外面和A的InternalMargin裡面還有一個10和20的距離;
因此:A的最小尺寸就是4+110+10=124,和6+60+20=86,然後B永遠處於A右下角的指定位置。
那麼這有什麼用呢?如果A是 GuiWindow 的 ContainerComposition 屬性,然後你放一個B進去,那麼當你拖動視窗的大小的時候,你會發現當視窗的客戶區小於124和86的時候,視窗就會限制你不能把他拖得更小。事實上,你整顆Composition樹的根節點的大小最後會反映到視窗上面去。
Bounds 屬性
這是一個喜聞樂見的屬性,因為透過這個屬性,你就可以強行hard code一個Composition的位置了。你可以通過修改控制元件的 BoundsComposition 屬性的 Bounds 屬性,從而強行指定一個控制元件的位置。然而需要注意的是,Bounds的優先順序是最低的,也就是說,如果它的值跟別的屬性衝突了(譬如說Size比最小尺寸小,位置跟那些Margin和Alignment有衝突)的話,那麼控制元件的位置和尺寸將不嚴格按照Bounds的設定擺放。控制元件會先參考別的屬性的值,實在找不到約束了,最後去看Bounds。所以有時候會發現一個控制元件的位置跟你強行指定的位置不一樣,就是這個原因。
我就是不喜歡你們hard code尺寸,哈哈哈哈哈哈。
GuiSharedSize(Root|Item)Composition
除此之外,GacUI還支援很多原生的排版功能。在這些排版功能裡面,最有趣的就是SharedSize了。這是什麼樣的排版呢?假設你們在設計選單。選單的文字跟快捷鍵部分在垂直的方向上是不重疊的。然而他們的父子結構決定了,一個選單首先按行切割,其次才按列切割。而且你還不能用表格做,因為每一個選單是一個獨立的控制元件,而且選單裡面還可以放別的東西啊。
那如何讓一個選單裡面的所有選單項,會在排版的時候,先互相交換文字和快捷鍵部分的長度,從而一個顯示成這樣的選單:
TEXT1 [CTRL+SHIT+DEL+Z]
A-VERY-LONG-TEXT [CTRL+Z]
不會被顯示成
TEXT1 [CTRL+SHIT+DEL+Z]
A-VERY-LONG-TEXT [CTRL+Z]
呢?這就靠SharedSize了。SharedSize由 GuiSharedSizeRootComposition 和 GuiSharedSizeItemComposition 組成。其中Item並不需要——而且幾乎也不可能——是Root的直接的Children。Root會在排版的時候去尋找(當然演算法不可能這麼寫了這樣太慢了)他子樹裡面的所有Item,然後根據每個Item設定的Group去分組,最後按照要求統一他們的最小尺寸。
因此在上面的選單的例子中,我們可以在整個選單的外圍放一個Root,而每一個選單項控制元件裡面,分別用兩個Item放文字跟快捷鍵,然後設定好分組的名字,文字分為一組,快捷鍵分為一組。最後要求每一個組的寬度都要統一(高度不需要)。最後的結果就是,所有的文字的最小尺寸都由最長的那個決定,所有的快捷鍵的最小尺寸也都由最長的那個決定,於是排版的結果就相當正確了。
尾聲
今天講的幾個屬性就是GacUI處理排版的時候的重要內容。當然GacUI不可能只支援這種排版,原生支援的排版功能還有Stack、Table、Flow、SharedSize和Document等。他們的具體內容可以參考 GacUI_Layout 這個demo。細節將在下一篇文章中講述。除了這五種以外,GacUI還有一些奇形怪狀的排版功能,這些基本只在製作控制元件皮膚的時候用到。
P.S.
控制相對位置的功能實在是太重要了,每次在CSS裡面搞這些的時候都覺得好蛋疼,明明這些功能在GacUI和WPF裡面設定出來如此簡單,結果CSS連讓一個div的大小貝設定成position: relative的child div的大小約束到這麼常見、直接、簡單的功能,都不提供。