WPF/UWP的Grid佈局竟然有Bug,還不止一個!瞭解Grid中那些未定義的佈局規則
只要你用 XAML 寫程式碼,我敢打賭你一定用各種方式使(nuè)用(dài)過 Grid
。不知你有沒有在此過程中看到過 Grid
那些匪夷所思的佈局結果呢?
本文將帶你來看看 Grid
佈局中的 Bug。
無限空間下的比例
先上一段程式碼,直接複製到你的試驗專案中執行:
<Canvas>
<Grid Height="100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="CornflowerBlue" Width="150" />
<Border Grid.Column="1" Background="Tomato" Width="150" />
<Border Grid.Column="2" Background="Teal" Width="150" />
</Grid>
</Canvas>
第一列固定 100
,第二列佔 1 個比例的 *
,第三列佔 2 個比例的 *
。你覺得最終的效果中,第二個 Border
和第三個 Border
的可見尺寸分別是多少呢?
按
下
F5
運
行
看
看
結
果
預料到了嗎?雖然第二列和第三列的比例是 1:2,但最終的可見比例卻是 1:1。
這裡是有破綻的,因為你可能會懷疑第三列其實已經是第二列的兩倍,只是右側是空白,看不出來。那麼現在,我們去掉 Canvas
,改用在父 Grid
中右對齊,也就是如下程式碼:
<Grid HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="CornflowerBlue" Width="150" />
<Border Grid.Column="1" Background="Tomato" Width="150" />
<Border Grid.Column="2" Background="Teal" Width="150" />
</Grid>
執行後,你會發現最右側是沒有空白的,也就是說第二列和第三列確實不存在 1:2 的比例——它們是等寬的。
那麼那一段失去的空間去哪裡了呢?讓我們縮小視窗:
竟然在左側還有剩餘空間的情況下,右側就開始壓縮元素空間了!我們能說那段丟失的一個 * 長度的空白到左邊去了嗎?顯然不能。
不過,我們能夠猜測,壓縮右側元素開始於最小 1:2 的比例正好不足時出現。
剛好不夠分的比例
右對齊能夠幫助我們區分右側是否真的佔有空間。那麼我們繼續右對齊做試驗。
現在,我們將第二列的 Border
做成跨第二和第三兩列的元素。第三列的 Border
放到第二列中。(也就是說,我們第三列不放元素了。)
<Grid HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="CornflowerBlue" Width="150" />
<Border Grid.Column="1" Grid.ColumnSpan="2" Background="Tomato" Width="150" />
<Border Grid.Column="1" Background="Teal" Width="150" />
</Grid>
執行看看,在得知前一節現象的情況下,新的現象並沒有出現多大的意外。第三列憑空消失,第二列與之之間依然失去了 1:2 的比例關係。
然而,我們還可以縮小視窗。
縮
小
窗
口
後
竟
然
為什麼在縮小視窗的時候突然間出現了那個紅色的 Border
?為什麼在紅色 Border
的右邊還留有空白?
如果說第一節中我們認識到右對齊時右邊剩餘的空白空間會丟掉,那麼為什麼此時右邊剩餘的空白空間會突然出現?
我試著稍微增加第二個 Border
的寬度,突然間,剛剛縮小視窗時的行為也能復現!
自動尺寸也能玩比例
現在,我們拋棄之前的右對齊測試方法,也不再使用預期按比例劃分空間的 *
。我們使用 Auto
來實現比例功能。
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Width="159" Grid.ColumnSpan="3" HorizontalAlignment="Center" Background="PaleGreen" />
<Border Width="28" HorizontalAlignment="Left" Background="#7FFF6347" />
<Border Width="51" Grid.Column="1" HorizontalAlignment="Center" Background="#7FC71585" />
<Border Width="28" Grid.Column="2" HorizontalAlignment="Right" Background="#7F008080" />
</Grid>
具體說來,我們有四個 Border
了,放在 Auto
尺寸的三列中。第一個 Border
橫跨三列,尺寸比其他總和都長,達到了 159;剩下的三個 Border
各佔一列,其中兩邊等長,中間稍長。
那麼實際佈局中各列是怎麼分的呢?以下是設計器為我們顯示的列寬:
46
、69
、46
是怎麼來的?莫非是 46:69
與 28:51
相同?然而實際計算結果卻並不是!
可萬一這是計算誤差呢?
那麼我們再來看看三個 Border
的另外兩組值:50:50:50
和 25:50:25
。
▲ 50:50:50
▲ 25:50:25
50:50:50
最終得到的是相同比例,但是 25:50:25
得到的列寬比例與 1:2
相去甚遠。也就是說,其實 Grid
內部並沒有按照元素所需的尺寸來按比例計算列寬。
相同比例也能有不同尺寸
在上一節的試驗中,不管比例如何,至少相同的設定尺寸帶來了相同的最終可見尺寸。然而,就算是這一點,也是能被顛覆的。
現在,我們將 3 列換成 4 列,Border
數量換成 6 個。
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border Width="159" Grid.ColumnSpan="3" HorizontalAlignment="Center" Background="PaleGreen" />
<Border Width="159" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Center" Background="PaleGreen" />
<Border Width="28" HorizontalAlignment="Left" Background="#7FFF6347" />
<Border Width="51" Grid.Column="1" HorizontalAlignment="Center" Background="#7FC71585" />
<Border Width="51" Grid.Column="2" HorizontalAlignment="Center" Background="#7FC71585" />
<Border Width="28" Grid.Column="3" HorizontalAlignment="Right" Background="#7F008080" />
</Grid>
具體來說,第一個 Border
跨前三列,第二個 Border
跨後三列,跟前一節的長 Border
一樣長。第三和第六個 Border
分在兩邊,與之前的短 Border
一樣短。中間的兩個 Border
與之前中間的 Border
一樣長。就像下圖所示的這樣。
那麼此時佈局出來的列寬是多少呢?
▲ 32:65:65:39
等等!那個 39 是怎麼來的?如果前一節裡相等尺寸的 Border
會得到相等尺寸的列寬,那麼這裡也將顛覆!事實上,即便此時列寬比例與元素所需比例一致,在這種佈局下也是有無窮多個解的。WPF 只是從這無窮多個解中挑選了一個出來——而且,還無法解釋!
總結 Grid 未定義的規則
總而言之,言而總之,Grid
佈局在特殊情況下是有一些不合常理的。我稱之為“未定義的規則”。這些未定義的規則總結起來有以下三點:
- 在無窮大布局空間時的 * 的比例
- 在跨多列布局時 * 的比例
- 在全 Auto 尺寸時各列尺寸
不過你也可能會吐槽我的用法不對,可是,作為一個連表現行為都公開的 API,其行為也是 API 的一部分,應該具有明確可追溯可文件化的行為;而不是由使用者去探索,最終無法猜測可發生事情的行為。
微軟沒有任何官方文件公開了這些詭異的行為,我也沒有在任何第三方資料中找到這樣的行為(這些都是我自己總結的)。我認為,微軟沒有為此公開文件是因為行為太過詭異,無法編寫成文件!
你可能還會質疑,可以去 Reference Source 查閱 Grid
佈局的原始碼,那樣就能解釋這些詭異的行為了。確實如此,那裡是這一切詭異佈局背後的罪魁禍首。
我閱讀過 Grid
的佈局原始碼,但沒能全部理解,而且在閱讀的過程中發現了一些微軟官方承認的 Bug(我也沒有能力去解決它)。
不過,我整整三天的時間寫了一個全新的 Grid
佈局演算法(感謝 @林德熙 抽出時間跟我探討 Grid
的佈局演算法)。在新的演算法中,對於微軟公開的 Grid
佈局行為,我跟它的表現是一樣的。對於本文中提到的各種 Bug,我找不到手段實現跟它一模一樣的佈局結果,但是,我可以文件化地完全確定 Grid
整個佈局的所有行為。包括以上所有我認為的“未定義的規則”。
新 Grid
佈局演算法的原始碼在 GitHub 上,我提交給了 Avalonia:A new grid layout algorithm to improve performance and fix some bugs by walterlv · Pull Request #1517 · AvaloniaUI/Avalonia。
相關文章
- WPF使用Grid佈局
- dispaly的Grid佈局與Flex佈局Flex
- Grid佈局
- css grid 佈局CSS
- css grid佈局CSS
- CSS 中的 Grid 佈局 完全指南CSS
- Grid佈局簡介
- CSS Grid佈局指南CSS
- grid網格佈局
- Grid 佈局-子項補充及常用佈局
- Grid 佈局發車啦
- grid佈局基本概念
- 【CSS】Grid 佈局總結CSS
- 深入淺出grid佈局
- grid佈局快速入門
- CSS:玩轉grid佈局CSS
- Grid 拖拽佈局實現
- 快速開始grid佈局
- 未來佈局之星Grid
- CSS Grid 網格佈局CSS
- 進擊的佈局之Grid Layout
- tkinter中佈局pack、place和grid(八)
- [翻譯] Grid 佈局完全指南
- CSS Grid 網格佈局教程CSS
- CSS grid佈局好文推薦CSS
- CSS Grid重構Medium的文章佈局CSS
- 你需要的Grid佈局入門教程
- 使用 Grid 進行常見佈局
- CSS Grid實現聖盃佈局CSS
- 關於 Grid 佈局的那點事兒
- [譯] 帶你入門 CSS Grid 佈局CSS
- css的佈局的定位(瞭解)CSS
- CSS Grid 系列(下)-使用Grid佈局構建網站首頁CSS網站
- 5分鐘掌握Grid佈局【多圖示例】
- CSS Grid 網格佈局邊框設定CSS
- 木桶佈局,瞭解一下
- 用 CSS Grid 佈局製作一個響應式柱狀圖CSS
- SAP UI5 Form 表單的 Responsive Grid Layout 佈局中的 breakpointUIORM