热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

WPF/UWP的Grid布局竟然有Bug,还不止一个!了解Grid中那些未定义的布局规则

只要你用XAML写代码,我敢打赌你一定用各种方式使(nuè)用(dài)过Grid。不知你有没有在此过程中看到过Grid那些匪夷所思的布局结果呢?本文将带你来看看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 各占一列,其中两边等长,中间稍长。

那么实际布局中各列是怎么分的呢?以下是设计器为我们显示的列宽:

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。


推荐阅读
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
author-avatar
lock2502898047_947
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有