Andreas Zita
Andreas Zita

Reputation: 7560

WPF: StackPanel with FirstChildFill?

I want a logical and simple way of producing a layout with one control set to fill and the rest to dock. I could use:

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

But its not very intuitive to use. I could also do it like this:

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

But its also quite alot of xaml. What I really want is something like this:

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

How could this be achieved while not having to reverse the items as with DockPanel and not using a fixed number of rows and attached properties as with Grid?

Upvotes: 6

Views: 4482

Answers (3)

Joe White
Joe White

Reputation: 97686

You could use a DockPanel with a StackPanel inside it. The "main" content would be shown last instead of first, but at least the bottom content would be shown in a logical order in your XAML. If you're willing to have the filled content be last, this would be the simplest way to go.

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

Or, if you want it all (including the filled content) to show up in the XAML in the same order it shows up on the screen, you could use a Grid with two rows and a StackPanel in the second row. As you pointed out, grids entail a fair bit of XAML, but the nested StackPanel would save you from having to add a RowDefinition and Grid.Row for each new item.

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

Upvotes: 1

Pavlo Glazkov
Pavlo Glazkov

Reputation: 20746

There is no such panel in WPF that would support this out of the box. If you want this particular behavior, you could create your own panel, but I would NOT advise you to do so, because it is pretty custom and needed not that often (what if want a panel that would have a behavior like MiddleChildFill? Would create your own panel for that case too?).

Usually this kind of layout is achieved using Grid (as you mentioned). Yes, this is true that Grid has a very verbose syntax (you could simplify it using a helper like this), but it gives you the most flexibility. And at the end of the day, this is what counts.

Upvotes: 0

Will Dean
Will Dean

Reputation: 39500

You can always write your own panel with different docking rules. You could use the standard DockPanel implementation (available in the framework source - it doesn't look very complicated) and create something similar with rules you prefer. You might even be able to create a class which derives from DockPanel and overrides ArrangeOverride.

But personally I would just use the dock panel, which does exactly what you want except that you don't like its rules about which member gets to be the fill.

IME grid has a horrible maintenance problem if you insert/delete rows, in that you find yourself endlessly adjusting row numbers - DockPanel is much easier in that regard.

Update:

Here you go, I've denied you the pleasure of doing this yourself - this is just cut-down/reversed version of the framework source:

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);
    }
}

Upvotes: 3

Related Questions