65.9K
CodeProject is changing. Read more.
Home

WPF - A Constraining Stack Panel

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (14 votes)

Jun 10, 2009

CPOL

2 min read

viewsIcon

43043

downloadIcon

827

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