ElementRecycler for Silverlight and WPF

In Silverlight and WPF, dynamically creating large numbers of child elements in a Panel (such as Canvas) can hit performance pretty hard.  First-time element initialization can be particularly heavy, so it’s worth minimizing that wherever possible.

Here’s a little helper class that will recycle a Panel’s child elements (comments removed for brevity).  Just tell it the number of elements you want to have / keep, and it’ll return them through an enumerator.  Internally it’ll keep track of “unused” elements it’s removed from the Panel and will add them again later if needed.

public class ElementRecycler<T> where T : UIElement, new()
{
    private Panel panel;
    private Stack<T> unused = new Stack<T>();

    public ElementRecycler( Panel panel )
    {
        this.panel = panel;
    }

    public IEnumerable<T> RecycleChildren( int count )
    {
        return RecycleChildren( panel, count, unused ).ToArray();
    }

    public static IEnumerable<T> RecycleChildren( Panel panel, int count, Stack<T> unused )
    {
        var elementEnum = panel.Children.OfType<T>().ToArray().AsEnumerable().GetEnumerator();

        while ( count-- > 0 )
        {
            if ( elementEnum.MoveNext() )
            {
                yield return elementEnum.Current;
            }
            else if ( unused.Count > 0 )
            {
                var recycled = unused.Pop();
                panel.Children.Add( recycled );
                yield return recycled;
            }
            else
            {
                var element = new T();
                panel.Children.Add( element );

                yield return element;
            }
        }

        while ( elementEnum.MoveNext() )
        {
            panel.Children.Remove( elementEnum.Current );
            unused.Push( elementEnum.Current );
        }
    }
}

Here’s a short example of how it could be used:

var ticks = Axis.Ticks.ToArray();
var tickElementEnum = tickRecycler.RecycleChildren( ticks.Length ).GetEnumerator();

foreach ( var tick in Axis.Ticks )
{
    if ( tickElementEnum.MoveNext() )
    {
        var tickElement = tickElementEnum.Current;
        var points = new PointCollection();

        points.Add( tickTransform.Transform( new Point( tick.Position, 0 ) ) );
        points.Add( tickTransform.Transform( new Point( tick.Position, tick.Type == TickType.Major ? 1 : 0.5 ) ) );

        tickElement.Stroke = tickBrush;
        tickElement.Points = points;
    }
}

To see an example of this class at work, take a look here.  ElementRecycler is used for the axis ticks and labels (drag the retirement age to the right to watch the scale change; note it’s still a little buggy and eats memory, but will be fixed soon 🙂 ).

5 Comments

Leave a comment