只要你用 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 中那些未定义的布局规则的更多相关文章

  1. CSS Grid 布局完全指南(图解 Grid 详细教程)

    CSS Grid 布局是 CSS 中最强大的布局系统.与 flexbox 的一维布局系统不同,CSS Grid 布局是一个二维布局系统,也就意味着它可以同时处理列和行.通过将 CSS 规则应用于 父元 ...

  2. 第二次前端作业grid布局练习

    grid布局 CSS Grid(网格) 布局(又称为 “Grid(网格)” ),是一个二维的基于网格的布局系统,它的目标是完全改变我们基于网格的用户界面的布局方式.CSS 一直用来布局我们的网页,但一 ...

  3. CSS3 Flex布局和Grid布局

      1 flex容器的六个属性 flex实现垂直居中: <div class="box"> <span class="item">< ...

  4. 5分钟学会 CSS Grid 布局

    欢迎加入前端交流群交流知识&&获取视频资料:749539640 这是一篇快速介绍网站未来布局的文章. Grid 布局是网站设计的基础,CSS Grid 是创建网格布局最强大和最简单的工 ...

  5. CSS学习笔记:grid布局

    目录 一.Grid布局简介 二.Grid布局的一些概念 三. 容器元素属性 1. grid-template-* 1.1 网格行和列的设置 1.2 repeat的使用 1.3 使用fr 1.4 aut ...

  6. WPF CheckBox样式 ScrollViewer样式 WrapPanel、StackPanel、Grid布局

    本节讲述布局,顺带加点样式给大家看看~单纯学布局,肯定是枯燥的~哈哈 那如上界面,该如何设计呢? 1.一些布局元素经常用到.Grid StackPanel Canvas WrapPanel等.如上这种 ...

  7. WPF中Grid布局

    WPF中Grid布局XMAl与后台更改,最普通的登录界面为例. <Grid Width="200" Height="100" > <!--定义 ...

  8. WPF Grid布局

    本节讲述布局,顺带加点样式给大家看看~单纯学布局,肯定是枯燥的~哈哈 那如上界面,该如何设计呢? 1.一些布局元素经常用到.Grid StackPanel Canvas WrapPanel等.如上这种 ...

  9. WPF教程六:布局之Grid面板

    Grid:网格面板 Grid顾名思义就是“网格”,以表格形式布局元素,对于整个面板上的元素进行布局,它的子控件被放在一个一个事先定义好的小格子里面,整齐配列. Grid和其他各个Panel比较起来,功 ...

随机推荐

  1. 解决msi文件在XP上安装未完成

    下载Ocra工具,然后删除"DIRCA_CheckFx"和"VSDCA_VsdLaunchConditions"这两个Action即可.第一步,下载并打开Ocr ...

  2. Lubuntu系统中java,tomcat的环境搭建(virtualbox中)

    一.安装Lubuntu系统 这一步没什么说的,到官网下载镜像,在virtualbox中安装即可安装时就已经可以选择安装源,当然,选中国的设置环装网络,可将该虚拟机设立为网络上的独立IP,和物理机间可以 ...

  3. python脚本6_打印菱形

    #输入菱形最宽的行所在的行数,打印菱形 m = int(input(">>>")) for n in range(m): print(" "* ...

  4. Python 基础教程(有经典的例子)

    http://www.runoob.com/python/os-listdir.html

  5. Pycharm-professional-2017.2.3破解安装

    初次接触Python,大神推荐使用PyCharm IDE工具,作为小白初生牛犊不怕虎,上手就来最新版的,这也许不是最好的选择,但在以后慢慢琢磨深入之后,会选择适合自己的版本,现参考把安装过程分享出来. ...

  6. UVALive-5713 Qin Shi Huang's National Road System (次小生成树)

    题目大意:有n个城市,要修一些路使得任意两个城市都能连通.但是有人答应可以不计成本的帮你修一条路,为了使成本最低,你要慎重选择修哪一条路.假设其余的道路长度为B,那条别人帮忙修的道路两端城市中的总人口 ...

  7. lspci能看到ifconfig -a看不到网卡

    随着宽带技术的快速发展,服务器使用万兆网卡的概率越来越高.最近装了几台服务器都用的万兆网卡,为了图便宜,网卡和模块都是淘宝上买的,这部还真遇到不少问题. 我的服务器都是centos6.4 64位的,网 ...

  8. innerHTML的兼容性

    问题描述: 给定一个表格,thead的内容一致,tbody的内容动态改变(内容,合并单元格等不同) 错误方案: 给tbody定义一个id,然后document.getElementById('id') ...

  9. jstack 分析程序性能

    摘录自:https://www.jianshu.com/p/6690f7e92f27 简要说明下步骤: 1:通过top命令,cpu,占用率较高的进程 2:通过 top -Hp PID 查看该进程中线程 ...

  10. yii2 联系我们发送邮件报错

    为什么会报错,因为国内的邮件服务商要求发送邮件的人和设置的smtp服务器账号要相同,因为联系我们的是用户,也就是发件人是用户,而不是我们配置的邮箱,所有出错. 这里我用了个取巧的办法,发件人改为自己, ...