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

时间:2015-06-08 21:30:46

标签: 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>

在上文中,除{1}之外的所有ColumnDefinition值都使用Width的默认值,即"*",即“星大小”。一个例外是包含嵌套Grid控件的列,该控件设置为"Auto"

我期望这种方式发挥作用的方式如下:

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

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

incorrectly distributed column widths

如果我没有外部网格,即只需使内部网格成为窗口中唯一的网格,它就可以正常工作:

correctly distributed column widths

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


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

enter image description here

但我真的想更全​​面地了解如何实现这一点,以便在更复杂的场景中,我可以在嵌套的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>

生成此输出:

enter image description here

换句话说,在最坏的情况下,我希望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>

运行程序时,您会看到:

enter image description here

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

enter image description here

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

3 个答案:

答案 0 :(得分:8)

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

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

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

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


与此同时,恕我直言的有用答案包括解决问题的方法。虽然不是我问题本身的实际答案,但它们显然对任何可能遇到问题的人都有帮助。所以我写这个答案来巩固所有已知的解决方案(无论好坏,一个大的&#34;功能和#34; 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>


解决方法#2:

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

这看起来像这样(并且基本上是回答者dub stylee提供的an answer here):

<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>

一个变体(为了简洁起见,我不会在这里包括)将使用一个MultiBinding,它将所有相关控件作为输入并返回集合中最大的MinWidth。当然,然后此绑定将用于每个Width的{​​{1}},以便所有列都明确设置为最大ColumnDefinition

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


解决方法#3:

通过使用MinWidth值的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> ,因此获得该对象的所有常规功能,同时解决关注的特定行为。我希望它不会干扰Grid的任何其他合法使用。

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

尽管如此,这在许多情况下都可以正常使用。


解决方法#4:

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

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

这是一种方法:

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; } 事件处理程序中),然后将其返回值分配给Grid.Loaded属性,以强制将宽度调整到合适的大小以适应所有列的最小所需宽度,同时保持指定的比例。

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


解决方法#5:

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


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

答案 1 :(得分:2)

这是一种解决方法,并没有解释您所描述的行为的原因,但它实现了您所寻找的目标:

<Grid ShowGridLines="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Button HorizontalAlignment="Left" Content="Button" />
    <Grid Grid.Column="1"
          ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0"
                Margin="5"
                MinWidth="{Binding ElementName=Button2, Path=ActualWidth}"
                Content="B"
                x:Name="Button1" />
        <Button Grid.Column="1"
                Margin="5"
                Content="Button"
                x:Name="Button2" />
    </Grid>
</Grid>

screen shot

我必须假设Auto列宽是根据子控件的最小宽度计算的。我无法通过任何文档确认或反驳这一点,但这可能是预期的行为。为了强制Button控件占用相同的大小,您只需将MinWidth属性绑定到另一个ActualWidth的{​​{1}}即可。在此示例中,我仅将Button绑定到Button1.MinWidth以说明您所需的行为。

另外,请忽略Button2.ActualWidth,我没有费心将它们设置为与默认设置不同。

答案 2 :(得分:1)

来自文档(Modern apps) (WPF)

  

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

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