Search code examples
c#wpflayoutfill

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?


Solution

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