This project has moved. For the latest updates, please go here.

[SOLVED] How to update ViewModel collections so CollectionView (e.g. Filtering) keeps working?

Feb 19, 2013 at 3:47 PM
I've got an application I've built that emulates the design of the BookLibrary application, but rather than books I present a collection of Materials used in an SAP data conversion project. With this project there are multiple sources of materials that I'd like to present, and the user can switch which source via a drop down on the main window. This action updates a "Selected" property on a parent view model and I'd like to have the controller update the list of materials in another view model.

With the BookLibrary example, imagine if there was a drop down that would let you work with books from multiple libraries. When a user switched which library is selected the list of books stored in the BookListViewModel should be updated, but without losing any of the functionality around filtering.

To demonstrate what I'm seeing, let me add a simple Refresh button to the BookListView.xaml file:

Imgur

Now the code behind this just follows the regular ICommand pattern and I've updated the BookController to have this Refresh() method:
private void Refresh()
        {
            bookDataModels = new SynchronizingCollection<BookDataModel, Book>(entityService.Books.Where(x => x.LendTo != null),
                b => new BookDataModel(b, lendToCommand));

            bookListViewModel.Books = bookDataModels;
        }
I also updated the BookListViewModel class to make the private books variable non-readonly and exposed it as a settable property:
        public IEnumerable<BookDataModel> Books { 
            get { return books; }
            set
            {
                if (books != value)
                {
                    books = value;
                    RaisePropertyChanged("Books");
                }
            }
        }
With those additions I can click on the Refresh button and get a result like this:

Imgur

Now here's the issue that I can't figure out how to solve. Once I update the books collection the CollectionView stops working, and I lose the ability to filter the list of books:

Imgur

I kind of get that the bookCollectionView object in BookListView.xaml.cs is being set when things are loaded for the first time, but how can I update it later on? Anyone know how to "reset" or otherwise update the ICollectionView properly so this functionality isn't lost?

Thanks in advance for any help and guidance!
Feb 21, 2013 at 12:08 PM
With the help of an answer on stackoverflow.com, and some more research about what events the CollectionViewSource responds to I think I have a solution. I needed to update the Books collection to be a List() that would allow me to manipulate the contents without losing the reference the collection view source was bound to. However, I also needed to update the BookListView.xaml.cs file to include a PropertyChanged handler like so:
        private void FirstTimeLoadedHandler(object sender, RoutedEventArgs e)
        {
            // Ensure that this handler is called only once.
            Loaded -= FirstTimeLoadedHandler;
            
            // The following code doesn't work in the WPF Designer environment (Cider or Blend).
            if (!WafConfiguration.IsInDesignMode)
            {
                bookCollectionView = CollectionViewSource.GetDefaultView(ViewModel.Books);
                bookCollectionView.Filter = Filter;
                ViewModel.BookCollectionView = bookCollectionView.Cast<BookDataModel>();
                ViewModel.PropertyChanged += new PropertyChangedEventHandler(ViewModel_PropertyChanged);

                bookTable.Focus();
                bookTable.CurrentCell = new DataGridCellInfo(ViewModel.Books.FirstOrDefault(), bookTable.Columns[0]);
            }
        }

        void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // If the property is the Books collection we want to refresh the collection view
            //
            if (e.PropertyName == "Books")
            {
                bookCollectionView.Refresh();
            }
        }
With that change in place I can modify the contents of the list within my view model, signal any listeners with RaisePropertyChanged("Books"), and then the bookCollectionView will automatically call Refresh() after the underlying collection has been updated.