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?
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);
}
}
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With