Like pancakes...

the random ramblings begin...
in

Custom Panels in Silverlight/WPF Part 2: ArrangeOverride

This is Part 2 of my series of posts dedicated to creating an animating and virtualizing WrapPanel for Silverlight. If you missed the Introduction or Part 1 I suggest you take a look.

Setup

WrapPanel

We’ve already created the WrapPanel class in Part 1, so now all we need to do is add the ArrangeOverride method to the class.

protected override Size ArrangeOverride(Size finalSize)
{
    return base.ArrangeOverride(finalSize);
}

ArrangeOverride

General Info

The ArrangeOverride method takes a Size parameter and returns a Size. The finalSize parameter represents the space allotted to the panel and the Width and Height values range from 0 to positive infinity. The finalSize parameter is determined by the MeasureOverride availableSize parameter and return value using the following process (defined for Width, but equivalent for the Height also).

If the Panel Width, MinWidth and MaxWidth are NaN (i.e. not explicitly set) and the HorizontalAlignment is Center, Left or Right then the Width value is from the MeasureOverride return value. If the Width values are NaN and HorizontalAlignment is Stretch then the Width is the maximum of the MeasureOverride availableSize width and the return value’s width property. If, however, the Width is set, the finalSize width is the maximum of the Width and the return value’s Width.

The ArrangeOverride return value is the size that is then used as the RenderSize. Typically, the finalSize is just returned regardless of the logic in the method.

The guts of the ArrangeOverride method is to physically place each child in the appropriate location. This is accomplished using the UIElement.Arrange(Rect) method. The Rect parameter passed into Arrange defines the X and Y coordinates of the top left corner of the child, relative to the parent, while the Width and Height values define the Width and Height of the area the child element should take up.

In my experience the ArrangeOverride method contains similar logic to the MeasureOverride as you have to iterate through the children and place them, tracking how much space has been used as you go along. The main difference is that UIElement.Measure is called in MeasureOverride and UIElement.Arrange is called in ArrangeOverride.

WrapPanel

For our panel the ArrangeOverride method will simply arrange each child element into the appropriate location using the same wrapping logic described in Part 1. The differences are that we don’t have to track the maximum width and, as mentioned above, we call UIElement.Arrange instead of UIElement.Measure.

protected override Size ArrangeOverride(Size finalSize)
{
    Size sizeSoFar = new Size(0, 0);

    foreach (UIElement child in Children)
    {
        child.Arrange(new Rect(sizeSoFar.Width, sizeSoFar.Height, 
                               child.DesiredSize.Width, child.DesiredSize.Height));

        if (sizeSoFar.Width + child.DesiredSize.Width >= finalSize.Width)
        {
            sizeSoFar.Height += child.DesiredSize.Height;
            sizeSoFar.Width = 0;
        }
        else
        {
            sizeSoFar.Width += child.DesiredSize.Width;
        }
    }

    return finalSize;
}

The same assumptions we made in Part 1 still apply here.

Calling Arrange

So, when does ArrangeOverride get called? Well, every time MeasureOverride is called ArrangeOverride is called after. So we can force an Arrange by calling UIElement.InvalidateMeasure(). However, if we know the measure is accurate and we want to skip what can be a complex process we can call UIElement.InvalidateArrange() directly instead. Also, just as with MeasureOverride, a custom defined DependencyProperty can register such that it includes FrameworkPropertyMetadataOptions.AffectsArrange (WPF only).

Completing the Example

So now our WrapPanel implementation has a MeasureOverride and ArrangeOverride and is complete. We built our test control (NumberBox) in Part 1 – so that’s ready to go. All we need now is to add the WrapPanel to a parent and add some code to add and remove children.

When creating a new Silverlight 3 project in Visual Studio I asked it to create a test page to host the Silverlight at build time. This UserControl is autocreated as Page.xaml and is has straightforward XAML:

<UserControl x:Class="Clarity.Demo.CustomPanel.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:Clarity.Demo.CustomPanel"
    MaxWidth="500" MaxHeight="500">
    <Grid x:Name="LayoutRoot" Background="White">
            <local:WrapPanel x:Name="wrapPanel" />
    </Grid>
</UserControl>

Don’t forget to reference your local namespace in order to get to your WrapPanel control. Next we’ll create a DispatcherTimer to add children to our WrapPanel. I implemented this in the Page constructor, but it a real-life scenario would life be populated via binding or user interaction. I place each new child control at the beginning of the Children collection so you can see each control is arranged to it’s new position with the addition of each subsequent child. Also, for the sake of seeing our example a little cleaner, I clear the Children when we’ve got 50 of them.

public partial class Page : UserControl
{
    private DispatcherTimer _timer = new DispatcherTimer();
    private int _count = 0;

    public Page()
    {
        InitializeComponent();
        PopulatePanel();
    }

    private void PopulatePanel()
    {
        _timer.Interval = TimeSpan.FromSeconds(1);
        _timer.Tick += (sender, args) =>
            {
                if (_count > 50)
                {
                    wrapPanel.Children.Clear();
                    _count = 0;
                }
                wrapPanel.Children.Insert(0, (new NumberBox(_count++)));
            };
        _timer.Start();
    }
}

And this is what it looks like:

Get Microsoft Silverlight

OK, admittedly this is kind of lame. The real fun starts in the next post when we will animate the controls as they rearrange.

Comments

Custom Panels in Silverlight/WPF Part 1: MeasureOverride - Like pancakes... said:

Pingback from  Custom Panels in Silverlight/WPF Part 1: MeasureOverride - Like pancakes...

# August 24, 2009 8:46 AM

Samoteph said:

Excellent Post !

# April 23, 2010 1:50 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)