6

我想要一种合乎逻辑且简单的方法来生成布局,其中一个控件集填充,其余控件集停靠。我可以使用:

<DockPanel LastChildFill="True">
  <Button Content="3" DockPanel.Dock="Bottom" />
  <Button Content="2" DockPanel.Dock="Bottom" />
  <Button Content="1" />
</DockPanel>

但是使用起来不是很直观。我也可以这样做:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Button Content="1" Grid.Row="0" />
  <Button Content="2" Grid.Row="1" />
  <Button Content="3" Grid.Row="2" />
</Grid>

但它也相当多的xaml。我真正想要的是这样的:

<StackPanel Fill="None|First|Last">
  <Button Content="1" />
  <Button Content="2" />
  <Button Content="3" />
</StackPanel>

如何在不必像 DockPanel 那样反转项目并且不像 Grid 那样使用固定数量的行和附加属性的情况下实现这一点?

4

3 回答 3

3

您始终可以使用不同的停靠规则编写自己的面板。您可以使用标准的 DockPanel 实现(在框架源代码中可用 - 它看起来并不复杂)并使用您喜欢的规则创建类似的东西。您甚至可以创建一个派生自 DockPanel 并覆盖 ArrangeOverride 的类。

但就我个人而言,我只会使用停靠面板,它完全符合您的要求,只是您不喜欢它关于哪个成员成为填充的规则。

如果您插入/删除行,IME 网格有一个可怕的维护问题,因为您会发现自己无休止地调整行号 - DockPanel 在这方面要容易得多。

更新:

给你,我拒绝你自己做这件事的乐趣——这只是框架源代码的缩减/反转版本:

public class BottomDockingPanel : DockPanel
{
    protected override Size ArrangeOverride(Size arrangeSize)
    {
        UIElementCollection children = InternalChildren;
        int totalChildrenCount = children.Count;

        double accumulatedBottom = 0;

        for (int i = totalChildrenCount-1; i >=0 ; --i)
        {
            UIElement child = children[i];
            if (child == null) { continue; }

            Size childDesiredSize = child.DesiredSize;
            Rect rcChild = new Rect(
                0,
                0,
                Math.Max(0.0, arrangeSize.Width - (0 + (double)0)),
                Math.Max(0.0, arrangeSize.Height - (0 + accumulatedBottom)));

            if (i > 0)
            {
                accumulatedBottom += childDesiredSize.Height;
                rcChild.Y = Math.Max(0.0, arrangeSize.Height - accumulatedBottom);
                rcChild.Height = childDesiredSize.Height;
            }

            child.Arrange(rcChild);
        }
        return (arrangeSize);
    }
}
于 2011-02-28T14:13:59.803 回答
1

您可以在其中使用带有 StackPanel 的 DockPanel。“主要”内容将显示在最后而不是第一个,但至少底部内容将在 XAML 中按逻辑顺序显示。如果您愿意将填充的内容放在最后,这将是最简单的方法。

<DockPanel LastChildFill="True">
  <StackPanel DockPanel.Dock="Bottom">
    <Button Content="Bottom 1" />
    <Button Content="Bottom 2" />
    <Button Content="Bottom 3" />
  </StackPanel>
  <Button Content="Main" />
</DockPanel>

或者,如果您希望所有内容(包括填充的内容)在 XAML 中显示的顺序与其在屏幕上显示的顺序相同,则可以使用具有两行的 Grid 和第二行的 StackPanel。正如您所指出的,网格需要相当多的 XAML,但嵌套的 StackPanel 将使您不必为每个新项目添加 RowDefinition 和 Grid.Row。

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>
  <Button Grid.Row="0" Content="Main" />
  <StackPanel Grid.Row="1">
    <Button Content="Bottom 1" />
    <Button Content="Bottom 2" />
    <Button Content="Bottom 3" />
  </StackPanel>
</Grid>
于 2011-02-28T14:35:12.720 回答
0

WPF 中没有这样的面板可以开箱即用地支持这一点。如果您想要这种特殊的行为,您可以创建自己的面板,但我不建议您这样做,因为它非常自定义并且不需要那么频繁(如果想要一个具有类似 MiddleChildFill 的行为的面板怎么办?会创建你自己的面板也适合这种情况?)。

通常这种布局是使用Grid(正如你提到的)来实现的。是的,确实Grid有一个非常冗长的语法(您可以使用这样的帮助程序来简化它,但它为您提供了最大的灵活性。归根结底,这才是最重要的。

于 2011-02-28T14:21:57.880 回答