Search code examples
listviewuwplistviewitem

GetContainerForItemOverride in UWP app


I'm working on an app that uses a cross-platform third-party layout engine to place UI elements in the Visual Tree at specific coordinates (by putting things in canvases). In my scenario I need a virtualizing ListView where every item of it goes through this layout engine.

All is fine until I try to delete items from the list view. I've narrowed the issue down to the fact that for my list view I am returning a Canvas when GetContainerForItemOverride() is called, instead of returning a ListViewItem. But of course I need a Canvas so that the layout engine can put stuff at specific coordinates in the Canvas.

I've created a very dumb sample below that will reproduce the issue that I'm hitting. Basically when I try to delete an item by calling .RemoveAt() or .Remove() then I get an InvalidCastException (btw the same problem happens if I'm using an ItemsSource in this example instead of adding directly to .Items).

Anyone knows how to solve this issue?

Here's the code

<Page x:Class="Sample.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:sample="using:Sample">

    <StackPanel>
        <sample:CustomListViewCrash x:Name="MyListViewCrash">
            <sample:CustomListViewCrash.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </sample:CustomListViewCrash.ItemsPanel>
        </sample:CustomListViewCrash>

        <Button Content="Delete" Tapped="Delete_OnTapped" />
    </StackPanel>

</Page>

And the code behind

public sealed partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();
        MyListViewCrash.Items.Add("blah blah");
    }

    private void Delete_OnTapped(object sender, TappedRoutedEventArgs e)
    {
        if (MyListViewCrash.Items.Count > 0)
        {
            MyListViewCrash.Items.RemoveAt(0);
        }

    }
}

public class CustomListViewCrash : ListView
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        var canvas = new Canvas
        {
            Width = 100,
            Height = 50
        };

        canvas.Children.Add(new Button());

        return canvas;
    }

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        var canvas = (Canvas) element;
        var button = (Button) canvas.Children[0];
        button.Content = item;

        base.PrepareContainerForItemOverride(element, item);
    }
}

And here's the info about the exception:

System.InvalidCastException occurred
  HResult=0x80004002
  Message=Specified cast is not valid.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Runtime.InteropServices.WindowsRuntime.IVector`1.RemoveAt(UInt32 index)
   at System.Runtime.InteropServices.WindowsRuntime.VectorToListAdapter.RemoveAtHelper[T](IVector`1 _this, UInt32 index)
   at System.Runtime.InteropServices.WindowsRuntime.VectorToListAdapter.RemoveAt[T](Int32 index)
   at ReactiveDelete.MainPage.Delete_OnTapped(Object sender, TappedRoutedEventArgs e) in MainPage.xaml.cs:line 31

Solution

  • From the exception of "System.InvalidCastException: Specified cast is not valid" , that container of the ListView should be the ListViewItem.

    You should be able to set the CustomListViewCrash inherits from the ItemsControl to replace the ListView. When the CustomListViewCrash class inherits from the ItemsControl, the container of the CustomListViewCrash is not ListViewItem.

    If you want your class inherits from the ListView, you should be able to set the Canvas to the Content of the ListViewItem. And we should be able to remove the base.PrepareContainerForItemOverride(element, item) method in the PrepareContainerForItemOverride.

    For example:

    public class CustomListViewCrash : ListView
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            var canvas = new Canvas
            {
                Width = 100,
                Height = 50
            };
            canvas.Children.Add(new Button());
            var listViewItem = new ListViewItem();
            listViewItem.Content = canvas;
            return listViewItem;
        }
    
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            var listViewItem = (ListViewItem)element;
            var canvas = (Canvas)listViewItem.Content;
            var button = (Button)canvas.Children[0];
            button.Content = item;
        }
    }