Custom Panels in Silverlight/WPF Part 2: ArrangeOverride

Posted by: Clarity Blogs: ASP.NET, on 21 Aug 2009 | View original | Bookmarked: 0 time(s)

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

Weve 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 values width property. If, however, the Width is set, the finalSize width is the maximum of the Width and the return values 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 dont 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 thats 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>

Dont forget to reference your local namespace in order to get to your WrapPanel control. Next well 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 its 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 weve 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();
    }
}

Now all you have to do is run the code to see how exciting our WrapPanel is! OK, admittedly this is kind of lame. The real fun starts in the next post when we will animate the controls as they rearrange.

Advertisement
Free Agile Project Management Tool from Telerik
TeamPulse Community Edition helps your team effectively capture requirements, manage project plans, assign and track work, and most importantly, be continually connected with each other.
Category: XAML | Other Posts: View all posts by this blogger | Report as irrelevant | View bloggers stats | Views: 2599 | Hits: 22

Similar Posts

  • Designer Support for One-Way navigations in Entity Framework 4 more
  • How To: Silverlight grid hierarchy load on demand using MVVM and RIA services more
  • Telerik Releases New Controls for Silverlight 3 and WPF more
  • Scenarios for WS-Passive and OpenID more
  • Customizing the SharePoint ECB with Javascript, Part 1 more
  • Custom Panels in Silverlight/WPF: Introduction more
  • Custom Panels in Silverlight/WPF Part 1: MeasureOverride more
  • Ruminations on Multi-Tenant Data Architectures more
  • Telerik Launches RadControls for Silverlight 3 for Line-of-Business Application Development more
  • Data Access Guidance more

News Categories

.NET | Agile | Ajax | Architecture | ASP.NET | BizTalk | C# | Certification | Data | DataGrid | DataSet | Debugger | DotNetNuke | Events | GridView | IIS | Indigo | JavaScript | Mobile | Mono | Patterns and Practices | Performance | Podcast | Refactor | Regex | Security | Sharepoint | Silverlight | Smart Client Applications | Software | SQL | VB.NET | Visual Studio | W3 | WCF | WinFx | WPF | WSE | XAML | XLinq | XML | XSD