Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListView.ContainerFromItem returns null after a new item is added in windows 8.1 store XAML

I have a simple ListView with no item template and the SelectionChanged event setup:

    <ListView x:Name="list1" HorizontalAlignment="Left"
              Height="556"
              Margin="209,93,0,0"
              VerticalAlignment="Top"
              Width="1033"
              SelectionChanged="list1_SelectionChanged" />

    private void list1_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {            
        var container = (sender as ListView).ContainerFromItem(e.AddedItems.First());
        var presenter = VisualTreeHelper.GetChild(container, 0);            
    }

I also have a test class as follows:

class Test
{
    public string FirstName { get; set; }
    public string Surname { get; set; }
}

Now in constructor for the page I have the following code that creates an ObservableCollection of the Test items, adds a few and then sets that as the ListView's ItemSource:

ObservableCollection<Test> testCollection;

public MainPage()
{
    this.InitializeComponent();

    testCollection = new ObservableCollection<Test>();
    testCollection.Add(new Test { FirstName = "Bob", Surname = "Smith1" });
    testCollection.Add(new Test { FirstName = "Bob", Surname = "Smith2" });
    testCollection.Add(new Test { FirstName = "Bob", Surname = "Smith3" });

    list1.ItemsSource = testCollection;
}

Now when I select an item in the list and the SelectionChanged Event fires, the container variable contains the ListViewItem as expected.

Now, I have button on the form too, here is the button click event:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        testCollection.Add(new Test { FirstName = "Bob", Surname = "Smith4" });

        list1.SelectedIndex = list1.Items.Count - 1;
    }

The last line selects the new item and the SelectionChanged event fires but this time the container variable is null.

Can anybody tell me why this is null and how do I get round this?

Thanks

like image 448
Sun Avatar asked Dec 25 '22 08:12

Sun


1 Answers

I can tell you.

What you are experiencing here is a problem of timing. How fast do you think you can add an item to a collection? Fast - just a millisecond or two. How fast do you think it takes the ListView to set the SelectedItem property and raise the SelectionChanged event? Fast - a millisecond or two. But, how fast do you think it takes the ListView to actually render the new item on the screen and generate the container for it? A long time - like 10 to 100 milliseconds, depending on the complexity of the DataTemplate.

I realize your test didn't set the ItemTemplate, and I assume you did it that way assuming that it would render faster - maybe instant. But there is a default DataTemplate in every ItemsControl. And, even a simple DataTemplate like the built-in one, still takes more time than it takes you to advance a single C# line.

This code will illustrate what I mean:

async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    var items = Enumerable.Range(1, 10)
        .Select(x => new Item { FirstName = "Bob", LastName = "Smith" + x.ToString() });
    var list = new ObservableCollection<Item>(items);
    MyListView.ItemsSource = list;
    var item = new Item { FirstName = "Jerry", LastName = "Nixon" };
    list.Add(item);
    await Task.Delay(1000);
    MyListView.SelectedItem = item;
}

private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    System.Diagnostics.Debug.Assert(e.AddedItems.Any());
    var listview = sender as ListView;
    var container = listview.ContainerFromItem(e.AddedItems.First());
    System.Diagnostics.Debug.Assert(container != null);
    var presenter = VisualTreeHelper.GetChild(container, 0);
    System.Diagnostics.Debug.Assert(presenter != null);
}

Please notice the Delay code; this is what makes it work.

Hopefully, you simplified your question and that is why you are doing the work in code-behind and not in a view model. Fine. First, be sure and handle this type of logic in Loaded so you can be confident that your ListView is even available. Then again, do this in your view model.

So, I have answered your question. Why? Because of timing.

Seems to me it might all be solved with a simple view model, but then again, I only know a little bit of your situation here. Maybe there's something I am not seeing.

Best of luck!

like image 185
Jerry Nixon Avatar answered Dec 27 '22 23:12

Jerry Nixon