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