Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: StackPanel with FirstChildFill?

Tags:

c#

layout

wpf

fill

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?

like image 834
Andreas Zita Avatar asked Feb 28 '11 13:02

Andreas Zita


2 Answers

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);
    }
}
like image 161
Will Dean Avatar answered Oct 23 '22 14:10

Will Dean


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>
like image 1
Joe White Avatar answered Oct 23 '22 13:10

Joe White