GacUI基本概念(二)——排版(2)
今天要講的是GacUI里面的三個基礎的排版功能。這些功能都可以在GacUI_Layout示例代碼里面找到。
基本概念
大部分排版的概念都是成對出現的,譬如說
GuiStackComposition 和 GuiStackItemComposition
GuiFlowComposition 和 GuiFlowItemComposition
GuiTableComposition 和 GuiCellComposition
熟悉地球上最先進的GUI:WPF的朋友們可能會立刻反應過來,在WPF里面,“右邊”的這三個類都是通過DependencyProperty來做的,所以實際上WPF只有“左邊”的這三個類。不過GacUI之所以不這么做有,自然是因為C++的限制,不可能做出好的DependencyProperty。事實上GacUI已經是我制作的第八個GUI類庫了,之前的七個都因為不同的原因而失敗了,其中就有一個是用來模擬DependencyProperty的。
DependencyProperty的原理就是在類里面放一張表,用來動態的查詢一個屬性的值,就跟Javascript等語言的做法一樣。但是這里的key其實并不是屬性的名字,而是用來定義DependencyProperty的那個類的名字。所以你在WPF里面才會用到什么Grid.Row啊,Grid.Column這樣的名字。
為什么要使用這樣的名字呢?這一點就是WPF比Windows Forms先進的地方。我們知道Windows Forms的Font屬性都是繼承的,但是這個所謂的繼承,實際上是實現在了所有定義或者覆蓋了這個Font屬性的類型里面,這是一種非常粗暴而且很容易出錯的實現方法。DependencyProperty就沒有這個問題,因為當你的屬性需要模擬這一行為的時候,你只要看一眼發現對象里面并沒有cache你的這個key和對應的值,那就直接往父對象里面找。更改的時候,還可以隨便廣播一個事件(當然我并沒有看WPF的代碼,所以我說的只是可能的一種方法)。于是所有跟這個屬性相關的邏輯就全部集中到了一起。
但是在C++里面沒法做,原因只有一個,就是因為太慢了。不過現在GacUI其實并沒有什么立場來說WPF這一點不好,因為我現在初始化窗口的時候還是在用反射。不過我跟WPF有一點本質的區別,就是我的腳本都是靜態類型的,所以過不久我就可以把腳本轉C++的功能上線了,到那個時候運行時就再也沒有什么反射了。WPF其實也注意到了這樣的一個問題,所以他在新的UWP里面(不要問我為什么WPF沒有,哈哈哈哈),實現了一個叫做x:Bind的綁定,做的事情跟GacUI其實一摸一樣:把data binding的邏輯全部轉換成代碼,而不是在運行時去hook這些對象。
英雄所見略同。
既然不能用DependencyProperty,那GacUI要怎么辦呢?鑒于layout其實是一個combinator,那我就本來在WPF寫成
<Button Grid.Row="0" Grid.Column="1">Click Me!</Button>的東西,在GacUI改成
<Cell Site="row:0 column:0"><Button Text="Click Me!"/> </Cell>就好了。Combinator就是這么用的,哈哈哈哈哈。
排版
上一篇文章 介紹了基礎的排版功能和 GuiSharedSizeRootComposition、GuiSharedSizeItemComposition 這一租排版對象。不過這里介紹的這三組跟SharedSize不同的是,“右邊”的對象必須是“左邊”的對象的直接子對象。不過在運行的時候我并沒有檢查,因為只要你不這么放,我實際上只會簡單的忽略這些屬性。下面介紹的所有的排版對象都有基礎的排版功能所需要的那些屬性,譬如AlignmentToParent、Margin、InternalMargin、PreferredMinSize、MinSizeLimitation等這些屬性,所以在這里我只會講每個對象獨有的功能。
GuiStackComposition 的屬性
Direction
Direction的意義很簡單。作為一個Stack,自然會有生長的方向。因此分別提供從左到右、從右到左、從上到下、從下到上的生長方向也是很自然的,參考 GuiStackComposition::Direction 。
Padding
Padding指的是每一個StackItem之間的間距。其實我們總是可以使用StackItem的InternalMargin,或者調節StackItem里面的對象的Margin或者AlignmentToParent來模擬這一屬性。但是當你需要在每一個StackItem之間都插入相同的空間的時候,這樣做無疑是很浪費時間的,因此Stack就提供了這樣的一個屬性。
ExtraMargin
ExtraMargin跟InternalMargin很接近,但是他只對StackItem起作用(也就是說,你可以把不是StackItem的東西放進Stack,那么這個時候,相對于這個對象,Stack就變成了一個普通的GuiBoundsComposition)。當確定了所有的StackItem需要的空間之后,ExtraMargin會在所有StackItem的總體的周圍留下這么大的空間,讓Stack本身變大。
IsStackItemClipped 和 EnsureVisible函數
在Stack并沒有要求要把自己變大到足夠放下所有子對象的前提下,StackItem是有可能會因為Stack不夠大而看不見的。因此IsStackItemClipped函數會告訴你這種情況到底發生了沒有。而且就算發生了也沒關系,因為EnsureVisible函數可以讓所有的StackItem根據Direction的要求進行滑動,使得你喜愛的其中一個StackItem被顯示出來。說到這里可能很多人都不明白為什么要有這樣的功能 —— 其實很簡單,Tab控件就有這個要求。
GuiStackItemComposition 的屬性
ExtraMargin
StackItem也有一個ExtraMargin屬性。但是這個屬性跟Margin不一樣的地方在于,Stack并不會去理會StackItem的ExtraMargin屬性的值。Stack會先告訴每一個StackItem他們應該被放到哪里,然后最終處理StackItem的位置的時候,會根據ExtraMargin變大一點。當然當你的ExtraMargin比Stack的Padding還要大的時候,你的StackItem就會跟別的StackItem有交叉。配合 GuiGraphicsComposition::MoveChild函數,那么Tab控件的需求就被完全滿足了 —— 點中的TabHeader不僅會變大,而且還會總是在最上面,擋住旁邊的兩個TabHeader。
GuiFlowComposition 的屬性
Axis
這個屬性的值是一個 GuiAxis 對象。雖然這個對象看起來很復雜,但是我們在使用的時候只需要關心它的構造函數。Stack是一維的,但是Flow是二維的,因此生長方向自然就會有8個,所以GuiAxis構造函數就需要你填入一個 AxisDirection 枚舉結構的值。
RowPadding
RowPadding是虛擬行的行距。不過這里的行是Axis屬性規定的Y軸方向的間距。根據設置的不同,所以這個虛擬的行也可能是現實中的列。
ColumnPadding
ColumnPadding是虛擬列的列距。不過這里的行是Axis屬性規定的X軸方向的間距。根據設置的不同,所以這個虛擬的列也可能是現實中的行。
ExtraMargin
ExtraMargin跟Stack的ExtraMargin意思完全一致,在此不再贅述。
Alignment
Alignment指的是當一個虛擬行已經放不下更多的FlowItem,但是他還有空間的時候,要怎么處理。當然我們會有(虛擬的)左對齊、居中和擴展這三種方法,所以我提供了 FlowAlignment 這一個枚舉類型。
GuiFlowItemComposition 的屬性
ExtraMargin
ExtraMargin跟StackItem的ExtraMargin意思完全一致,在此不再贅述。
FlowOption
FlowOption指的是計算基線的方法。不同的FlowItem可以使用不同的基線,從而使得內容在邏輯上被真正的對齊。舉個例子,你需要放很多按鈕,但是其中一個按鈕可能下面會有一點裝飾,那么這個時候你就需要抬高一下相應的FlowItem的基線,使得對齊的是按鈕,從而裝飾就顯示在正一行的下面。
在這里需要指出,當FlowOption::baseLine的值是Percentage的時候,基線是是從上往下計算的。因此你要底部對齊,percentage屬性就要寫1.0。
GuiTableComposition 的屬性
Rows和Columns
Table的Rows和Columns屬性都是 GuiCellOption 的值的列表。當你使用C++設置這個值的時候,你需要首先調用 GuiTableComposition::SetRowsAndColumns 函數告訴Table一共有多少行多少列,然后調用 GuiTableComposition::SetRowOption 和 GuiTableComposition::SetColumnOption 去指定具體的值。
GuiCellOption的composeType屬性可以設置,這一行或者列要使用內容的最小值、固定的大小或者是占用Table空間的百分比來構成。這里需要注意的是,Table會首先排除composeType是MinSize和Absolute的那些行和列占用的空間,剩下的部分才分配給composeType是Percentage的行和列。
因此如果你需要講一個按鈕居中在窗口的中間的話,你就可以簡單地通過設置這些屬性來完成:
<Table AlignmentToParent="left:0 top:0 right:0 bottom:0"><att.Rows><CellOption>composeType:Percentage percentage:0.5</CellOption><CellOption>composeType:MinSize</CellOption><CellOption>composeType:Percentage percentage:0.5</CellOption></att.Rows><att.Columns><CellOption>composeType:Percentage percentage:0.5</CellOption><CellOption>composeType:MinSize</CellOption><CellOption>composeType:Percentage percentage:0.5</CellOption></att.Columns><Cell Site="row:1 column:1><Button Text="Click Me!"/></Cell> </Table>所有percentage加起來并沒有要求一定要是1,所以你就算寫是三個2,那也跟三個0.1是一樣的 —— 每一個占用1/3的空間。
CellPadding
CellPadding指的是Table的外邊框和內邊框的大小。也就是說除了行距和列距以外,Table本身還會放大CellPadding這么大的地方,讓Cell和Table本身也有一個距離。
GuiCellComposition 的屬性
Site
Site屬性分別有四個值:row、column、rowSpan和columnSpan,用來指定一個Cell在Table中到底占用了哪些格子。不同的Cell之間不能重疊,但是一個Cell可以占用多個格子。舉個例子:如果你需要在一個窗口里面放一個文本框,然后右下角有OK和Cancel兩個按鈕的話,我們自然可以想到需要使用2×3的表格來做。使用GacUI當然可以簡單地做到:
<Table AlignmentToParent=left:0 top:0 right:0 bottom:0" CellPadding="5"><att.Rows><CellOption>composeType:Percentage percentage:0.5</CellOption><CellOption>composeType:MinSize</CellOption></att.Rows><att.Columns><CellOption>composeType:Percentage percentage:0.5</CellOption><CellOption>composeType:MinSize</CellOption><CellOption>composeType:MinSize</CellOption></att.Columns><Cell Site="row:0 column:0 columnSpan:3"><MultilineTextBox><att.BoundsComposition-set AlignmentToParent="left:0 top:0 right:0 bottom:0"/></MultilineTextBox></Cell><Cell Site="row:2 column:1"><Button Text="OK"><!-- 當文字變得很多的時候,按鈕會自動變大,但是最小會保持30×100的大小 --><att.BoundsComposition-set PreferredMinSize="x:100 y:30"/></Button></Cell><Cell Site="row:2 column:1"><Button Text="Cancel"><att.BoundsComposition-set PreferredMinSize="x:100 y:30"/></Button></Cell> </Table>尾聲
GacUI其實還可以通過把Control和Composition放進一個富文本文檔里面來進行排版。不過由于篇幅限制,這個內容我將在介紹富文本文檔的時候一并說明。
總結
以上是生活随笔為你收集整理的GacUI基本概念(二)——排版(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分享周鸿祎的《如何建立一个“铁打的营盘”
- 下一篇: 黑客攻破网站涂鸦特效(强烈建议看看)