WPF/UWP 的 Grid 布局竟然有 Bug,還不止一個!了解 Grid 中那些未定義的布局規則
版權聲明:本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:http://blog.csdn.net/wpwalter/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請與我聯系([email protected])。 https://blog.csdn.net/WPwalter/article/details/80371262
只要你用 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/UWP 的 Grid 布局竟然有 Bug,還不止一個!了解 Grid 中那些未定義的布局規則