寻求关于WPF Grid列跨度行为的解释

9
我在http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5c7f5cdf-4351-4969-990f-29ce9ec84b87/上提出了一个问题,但仍然缺乏关于奇怪行为的好解释。
运行以下XAML代码,可以看到列0中的TextBlock宽度大于100,即使已将该列设置为宽度100。我认为这种奇怪的现象可能与它被包含在ScrollViewer中有关,但我不知道原因。如果在列上设置MaxWidth,则可以正常工作,但设置Width则不行。
以下是需要回答的问题:
1. 为什么列0的宽度没有被遵守?
2. 当您删除滚动视图时,列大小调整的行为为什么会不同?
非常感谢任何解释!这对我来说真是个难题。
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300">
    <ScrollViewer HorizontalScrollBarVisibility="Auto" >
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="textBlock" Text="{Binding ElementName=textBlock, Path=ActualWidth}" />
            <TextBlock Text="column 1" Grid.Column="1" />
            <TextBlock Grid.Row="1" Grid.ColumnSpan="3" Text="text here that is wider than the first two columns combined" />
        </Grid>
    </ScrollViewer>
</Window>
4个回答

7
这是一个非常好的问题,考验了我们直觉的极限。它揭示了网格布局逻辑的实现细节。
100 的宽度没有被保留,因为:
1. 第三列中没有任何东西使网格给它一个宽度。 2. 第二行中的长文本比前两列宽。 3. 当网格的宽度不受约束或不由其父级设置时,其布局逻辑显然会拉伸第一列而不是最后一列。
通过在第一列上放置 MaxWidth,您可以限制网格的布局逻辑,这样它就会移到第二列并将其拉伸。您会注意到,在这种情况下,它将比 100 宽。
但是,一旦网格的宽度设置为特定值或受其父级约束(例如在窗口中没有 ScrollViewer 时),网格的宽度就有一个特定的值,即使第三列为空,也会设置其宽度。现在网格的自动调整大小代码被停用,它不再拉伸任何列以尝试挤入该文本。您可以通过在网格上放置特定宽度来查看这一点,即使它仍在 ScrollViewer 中。
编辑:现在我读了你原帖中 MSDN 支持答案的答案,我相信它是正确的,这意味着这可能是附加属性实现的结果,而不是网格本身。然而,原则是相同的,希望我的解释足够清楚,能理解这里的微妙之处。

感谢您提供周到的答案。正如我在此主题中早些时候的评论中所说,这使我想知道:1. 为什么MaxWidth比固定宽度更受重视?2. 您认为这是Grid控件的缺陷还是MSDN上的Grid控件文档的缺陷?我没有看到MSDN中有任何描述此行为的内容。我很想将其记录为缺陷。 - Dale Barnard
1
这不是“尊重”MaxWidth而不是Width的问题。一个是约束,而另一个是初始起始值。如果存在错误,则是ColSpan附加属性影响布局的天真方式。第三列根本没有宽度,直到这个ColSpan项目出现,即使它的宽度为“*”,当没有足够的宽度来适应ColSpan项目时,Grid也会从左到右扩展列。直觉上,这感觉不对,但我不会称其为错误。这肯定是一个有漏洞的抽象。 - Jerry Bullard
1
微软可能会更改布局行为,寻找宽度为“*”的列并首先加宽(而不是从左到右加宽列)。然而,可能有很多代码依赖于当前的行为。微软很可能只是将其定义为标准并继续前进。但由于这是如此反直觉和看似错误,他们可能会给我们带来惊喜。 - Jerry Bullard
我同意,由于人们可能依赖当前的行为,所以他们不会改变它。我只是建议他们记录这种不直观的行为。此外,我在MSDN文档中没有看到MaxWidth是一个约束条件,但Width是一个起始值。你有在哪里看到过这个记录吗?还是你只是从证据中推断出来的?感谢您的评论! - Dale Barnard

3

下面这个例子展示了使用 Canvas 而不是 ScrollViewer 会出现的问题:

<Canvas>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" x:Name="textBlock1" Text="{Binding ElementName=textBlock1, Path=ActualWidth}"/>
        <TextBlock Grid.Column="1" x:Name="textBlock2" Text="{Binding ElementName=textBlock2, Path=ActualWidth}"/>
        <TextBlock Grid.Row="1" Grid.ColumnSpan="3" Width="300"/>
    </Grid>
</Canvas>

此示例表明,当给定无限空间时,前两列会被错误地扩展33%。我现在没有工作参考来源来调试这个问题,因为SP1破坏了.NET4参考源,但坦率地说,在源文件中一行行定位并不会帮助您,因此我们不要这样做。
相反,我们将同意这肯定是一个错误,并且我们可以通过将Grid.MaxWidth设置为逐渐更大的值来证明它是一个错误,两列的宽度仍然保持为100,无论它有多大。但是,如果您将Grid放置在Canvas内部,并且不设置Grid.MaxWidth,则在测量期间的值将为double.PositiveInfinity,该值将产生133的列宽度。因此,我们可以推测,在正无穷大小约束的特殊情况下,在列大小计算期间未正确处理。
幸运的是,同样的实验提供了一个简单的解决方法:当Grid在允许其无限制的空间的另一个控件内使用时(如ScrollViewerCanvas),只需为Grid.MaxWidth提供一个荒谬的大值即可。我建议您使用类似以下内容的东西:
<Grid MaxWidth="1000000">

这种方法通过防止大小约束具有正无穷大的问题值,从而避免了错误,并在实际上实现了相同的效果。
但是这个:
<Grid MaxWidth="{x:Static sys:Double.PositiveInfinity}">

会触发该漏洞。


你发现关于将Grid.MaxWidth设置为非NaN值的事情很有趣。我想我最好将此报告为缺陷。 - Dale Barnard

3
简短回答:
这是由以下因素的组合造成的:
1. 存在 ScrollViewer,它允许网格(如果愿意)采取任何所需大小。
2. 网格没有明确的宽度。
3. 一列(Column 2)其宽度未指定,将其设置为 1*,使其最终大小取决于网格和其他列的大小。
4. TextBlock 跨越三列。

如果您:
1. 删除 ScrollViewer,则网格仅允许增长到窗口的客户区域(在您的示例中约为 278),并且长文本块必须适应此宽度,否则会被修剪。
2. 设置网格的明确宽度,这再次修剪了文本块以适应。
3. 设置列 2 的明确宽度,为网格提供固定宽度(100+100+width_of_col2),这再次修剪了文本块以适应。
4. 删除 colspan,不包含它并具有定义的固定宽度的列将采用该宽度。

这里是发生的事情:
这只是对测量和排列传递的粗略而不精确的解释,但应该能给出一个公平的想法。

首先,col0为100,col1为100,col2为0。基于此,网格的大小将为100+100+0=200。当网格请求其子元素(文本块)进行测量时,它会发现前两个文本块适合其列的宽度。然而,第三个文本块需要288。由于网格没有定义任何宽度,并且在滚动查看器内,如果其中一个子元素需要它,则可以增加其大小。现在,网格必须将其大小从200增加到288(即增加了88)。这意味着每个跨越该文本块的列(所有三个列)将扩展29像素/3≈29像素。这使得col0=100+29=129,col1=100+29=129,col2=0+29。

试试这个:
包含一个矩形,将其放在col2中,并将矩形的宽度设置为20。

这是正在发生的事情:
首先,col0和col1各自需要少于100的文本块,因此它们都为100。 col2需要20,因为其中的矩形需要它。基于此,网格的宽度将为100+100+20=220。但是,由于跨越列的文本块,网格必须将其大小从220增加到288(即增加了68)。这意味着每个跨越该文本块的列(所有三个列)将扩展68/3≈23像素。这使得col0=100+23=123,col1=100+23=123,col2=20+23=43。

HTH.


感谢您的周到回答。这让我想知道:1.为什么MaxWidth比固定宽度更受重视?2.您认为这是Grid控件或MSDN上Grid控件文档的缺陷吗?我在MSDN上没有看到任何接近描述此行为的内容。我很想将其记录为缺陷。(由于同样适用于下一个答案,我在下一个答案中也重复了此评论。) - Dale Barnard
不,我不认为这是一个缺陷。我认为宽度和高度是一种请求值,并且可能无法被尊重,考虑到测量和排列传递是递归的。这就是为什么提供MaxWidth和MaxHeight(甚至MinWidth和MinHeight)来帮助用户设置约束条件。 - publicgk
回答问题1的内容来自MSDN:FrameworkElement类公开了四个属性,用于描述元素的宽度特性。这四个属性可能会发生冲突,当它们发生冲突时,优先考虑MinWidth值,其次是MaxWidth值,再次是Width值。第四个属性ActualWidth是只读的,并报告由布局过程与之交互确定的实际宽度。 - publicgk
MSDN上似乎没有明确说明Width是“一种请求值”,而不是绝对值,这暗示了文档缺陷。我也不清楚你关于优先级的其他评论的意图。如果没有指定MaxWidth和MinWidth,那么Width的优先级是什么?感谢您的回复。 - Dale Barnard
啊,我猜原因是我们正在讨论一个 ColumnDefinition 的宽度,而它不是 FrameworkElement 而是 FrameworkContentElement。抱歉混淆了。答案在于 Grid 是如何实现的(以及为什么这样)。是时候提供赏金来吸引专家回答了。 :) - publicgk

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接