aidesigner
aidesigner

Reputation: 563

Changing CollectionViewSource Source in a MVVM world

Edited: I created a new VS2010 WPF appilication with just 3 files MainWindow.xaml, MainWindow.xaml.cs, and MainWindowViewModel.cs (Listed Below). If someone feels really helpful you can recreate the problem in seconds (copy/paste). When you run the app the DataGrid will display string "OldCollection" which is wrong. If you change the ItemsSource binding to MyCollection it displays "NewCollection" which is correct.

Full Description: At first I had a DataGrid with its ItemsSource bound to MyCollection. I have/need a method UpdateCollection that assigns a new ObservableCollection<> to MyCollection. With the addition of NotifyPropertyChange to MyCollection the UI updates.

Next it became necessary to introduce a CollectionViewSource to enable grouping. With the UI bound to MyCollectionView, calls to UpdateCollection now have no effect. The debugger confirms that MyCollectionView always contains the initial MyCollection. How can I get my NewCollection to be reflected in the View? I have tried View.Refresh(), Binding CollectionViewSource, and countless other strategies.

Note: Primarily others are concerned with the changes to Collection items not updating the view (grouping/sorting) without calling Refresh. My problem is I am assigning a brand new collection to CollectionViewSource and the view/UI never changes.

// MainWindow.xaml
<Window x:Class="CollectionView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid Name="grid" ItemsSource="{Binding MyCollectionView}" />
    </Grid>
</Window>

//MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace CollectionView
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }
}

//MainWindowViewModel.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Windows.Data;
using System.ComponentModel;

namespace CollectionView
{
    class MainWindowViewModel : INotifyPropertyChanged
    {
        public MainWindowViewModel()
        {
            MyCollection = new ObservableCollection<MyObject>() { new MyObject() { TestString = "OldCollection" } };

            MyCollectionViewSource = new CollectionViewSource();

            // Bind CollectionViewSource.Source to MyCollection
            Binding MyBind = new Binding() { Source = MyCollection };
            BindingOperations.SetBinding(MyCollectionViewSource, CollectionViewSource.SourceProperty, MyBind);

            // The DataGrid is bound to this ICollectionView
            MyCollectionView = MyCollectionViewSource.View;

            // This assignment here to demonstrate that View/UI does not update to show "NewCollection"
            MyCollection = new ObservableCollection<MyObject>() { new MyObject() { TestString = "NewCollection" } };
        }

        // Collection Property
        // NotifyPropertyChanged added specifically to notify of MyCollection re-assignment
        ObservableCollection<MyObject> _MyCollection;
        public ObservableCollection<MyObject> MyCollection
        {
            get { return _MyCollection; }
            set
            {
                if (value != _MyCollection)
                {
                    _MyCollection = value;
                    NotifyPropertyChanged("MyCollection");
                }
            }
        }

        public CollectionViewSource MyCollectionViewSource { get; private set; }
        public ICollectionView MyCollectionView { get; private set; }

        // Method updates MyCollection itself (Called via ICommand from another ViewModel)
        public void UpdateCollection(ObservableCollection<MyObject> NewCollection)
        {
            MyCollection = NewCollection;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
    }

    class MyObject
    {
        public string TestString { get; set; }
    }
}

Thanks,

Upvotes: 10

Views: 19171

Answers (3)

blindmeis
blindmeis

Reputation: 22435

I would choose one of the two following solutions.

First, you could take your ObservableCollection and create an ICollectionView (grouping, sorting) once. Instead of replacing the ObservableCollection you can use .Clear() and add the items from the new Collection. This has the additional bonus of not breaking your grouping and sorting.

Second approach: whenever you replace your ObservableCollection you have to create a new ICollectionView for sorting and grouping.

 this._view = (ICollectionView)CollectionViewSource.GetDefaultView(this.MyCollection);

You can simply bind to your collection if you take the DefaultView

 <DataGrid Name="grid" ItemsSource="{Binding MyCollection}" />

and you can throw away your CollectionViewSource code binding stuff.

Upvotes: 10

Alain
Alain

Reputation: 27220

The fact that the binding isn't working is incredibly weird. I encountered the exact same problem with the Xceed DataGridCollectionViewSource - but I just assumed it was because Xceed was screwed up.

My solution was to actually create a brand new DataGridCollectionViewSource every time the underlying collection was replaced and to reconfigure it programatically, and then update the myDataGrid.Source property to point to the new DataGridCollectionViewSource. That workaround certainly will work, but that completely defeats the purpose of bindings, which should be working:

void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    //Xceed DataGridCollectionView are terrible and do not update when the bound table changes, so we need to replace them each change.
   if (e.PropertyName == "MyCollection")
   {
       DataGridCollectionView GridView = new DataGridCollectionView(MyViewModel.MyCollection.DefaultView);
       GridView.SortDescriptions.Add(new SortDescription("DataSetID", ListSortDirection.Ascending));
       uiMyCollectionGrid.ItemsSource = GridView;
   }
}

Maybe this brute force solution can solve your problem? I understand if you don't even want to consider this yet since it's so dirty.

Upvotes: 0

Alain
Alain

Reputation: 27220

The problem is definitely the fact that you aren't binding the source to your MyCollection property, you're assigning it once and it never gets updated again.

You should be doing something like the following:

MyCollectionViewSource = new CollectionViewSource();
Binding binding = new Binding();
binding.Source = MyCollection;
BindingOperations.SetBinding( MyCollectionViewSource , 
                              CollectionViewSource.SourceProperty, 
                              binding );

I apologize if the above doesn't work right away without tweaking - this is usually the type of thing I set up in xaml, because it's much easier there.

<CollectionViewSource Source="{Binding MyCollection}" />

See Also: How do I change the binding of a CollectionView.Source?

Upvotes: 0

Related Questions