WPF - A Constraining Stack Panel






4.43/5 (14 votes)
Custom stack panel for dealing with scrollable elements

Introduction
The first column in the picture above shows how a regular StackPanel
gives scrollable elements all the space they desire, which may render some elements invisible. Now, this could be fixed by using a Grid
instead, but I wanted an easy interface for dynamically adding, inserting and removing elements, so I took a stab at a custom panel. The second column in the picture shows this custom panel, ConstrainingStackPanel
, which is able to constrain the size of scrollable elements, so that the panel doesn't use more space than available.
Using the Code
The interface is similar to the standard StackPanel
, with the exception of a custom attached property called "Constrain
", which tells the panel to constrain the size of an element, if necessary. Elements which support scrolling should have this property set to true
.
Currently, the panel only supports vertical orientation.
Here's the XAML for the second column in the picture above:
<my:ConstrainingStackPanel Grid.Column="1" Margin="10">
<TextBlock Margin="5">Column 2 - ConstrainigStackPanel</TextBlock>
<Button>a</Button>
<ListBox my:ConstrainingStackPanel.Constrain="true"
ItemsSource="{StaticResource actors}"/>
<Button>b</Button>
<ListBox my:ConstrainingStackPanel.Constrain="true"
ItemsSource="{StaticResource movies}"/>
<Button>c</Button>
</my:ConstrainingStackPanel>
Points of Interest
Most of the work is done in the panel's MeasureOverride
method:
protected override Size MeasureOverride(Size availableSize)
{
// Desired size for this panel to return to the parent
double desiredHeight = 0;
double desiredWidth = 0;
// Desired heights the two 'types' of children
double desiredHeightConstrainableChildren = 0;
double desiredHeightRegularChildren = 0;
_constrainableChildren.Clear();
foreach (UIElement child in InternalChildren)
{
// Let child figure out how much space it needs
child.Measure(availableSize);
if (GetConstrain(child))
{
// Deal with constrainable children later once we know if they
// need to be constrained or not
_constrainableChildren.Add(child);
desiredHeightConstrainableChildren += child.DesiredSize.Height;
}
else
{
desiredHeightRegularChildren += child.DesiredSize.Height;
desiredHeight += child.DesiredSize.Height;
desiredWidth = Math.Max(desiredWidth, child.DesiredSize.Width);
}
}
// If the desired height of all children exceeds the available height, set the
// constrain flag to true
double desiredHeightAllChildren =
desiredHeightConstrainableChildren + desiredHeightRegularChildren;
bool constrain = desiredHeightAllChildren > availableSize.Height;
// Holds the space available for the constrainable children to share
double availableVerticalSpace =
Math.Max(availableSize.Height - desiredHeightRegularChildren, 0);
// Re-measure these children and constrain them proportionally, if necessary, so the
// largest child gets the largest portion of the vertical space available
foreach (UIElement child in _constrainableChildren)
{
if (constrain)
{
double percent =
child.DesiredSize.Height / desiredHeightConstrainableChildren;
double verticalSpace = percent * availableVerticalSpace;
child.Measure(new Size(availableSize.Width, verticalSpace));
}
desiredHeight += child.DesiredSize.Height;
desiredWidth = Math.Max(desiredWidth, child.DesiredSize.Width);
}
return new Size(desiredWidth, desiredHeight);
}
The basic idea is to calculate the amount of space available for the scrollable elements. Once that space has been calculated, the space is then divided up among the scrollable elements. The division of space is done proportionally to the desired sizes of the elements.
Final Points
Currently, only vertical orientation is supported.
The panel works best when the entire size of a scrollable element is actually scrollable, so if a user control with a partial scrollable area is added to the panel, the space allocation may not be accurate. Maybe the panel's interface could be improved to expose more control over how space is allocated to scrollable elements.
Any comments or suggestions are welcome.
History
- 9th June, 2009: Initial version
- 11th June, 2009: Updated demo