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
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;
}
}