Jerry Nixon
Jerry Nixon

Reputation: 31813

Inserting an item in an ItemsControl

Here are the requirements.

In my ItemsControl (you can use a ListView if it helps you to consider the scenario). I want to inject, not a record, but a unique DataTemplate into an arbitrary indexed location in my list.

For example, I might want to insert it into the first position, index 0, or the third position, index 2, or perhaps even have the logic to insert it into the last position, index count-1.

I will need to sub-class ListView to accomplish this, I realize. That being said, I could easily create the SpecialItemTemplate, SpecialItemIndex DP properties to have the values.

Added requirements:

  1. Don't require a special type of collection
  2. Don't require manipulating the existing data
  3. Allow the IsHitTestVisible to be variable, too

Any ideas how to accomplish this feat (in WinRT)?

Upvotes: 1

Views: 232

Answers (1)

Martin
Martin

Reputation: 6146

Here is a solution that is basically a Behavior with a Template property which can be attached to any ItemsControl. I tested it with virtualizing and non-virtualizing panels, works for both cases. If you think the code is convoluted... well I can't disagree, wrote it a while back and I can't remember what reasons I had to write it the way it ended up.

Usage:

<ListBox ItemsSource="{Binding Persons}">

    <Interactivity:Interaction.Behaviors>
        <AlternativeItemTemplate Index="42">
            <DataTemplate>
                ...foo...
            </DataTemplate>
        </AlternativeItemTemplate>
    </Interactivity:Interaction.Behaviors>

    <ListBox.ItemTemplate>
        <DataTemplate>
            ...bar...
        </DataTemplate>
    </ListBox.ItemTemplate>

</ListBox>

and the classes:

[ContentProperty( "ItemTemplate" )]
public class AlternativeItemTemplate : ItemContainerDecorator
{
    public DataTemplate ItemTemplate
    {
        get { return (DataTemplate) GetValue( ItemTemplateProperty ); }
        set { SetValue( ItemTemplateProperty, value ); }
    }

    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.Register( "ItemTemplate", typeof( DataTemplate ), typeof( AlternativeItemTemplate ), new PropertyMetadata( null ) );

    public int Index
    {
        get { return (int) GetValue( IndexProperty ); }
        set { SetValue( IndexProperty, value ); }
    }

    public static readonly DependencyProperty IndexProperty =
        DependencyProperty.Register( "Index", typeof( int ), typeof( AlternativeItemTemplate ), new PropertyMetadata( -1 ) );

    protected override void OnContainersChanged()
    {
        if (!AssociatedObject.Items.Any() || Index < 0 || Index >= AssociatedObject.Items.Count)
        {
            ItemContentPresenter = null;
            ItemContentControl = null;
            m_overwrittenTemplate = null;
            return;
        }

        TryUpdateItem( ItemContainerGenerator.ContainerFromItem( AssociatedObject.Items[Index] ) );
    }

    private ContentPresenter ItemContentPresenter { get; set; }
    private ContentControl ItemContentControl { get; set; }
    private DataTemplate m_overwrittenTemplate;

    private void TryUpdateItem( DependencyObject itemContainer )
    {
        if (itemContainer == null)
        {
            ResetOverwrittenTemplate();
        }

        var containerAsPresenter = itemContainer as ContentPresenter;
        if (containerAsPresenter != null) UpdateItemContentPresenter( containerAsPresenter );
        else
        {
            var containerAsControl = itemContainer as ContentControl;
            if (containerAsControl != null) UpdateItemContentControl( containerAsControl );
        }
    }

    private void ResetOverwrittenTemplate()
    {
        if (ItemContentPresenter != null)
            ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;

        if (ItemContentControl != null)
            ItemContentControl.ContentTemplate = m_overwrittenTemplate;

        ItemContentPresenter = null;
        ItemContentControl = null;

        m_overwrittenTemplate = null;
    }

    private void UpdateItemContentPresenter( ContentPresenter container )
    {
        if (ItemContentPresenter != null)
            ItemContentPresenter.ContentTemplate = m_overwrittenTemplate;

        ItemContentPresenter = container;
        m_overwrittenTemplate = ItemContentPresenter.ContentTemplate;
        ItemContentPresenter.ContentTemplate = ItemTemplate;
    }

    private void UpdateItemContentControl( ContentControl container )
    {
        if (ItemContentControl != null)
            ItemContentControl.ContentTemplate = m_overwrittenTemplate;

        ItemContentControl = container;
        m_overwrittenTemplate = ItemContentControl.ContentTemplate;
        ItemContentControl.ContentTemplate = ItemTemplate;
    }
}

public abstract class ItemContainerDecorator : Behavior<ItemsControl>
{
    private Dictionary<object, DependencyObject> LastKnownContainers = new Dictionary<object, DependencyObject>();

    protected ItemContainerGenerator ItemContainerGenerator { get { return (AssociatedObject != null) ? AssociatedObject.ItemContainerGenerator : null; } }

    protected override void OnAttached()
    {
        base.OnAttached();
        ItemContainerGenerator.ItemsChanged += HandleItemsChangedInitially;

        if (!TryAddObservers())
        {
            AssociatedObject.Loaded += AddObserversOnLoaded;
        }

        AssociatedObject.Loaded += OnItemsControlLoaded;
        AssociatedObject.LayoutUpdated += OnItemsControlLayoutUpdated;

        CheckContainersChanged();
    }

    private void OnItemsControlLayoutUpdated(object sender, EventArgs eventArgs)
    {
        CheckContainersChanged();
    }

    private void OnItemsControlLoaded(object sender, RoutedEventArgs e)
    {
        CheckContainersChanged();
    }

    private void AddObserversOnLoaded( object sender, RoutedEventArgs e )
    {
        AssociatedObject.Loaded -= AddObserversOnLoaded;
        TryAddObservers();
    }

    private bool TryAddObservers()
    {
        const bool success = true;
        Panel itemsHost =
            AssociatedObject.GetVisualDescendants().OfType<Panel>().FirstOrDefault( panel => panel.IsItemsHost );

        if (itemsHost != null)
        {
            var virtualizingItemsHost = itemsHost as VirtualizingPanel;
            if (virtualizingItemsHost != null)
            {
                virtualizingItemsHost.LayoutUpdated += OnVirtualizingItemsHostLayoutUpdated;
                m_virtualizingItemsHost = virtualizingItemsHost;
            }
            return success;
        }
        return !success;
    }

    private VirtualizingPanel m_virtualizingItemsHost;
    private bool LayoutUpdatedOccurredFirst;

    private void OnVirtualizingItemsHostLayoutUpdated( object sender, EventArgs eventArgs )
    {
        LayoutUpdatedOccurredFirst = true;
        CheckContainersChanged();
    }

    protected override void OnDetaching()
    {
        ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;
        ItemContainerGenerator.ItemsChanged -= HandleItemsChanged;

        AssociatedObject.Loaded -= OnItemsControlLoaded;
        AssociatedObject.LayoutUpdated -= OnItemsControlLayoutUpdated;

        AssociatedObject.Loaded -= AddObserversOnLoaded;
        if (m_virtualizingItemsHost != null) m_virtualizingItemsHost.LayoutUpdated -= OnVirtualizingItemsHostLayoutUpdated;
        m_virtualizingItemsHost = null;

        base.OnDetaching();
    }

    private void HandleItemsChangedInitially( object sender, ItemsChangedEventArgs e )
    {
        ItemContainerGenerator.ItemsChanged -= HandleItemsChangedInitially;

        if (!LayoutUpdatedOccurredFirst)
        {

            //sometimes calling UpdateLayout throws an ArgumentException
            //don't know why so we just swallow it
            //it's not particularly important
            try
            {
                AssociatedObject.UpdateLayout();
            }
            catch (ArgumentException) { }
        }

        ItemContainerGenerator.ItemsChanged += HandleItemsChanged;
        CheckContainersChanged();
    }

    private void HandleItemsChanged( object sender, ItemsChangedEventArgs e )
    {
        CheckContainersChanged();
    }

    private void CheckContainersChanged()
    {
        var newestContainers = new Dictionary<object, DependencyObject>();
        foreach (var item in AssociatedObject.Items)
        {
            newestContainers[item] = ItemContainerGenerator.ContainerFromItem( item );
        }

        if (!LastKnownContainers.SequenceEqual( newestContainers ))
        {
            LastKnownContainers = newestContainers;
            OnContainersChanged();
        }
    }

    protected abstract void OnContainersChanged();
}

Upvotes: 1

Related Questions