1. 程式人生 > >WPF/UWP 的 Grid 布局竟然有 Bug,還不止一個!了解 Grid 中那些未定義的布局規則

WPF/UWP 的 Grid 布局竟然有 Bug,還不止一個!了解 Grid 中那些未定義的布局規則

fin border print mar ofo tag 分享圖片 soft sdn

原文: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 各占一列,其中兩邊等長,中間稍長。

技術分享圖片

那麽實際布局中各列是怎麽分的呢?以下是設計器為我們顯示的列寬:

技術分享圖片

466946 是怎麽來的?莫非是 46:6928:51 相同?然而實際計算結果卻並不是!

可萬一這是計算誤差呢?

那麽我們再來看看三個 Border 的另外兩組值:50:50:5025: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 布局在特殊情況下是有一些不合常理的。我稱之為“未定義的規則”。這些未定義的規則總結起來有以下三點:

  1. 在無窮大布局空間時的 * 的比例
  2. 在跨多列布局時 * 的比例
  3. 在全 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 中那些未定義的布局規則