WPF item container recycling
Asked Answered
P

1

2

I want to implement a custom Canvas that recycles containers when used as an ItemsPanel. So I derived from VirtualizingPanel and override the ArrangeOverride and MeasureOverride. I am doing the generation in MeasureOverride like this:

var children = base.InternalChildren;

var itemsControl = ItemsControl.GetItemsOwner(this);
var itemsCount = itemsControl.Items.Count;

IItemContainerGenerator generator = itemsControl.ItemContainerGenerator;

var startPos = generator.GeneratorPositionFromIndex(0);

using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
{
    for (int i = 0; i < itemsCount; i++)
    {
        bool isNewlyRealized;

        var child = generator.GenerateNext(out isNewlyRealized) as UIElement;

        if (isNewlyRealized)
        {
            base.AddInternalChild(child);
            generator.PrepareItemContainer(child);
        }

        child.Measure(constraint);
    }
}

What I don't know is how to make the recycling. I tried something like the following:

protected override void OnItemsChanged(object sender, ItemsChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Remove:
        case NotifyCollectionChangedAction.Replace:
        case NotifyCollectionChangedAction.Move:
            IRecyclingItemContainerGenerator generator = ItemsControl.GetItemsOwner(this).ItemContainerGenerator;
            generator.Recycle(e.Position, e.ItemUICount);
            RemoveInternalChildRange(e.Position.Index, e.ItemUICount);
            break;
    }
}

But it doesn't work. Any idea how to do this?

Papageno answered 22/3, 2010 at 15:43 Comment(0)
I
4

Look here: http://blogs.claritycon.com/blogs/lee_roth/default.aspx

I make recycling the following way:

In OnItemsChanged, I only call RemoveInternalChildRange:

protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
      switch (args.Action)
      {
        case NotifyCollectionChangedAction.Remove:
        case NotifyCollectionChangedAction.Replace:
          RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
          break;
        case NotifyCollectionChangedAction.Move:
          RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
          break;
      }
    }

In Measure override, I first add new items, then I remove the old ones. If you use recycling you have to know that the new-Flag that you get by calling GenerateNext is also false if you get a recycled container.

Here we add new Items:

GeneratorPosition start = ItemContainerGenerator.GeneratorPositionFromIndex(iFirstItemIndex);
      int iChildIndex = (start.Offset == 0) ? start.Index : start.Index + 1;
      using (ItemContainerGenerator.StartAt(start, GeneratorDirection.Forward, true))
      {
        for (int i = iFirstItemIndex; i <= iLastItemIndex; i++, iChildIndex++)
        {
          bool bNew;
          UIElement element = (UIElement)ItemContainerGenerator.GenerateNext(out bNew);
          //If we get a new instance
          if (bNew)
          {
            if (iChildIndex >= Children.Count) AddInternalChild(element);
            else InsertInternalChild(iChildIndex, element);
            ItemContainerGenerator.PrepareItemContainer(element);
          }
          //If we get a recycled element
          else if (!InternalChildren.Contains(element))
          {
            InsertInternalChild(iChildIndex, element);
            ItemContainerGenerator.PrepareItemContainer(element);
          }
          element.Measure(...);
        }
      }

After adding Items we remove the old Items:

for (int i = Children.Count - 1; i >= 0; i--)
      {
        GeneratorPosition childGeneratorPosition = new GeneratorPosition(i, 0);
        int iIndex = ItemContainerGenerator.IndexFromGeneratorPosition(childGeneratorPosition);
        if (iIndex < iFirstItemIndex || iIndex > iLastItemIndex)
        {
          //remove() calls ItemContainerGenerator.remove() OR recycle(). Both works.
          remove(childGeneratorPosition, 1);
          RemoveInternalChildRange(i, 1);
        }
      }

I hope I could help you.

Ingar answered 18/5, 2010 at 9:40 Comment(2)
Actually, if you're recycling, you should call your 'cleanup' code (your last code block) before generating the new containers. This is because you want to recycle the available containers before they are needed, not after. Saves a few unneeded creations.Maximo
The link is no longer available.Dex

© 2022 - 2024 — McMap. All rights reserved.