为什么嵌套网格中的星形大小不起作用?

Pet*_*iho 15 c# wpf

考虑以下XAML:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>
Run Code Online (Sandbox Code Playgroud)

在上文中,所有ColumnDefinition除了一个值,使用默认值Width,也就是"*",即"明星上浆".一个例外是包含嵌套Grid控件的列,该控件设置为"Auto".

我期望它的工作方式如下:

  • 外部Grid根据其内容的需要调整第二列的大小,然后将控件的剩余宽度分配给第一列.
  • 内部Grid将其可用空间均匀分布到两列.毕竟,它们都设置为使用星形大小,并且星形大小应该将GridLength属性(在这种情况下为宽度)设置为可用空间的加权分布.此内部的最小布局大小Grid(外部Grid计算其第二列宽度所需)是均匀分布宽度,星形大小的列的总和(即在这种情况下,是列的宽度的两倍,最广泛的内容).

但相反,嵌套网格的列宽根据计算出的每个按钮的最小大小进行设置,两个星大小的列之间没有明显的加权关系(为清晰起见,显示了网格线):

错误分布的列宽

如果我没有外部网格,它只是按预期工作,即只是使内部网格成为窗口中唯一的网格:

正确分布的列宽

两列被强制为相同的大小,当然左手按钮被拉伸以适应其包含单元格的大小(这就是我想要的......最终目标是让这两个按钮具有相同的宽度,网格列提供了完成该布局的布局.


在这个特定的例子中,我可以UniformGrid用作解决方法,强制均匀分布列宽.这就是我想要它实际看起来的样子(UniformGrid没有ShowGridLines属性,所以你只需要想象两个最右边按钮之间的假想线):

在此输入图像描述

但我真的想更全​​面地了解如何实现这一点,以便在更复杂的场景中我能够在嵌套Grid控件中使用星型大小.


似乎不知何故,被包含在另一个Grid控件的单元格中正在改变为内部Grid控件计算星形大小的方式(或者防止星形大小完全没有任何影响).但为什么会这样呢?我是否遗漏了(再次)WPF的一些深奥的布局规则,将其解释为"按设计"行为?或者这仅仅是框架中的错误?


更新:

我理解Ben的答案意味着考虑每列的最小尺寸,星形尺寸应仅分布剩余空间.但这不是人们在其他场景中看到的.

例如,如果包含内部网格的列已明确调整大小,则对内部网格的列使用星号大小会导致列的大小均匀,就像我期望的那样.

即这个XAML:

<Grid ShowGridLines="True">
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <!--
    <ColumnDefinition Width="Auto"/>
    -->
    <ColumnDefinition Width="60"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1" ShowGridLines="True">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>
Run Code Online (Sandbox Code Playgroud)

产生这个输出:

在此输入图像描述

换句话说,在最坏的情况下,我希望WPF首先计算内部网格的最小尺寸而不考虑星形尺寸(例如,如果短按钮需要10个像素而长按钮需要70个,那么总宽度将是80),然后仍然均匀地分布列宽(即在10/70示例中,每列将卷起40个像素,截断更长的按钮,类似于上面的图像).

为什么星形尺寸有时会在列间均匀分布宽度,有时不是?


更新#2:

下面是一个简单的例子,它清楚而明确地显示了WPF如何根据是否是计算Grid宽度的方式来处理星形尺寸,或者您是:

<Window x:Class="TestGridLayout2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        SizeToContent="WidthAndHeight"
        Title="MainWindow">
  <Window.Resources>
    <Style TargetType="Border">
      <Setter Property="BorderBrush" Value="Black"/>
      <Setter Property="BorderThickness" Value="1"/>
    </Style>
    <Style TargetType="TextBlock">
      <Setter Property="FontSize" Value="24"/>
    </Style>
  </Window.Resources>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="3*"/>
    </Grid.ColumnDefinitions>

    <Border Grid.Column="0"/>
    <Border Grid.Column="1"/>

    <StackPanel>
      <TextBlock Text="Some text -- one"/>
      <TextBlock Text="Some text -- two"/>
      <TextBlock Text="Some text -- three"/>
    </StackPanel>
    <StackPanel Grid.Column="1">
      <TextBlock Text="one"/>
      <TextBlock Text="two"/>
      <TextBlock Text="three"/>
    </StackPanel>
  </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

运行程序时,您会看到:

在此输入图像描述

WPF忽略了星形大小,将每个列宽设置为最小值.如果您只是单击窗口边框,就好像要调整窗口大小(您甚至不必在任何地方实际拖动边框),Grid布局将重做,您将得到:

在此输入图像描述

此时,应用星形大小(正如我所期望的那样),并根据XAML声明对列进行比例调整.

Pet*_*iho 8

我同意Ben的回答强烈暗示这里可能发生的事情:

正如Ben指出的那样,为了计算内部Grid物体的最小宽度,WPF忽略了星形尺寸(也许是合理的......我认为存在进行诚实辩论的空间,但显然这是一种可能且合法的设计).

什么不清楚(以及Ben没有回答)是为什么这应该意味着当计算内部的列宽时,也会忽略星形尺寸Grid.由于在外部施加宽度时,比例宽度将导致内容被截断,如果需要保留这些比例,为什么在根据内容的最小所需大小自动计算宽度时不会发生同样的事情.

即我仍在寻找我的问题答案.


与此同时,恕我直言的有用答案包括解决问题的方法.虽然不是我问题本身的实际答案,但它们显然对任何可能遇到问题的人都有帮助.所以我写这个答案来巩固所有已知的解决方案(无论好坏,WPF的一个重要特征是,似乎总有至少几种不同的方法来实现相同的结果:)).


解决方法#1:

使用UniformGrid而不是Grid内部网格对象.此对象没有所有相同的功能Grid,当然也不允许任何列具有不同的宽度.因此它可能在所有情况下都没用.但它很容易解决这里的简单问题:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <UniformGrid Rows="1" Columns="2" Grid.Column="1">
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </UniformGrid>
</Grid>
Run Code Online (Sandbox Code Playgroud)


解决方法#2:

MinWidth将较小内容对象的属性(例如,此处,Button网格中的第一个)绑定到较大内容对象的ActualWidth属性.这当然需要知道哪个对象具有最大宽度.在本地化方案中,这可能会有问题,因为除了文本资源之外,XAML还必须进行本地化.但无论如何这有时是必要的,所以...... :)

这看起来像这样(并且本质上是回答者dub stylee提供的答案):

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"
            MinWidth="{Binding ElementName=button2, Path=ActualWidth}"/>
    <Button x:Name="button2" Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>
Run Code Online (Sandbox Code Playgroud)

一个变体(为简洁起见,我将不包括在这里)将使用一个MultiBinding将所有相关控件作为输入并返回最大MinWidth的集合.当然,然后将这个绑定用于Width每个绑定ColumnDefinition,以便将所有列显式设置为最大MinWidth.

绑定方案还有其他变体,具体取决于您要使用和/或设置的宽度.这些都不是理想的,不仅仅是因为潜在的本地化问题,还因为它在XAML中嵌入了更明确的关系.但在许多情况下,它将完美地运作.


解决方法#3:

通过使用值的SharedSizeGroup属性,ColumnDefinition可以明确强制一组列具有相同的宽度.在这种方法中,Grid然后在此基础上计算内部对象的最小宽度,当然宽度也相同.

例如:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
  <Button Content="Button" HorizontalAlignment="Left"/>
  <Grid Grid.Column="1" IsSharedSizeScope="True">
    <Grid.ColumnDefinitions>
      <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
      <ColumnDefinition SharedSizeGroup="buttonWidthGroup"/>
    </Grid.ColumnDefinitions>
    <Button Content="B" Margin="5"/>
    <Button Content="Button" Grid.Column="1" Margin="5"/>
  </Grid>
</Grid>
Run Code Online (Sandbox Code Playgroud)

这种方法允许人们使用Grid,因此获得该对象的所有正常特征,同时解决关注的特定行为.我希望它不会干扰任何其他合法使用SharedSizeGroup.

但是,它确实意味着"Auto"尺寸大小,并且排除"*"(明星)尺寸.在这种特殊情况下,这不是一个问题,但是在解决方法的情况下UniformGrid,它在尝试将其与其他大小调整结合时确实限制了一个选项.例如,使用第三列"Auto"并希望SharedSizeGroup列占用剩余的空间Grid.

尽管如此,这在许多情况下都可以正常工作而不会有任何问题.


解决方法#4:

我最后重新审视了这个问题,因为我遇到了一个关于主题的变体.在这种情况下,我正在处理一种情况,我希望对于正在调整大小的列具有不同的比例.所有上述变通方法都假设列大小相等.但我希望(例如)一列有25%的空间,另一列有75%的空间.和以前一样,我希望它的总大小Grid能够容纳所有列所需的最小宽度.

这个解决方法涉及简单地自己做我觉得WPF应该做的计算.即,获取每列内容的最小宽度以及指定的比例大小,计算Grid控件的实际宽度.

这是一个可以做到这一点的方法:

private static double ComputeGridSizeForStarWidths(Grid grid)
{
    double maxTargetWidth = double.MinValue, otherWidth = 0;
    double starTotal = grid.ColumnDefinitions
        .Where(d => d.Width.IsStar).Sum(d => d.Width.Value);

    foreach (ColumnDefinition definition in grid.ColumnDefinitions)
    {
        if (!definition.Width.IsStar)
        {
            otherWidth += definition.ActualWidth;
            continue;
        }

        double targetWidth = definition.ActualWidth / (definition.Width.Value / starTotal);

        if (maxTargetWidth < targetWidth)
        {
            maxTargetWidth = targetWidth;
        }
    }

    return otherWidth + maxTargetWidth;
}
Run Code Online (Sandbox Code Playgroud)

此代码找到的最小宽度仍然可以容纳列的最小宽度和比例大小的每个星形大小的列,以及不使用星形大小的其余列.

您可以在适当的时间调用此方法(例如,在Grid.Loaded事件处理程序中),然后将其返回值分配给Grid.Width属性,以将宽度强制为正确的大小,以适应所有列的最小所需宽度,同时保持指定的比例.

对于行高,类似的方法也会做同样的事情.


解决方法#5:

我想它指出:人们可以简单地明确指定Grid大小.这仅适用于预先知道大小的内容,但在许多情况下,这也很好.(在其他情况下,它会截断内容,因为即使指定的大小太小,当它明确时,WPF继续并应用星号大小比例).


如果他们意识到与已经显示的那些有很大不同的好的解决方法,我鼓励其他人为这个答案增加额外的解决方法.或者,随意用你的解决方法发布另一个答案,我会(在我最早的方便:) :)自己添加.


Ben*_*igt 1

从文档(现代应用程序) (WPF)

starSizing 一种约定,您可以通过该约定调整行或列的大小以占用网格中的剩余可用空间。

它不会导致请求更多的最小空间,它会影响超过最小空间的分配。