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/UWP 的 Grid 布局竟然有 Bug,还不止一个!了解 Grid 中那些未定义的布局规则的更多相关文章
- CSS Grid 布局完全指南(图解 Grid 详细教程)
CSS Grid 布局是 CSS 中最强大的布局系统.与 flexbox 的一维布局系统不同,CSS Grid 布局是一个二维布局系统,也就意味着它可以同时处理列和行.通过将 CSS 规则应用于 父元 ...
- 第二次前端作业grid布局练习
grid布局 CSS Grid(网格) 布局(又称为 “Grid(网格)” ),是一个二维的基于网格的布局系统,它的目标是完全改变我们基于网格的用户界面的布局方式.CSS 一直用来布局我们的网页,但一 ...
- CSS3 Flex布局和Grid布局
1 flex容器的六个属性 flex实现垂直居中: <div class="box"> <span class="item">< ...
- 5分钟学会 CSS Grid 布局
欢迎加入前端交流群交流知识&&获取视频资料:749539640 这是一篇快速介绍网站未来布局的文章. Grid 布局是网站设计的基础,CSS Grid 是创建网格布局最强大和最简单的工 ...
- CSS学习笔记:grid布局
目录 一.Grid布局简介 二.Grid布局的一些概念 三. 容器元素属性 1. grid-template-* 1.1 网格行和列的设置 1.2 repeat的使用 1.3 使用fr 1.4 aut ...
- WPF CheckBox样式 ScrollViewer样式 WrapPanel、StackPanel、Grid布局
本节讲述布局,顺带加点样式给大家看看~单纯学布局,肯定是枯燥的~哈哈 那如上界面,该如何设计呢? 1.一些布局元素经常用到.Grid StackPanel Canvas WrapPanel等.如上这种 ...
- WPF中Grid布局
WPF中Grid布局XMAl与后台更改,最普通的登录界面为例. <Grid Width="200" Height="100" > <!--定义 ...
- WPF Grid布局
本节讲述布局,顺带加点样式给大家看看~单纯学布局,肯定是枯燥的~哈哈 那如上界面,该如何设计呢? 1.一些布局元素经常用到.Grid StackPanel Canvas WrapPanel等.如上这种 ...
- WPF教程六:布局之Grid面板
Grid:网格面板 Grid顾名思义就是“网格”,以表格形式布局元素,对于整个面板上的元素进行布局,它的子控件被放在一个一个事先定义好的小格子里面,整齐配列. Grid和其他各个Panel比较起来,功 ...
随机推荐
- 解决msi文件在XP上安装未完成
下载Ocra工具,然后删除"DIRCA_CheckFx"和"VSDCA_VsdLaunchConditions"这两个Action即可.第一步,下载并打开Ocr ...
- Lubuntu系统中java,tomcat的环境搭建(virtualbox中)
一.安装Lubuntu系统 这一步没什么说的,到官网下载镜像,在virtualbox中安装即可安装时就已经可以选择安装源,当然,选中国的设置环装网络,可将该虚拟机设立为网络上的独立IP,和物理机间可以 ...
- python脚本6_打印菱形
#输入菱形最宽的行所在的行数,打印菱形 m = int(input(">>>")) for n in range(m): print(" "* ...
- Python 基础教程(有经典的例子)
http://www.runoob.com/python/os-listdir.html
- Pycharm-professional-2017.2.3破解安装
初次接触Python,大神推荐使用PyCharm IDE工具,作为小白初生牛犊不怕虎,上手就来最新版的,这也许不是最好的选择,但在以后慢慢琢磨深入之后,会选择适合自己的版本,现参考把安装过程分享出来. ...
- UVALive-5713 Qin Shi Huang's National Road System (次小生成树)
题目大意:有n个城市,要修一些路使得任意两个城市都能连通.但是有人答应可以不计成本的帮你修一条路,为了使成本最低,你要慎重选择修哪一条路.假设其余的道路长度为B,那条别人帮忙修的道路两端城市中的总人口 ...
- lspci能看到ifconfig -a看不到网卡
随着宽带技术的快速发展,服务器使用万兆网卡的概率越来越高.最近装了几台服务器都用的万兆网卡,为了图便宜,网卡和模块都是淘宝上买的,这部还真遇到不少问题. 我的服务器都是centos6.4 64位的,网 ...
- innerHTML的兼容性
问题描述: 给定一个表格,thead的内容一致,tbody的内容动态改变(内容,合并单元格等不同) 错误方案: 给tbody定义一个id,然后document.getElementById('id') ...
- jstack 分析程序性能
摘录自:https://www.jianshu.com/p/6690f7e92f27 简要说明下步骤: 1:通过top命令,cpu,占用率较高的进程 2:通过 top -Hp PID 查看该进程中线程 ...
- yii2 联系我们发送邮件报错
为什么会报错,因为国内的邮件服务商要求发送邮件的人和设置的smtp服务器账号要相同,因为联系我们的是用户,也就是发件人是用户,而不是我们配置的邮箱,所有出错. 这里我用了个取巧的办法,发件人改为自己, ...