ObservableCollection via ListCollectionView displays incorrect list of items

epalm picture epalm · Jan 10, 2012 · Viewed 8.5k times · Source

C#:

public partial class MainWindow : Window
{
    private readonly ViewModel vm;

    public MainWindow()
    {
        InitializeComponent();
        vm = new ViewModel();

        DataContext = vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        vm.Models.RemoveAt(0);
    }
}

public class ViewModel
{
    public ObservableCollection<Model> Models { get; set; }
    public ListCollectionView View { get; set; }

    public ViewModel()
    {
        Models = new ObservableCollection<Model>()
        {
            new Model() { Name = "Gordon Freeman" },
            new Model() { Name = "Isaac Kleiner" },
            new Model() { Name = "Eli Vance" },
            new Model() { Name = "Alyx Vance" },
        };

        Models.CollectionChanged += (s, e) => View.Refresh();
        View = new ListCollectionView(Models);
    }
}

public class Model
{
    public string Name { get; set; }

    public override string ToString()
    {
        return Name;
    }
}

XAML:

<StackPanel>
    <ListBox ItemsSource="{Binding Path=View}" />
    <Button Click="Button_Click">Click</Button>
</StackPanel>

The ObservableCollection contains 4 elements, and the ListBox is displaying all 4, as expected. When the button is clicked, the 1st element of the ObservableCollection is removed. The ListBox, however, is now displaying only the 2nd and 3rd. It would appear the 1st and 4th have been removed.

If the line Models.CollectionChanged += (s, e) => View.Refresh(); is moved after View = new ListCollectionView(Models); (or commented out entirely) things work as expected.

Why?

P.S. This is a simple piece of a larger puzzle. In this small example, I realize I don't need to call View.Refresh(); on CollectionChanged for the ListBox to update itself.

Answer

H.B. picture H.B. · Jan 10, 2012

My guess would be that the refresh interferes with the automatic update by the view. Presumably the view subscribes to CollectionChanged as well in the constructor, so if you also subscribe to the event before the view does and call refresh you get an unwanted update in between the collection change and the view's own update.

e.g.

Item 0 is removed -> Notify event listeners
=> Your handler: Refresh() -> Rebuild view => Item gets removed.
=> View handler: Event args say: Item X was removed -> Remove item X

This still does not explain why the first and the last items get removed but it seems reasonable to me.

If the subscription is after the view instantiation:

Item 0 is removed -> Notify event listeners
=> View handler: Event args say: Item X was removed -> Remove item X
=> Your handler: Refresh() -> Rebuild view => Nothing changed.