ObservableCollection + ICollectionView = ObservableCollectionView? - wpf

I have a View Model that exposes an ObservableCollection. Now I want to add grouping and therefore want to use a View class, like e.g. the ListCollectionView. But that is not a generic type. Also, if I construct a ListCollectionView from an ObservableCollection, then it is not updated when the ObservableCollection is.
Did someone implement an ObservableCollection-backed View-collection somewhere?
If not, is it possible to achieve this dynamism somehow by using XAML?

You don't need a specific implementation ; a CollectionView is just a view of a collection. You can get the default CollectionView for a collection using CollectionViewSource.GetDefaultView:
ObservableCollection<Something> collection = ...
ICollectionView view = CollectionViewSource.GetDefaultView(collection);

Related

WPF: Which collection class to use

After spending a whole day trying different suggestions, I'm back at square 1. I'm trying to bind my view, a XAML Window, to one of my ViewModel properties, say, SalesOrders. The ViewModel in turn talks to the Model (an EF Model on top of a database). The question I'm facing is the collection type that I should use to expose my SalesOrders property.
I have tried the following types, none of which does all of what I need.
List<T>
ObservableCollection<T>
BindingList<T>
CollectionViewSource on top of the above
Here's what I need my collection to do:
The view has Previous/Next buttons, so the collection should provide some sort of currency manager.
There's a Save button in the view, which I need to get enabled/disabled immediately based on whether the SalesOrder collection has any changes. Since SalesOrder is already an EF type, all of its fields implement INotifyPropertyChanged.
CollectionViewSource provides me with navigation methods (previous/next) but doesn't listen to PropertyChanged events, so modifying data in the view doesn't turn the Save button on. BindingList can listen to PropertyChanged events, but doesn't provide navigation methods. ObservableCollection lacks both functionalities.
TIA.
Why don't you use ObservableCollection<T> then subscribe to the CollectionChanged event to enable or disable your save button as outlined in the answer of the thread MVVM ObservableCollection Bind TwoWay.
According to MSDN about CollectionView here:
In WPF applications, all collections have an associated default
collection view. Rather than working with the collection directly, the
binding engine always accesses the collection through the associated
view. To get the default view, use the
CollectionViewSource.GetDefaultView method. An internal class based on
CollectionView is the default view for collections that implement only
IEnumerable. ListCollectionView is the default view for collections
that implement IList. BindingListCollectionView is the default view
for collections that implement IBindingListView or IBindingList.
Which means you can use BindingList for SalesOrders and bind it in the View, then to manage the navigation you can access its automatically created CollectionView from the ViewModel with:
myCollectionView = (BindingListCollectionView)CollectionViewSource.GetDefaultView(this.SalesOrders);

How does binding to collections really work?

Well, I'm confused.
If my control has dependency property ItemsSource of IEnumerable type and user binds collection to it what object do I have in DependencyPropertyChangedEventArgs.NewValue?
As far as I know CollectionView is implicitly created for collections and I expect args.NewValue to be of type ICollectionView.
From this blog:
When a user binds a WPF property to a collection of data, WPF
automatically creates a view to wrap the collection, and binds the
property to the view, not the raw collection. This behavior always
happens, and is independent of CollectionViewSource.
But debugger (VS 2012, .net v.4.0) shows me that I receive original raw collection in NewValue. (BindsDirectlyToSource is not set and equals false by default)
How can this be?!
I cannot understand how in this case WPF controls support sorting, grouping and filtering.
How and when is CollectionView injected and used?
Maybe the following extract from the Remarks section in CollectionView answers your question:
In WPF applications, all collections have an associated default
collection view. Rather than working with the collection directly, the
binding engine always accesses the collection through the associated
view. To get the default view, use the
CollectionViewSource.GetDefaultView method. An internal class based on
CollectionView is the default view for collections that implement only
IEnumerable. ListCollectionView is the default view for collections
that implement IList. BindingListCollectionView is the default view
for collections that implement IBindingListView or IBindingList.
Alternatively, you can create a view of your collection in Extensible
Application Markup Language (XAML) by using the CollectionViewSource
class and then bind your control to that view. The
CollectionViewSource class is the XAML representation of the
CollectionView class. For an example, see How to: Sort and Group Data
Using a View in XAML.
So if you do not explicitly bind to a CollectionViewSource, a collection binding is always made to the original collection (what you get in NewValue), but access to the collection (e.g. get an item by index) is always done through the default view. Therefore the statement "binds the property to the view, not the raw collection" is not exactly true.
A quick test revealed that GetDefaultView returns a System.Windows.Data.ListCollectionView for my bound ObservableCollection.

Calling property changed on a bound element

My view model implements INotifyPropertyChanged for properties that it makes available to my view. It makes available a collection of objects that do not implement INotifyPropertyChanged.
My collection is bound to an ItemControl in my view, with an ItemTemplate used to display each item invidually. The item template is bound to the Name attribute of my collection members.
How can i tell my view to update when properties of my collection members change?
You need to either implement INotifyPropertyChanged for the objects in your collection (recommend approach), or you can manually refresh the binding by something like
myItemsControl.GetBindingExpression(
ItemsControl.ItemsSourceProperty).UpdateTarget();
If you're in the ViewModel, you might be able to raise a PropertyChanged event on your Collection class such the following, although I am not sure if that will update the individual items or not
// My PropertyChanged method is usually called RaisePropertyChanged
RaisePropertyChanged("MyCollection");
You can also do what Mirimon suggeted and set the value to null then back again, although personally I would recommend a different approach if possible.
You must implement INotifyPropertyChanged for collection members. Or you can reset your collection in ViewModel:
public void Reset() {
List<TestData> temp = YourCollection;
YourCollection = null;
YourCollection = temp;
}

How to keep a data-bound list reverse-sorted

I have a listbox bound to a collection. I would like the ListBox to always reverse the order of the items. This handler--hooked up to the control's load event--works for the initial load, but not thereafter. Ive tried using the SourceUpdated event but that doesnt seem to work.
How do I maintain a constant active sort?
MyList.Items.SortDescriptions.Add(New SortDescription("Content", ListSortDirection.Descending))
How is the collection stored that supplies the items for the ListBox? It should be a collection that supports INotifyCollectionChanged. The framework provides ObservableCollection<T> which you can use.
In the constructor of your ViewModel (or wherever the collection lives), you then get the DefaultView for adding the SortDescription. The CollectionView is like a layer on top of your collection, which you can use to sort, group, filter, etc. the items without actually affecting the underlying data source. The framework creates a default one for you. To get a reference to it, you can use code similar to the following:
var collectionView = CollectionViewSource.GetDefaultView(Widgets);
if(collectionView == null)
return;
collectionView.SortDescriptions.Add(new SortDescription("Content", ListSortDirection.Descending));
With that in place, you should be able to add items to the ObservableCollection<T> and the sort order will be maintained.
If your source collection is a List<T> or some other collection that doesn't implement INotifyCollectionChanged, there is no way WPF can detect when an item is added. You need to use a collection that implements INotifyCollectionChanged, like ObservableCollection<T>.
Also, the items in your collection need to implement INotifyCollectionChanged so that changes to the items are taken into account

ItemsControl - Bind Control's and Backing Collection's Sort Orders Together

Is there a way to bind an ItemsControl (like ListView or DataGrid) to a sorted collection such that:
whenever the collection is resorted via code, the ItemsControl automatically reflects the new sort order,
AND whenever the ItemsControl is sorted, it re-sorts the collection?
Thank you,
Ben
You'll need to use the
CollectionViewSource.GetDefaultView()
method to get the default view of your ObservableCollection and apply the sort on that.
For example, below I'm sorting the ObservableCollection named 'authors' by BookTitle.
ObservableCollection<Author> authors = new ObservableCollection<Author>();
authors = PopulateCollection();
// Sort by BookTitle
System.ComponentModel.ICollectionView colView;
colView = CollectionViewSource.GetDefaultView(authors);
colView.SortDescriptions.Add(new System.ComponentModel.SortDescription("BookTitle", ListSortDirection.Descending));
try to define this two attributes on the bining:
IsSynchronizedWithCurrentItem=true
BindsDirectlyToSource=true
i did not tried this but it might work..
Putting your items into an ObservableCollection and then binding to the ObservableCollection should do the trick. Any sorting done on the ObservableCollection should "translate" to the UI layer.

Resources