WPF: When/where are Collection Views created for a bound collection? - c#

I'm familiar with the concept that a collection used for a WPF Binding (for example, ItemsSource) actually uses the default collection view for that collection, but I am looking for some clarification on where that happens. Potentially depending on that answer, what is the recommended way to implement similar binding behavior if I am creating my own UserControl that has its own property like ItemsSource and I want to use ICollectionView functionality on it within my UserControl?
So if my UserControl has a ItemsSource property like:
public static readonly DependencyProperty TestPropProperty =
DependencyProperty.Register("TestProp", typeof(System.Collections.IEnumerable), typeof(MyUserControl),
new FrameworkPropertyMetadata(null, TestPropChanged, null));
And my XAML uses it like this:
<local:MyUserControl TestProp="{Binding MyCollection}"/>
Let's say MyCollection is of type ConfigCollection, my own type, derived from ObservableCollection. From MyUserControl's perspective, the binding sets TestProp to an object of type ConfigCollection (NOT a Collection View object).
The MS docs: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-6.0#binding-to-collections
Under "Using a default view", state:
WPF also creates a default collection view for every collection used as a binding source. If you bind directly to a collection, WPF binds to its default view.
That seems to be pretty universal, so in my case I was thinking it might be set as a Collection View whose source was MyCollection, but that is not the case.
So, does the "magic" of translating a collection into its default collection view all happen within each control's code? Is there a recommended way of making sure my ItemsSource can accept either a collection (and use the default view) or accept a Collection View and then use that specific view rather than the default?

Related

Using ViewModels in ObservableCollections in Prism

As far as I know, the default way to use a ObservableCollection that is bound to a listview is with model classes as elements (ObservableCollection<MyModel>). So when a listview element is selected, we use NavigateAsync and pass the model, which then can be used by the ViewModel to "fill itself".
The problem with this approach is, that it's not possible to use ViewModel properties for binding in the listview.
For example:
I have a View, ViewModel and Model "PickList", which contains a collection of "PickLine" objects - each having a View, ViewModel and Model themselves. The PickLine object contains a property "PickedQuantity" and a property "OpenQuantity". Now in my PickList view, I don't want to bind these two to separate items (e.g. two labels), but I want to have one label to display both I a format like for example "PickedQuantity / OpenQuantity". I know this example can be solved by using multi binding or something like this. But that's not the meaning of it all.
My PickLine ViewModel already has a property "QuantityString", that I want to bind to the label of a listview element via DataTemplate. But how can I do this. Is it even possible?
Make a property that combines the two other properties and bind to that. E.g.:
public string FullQuantity {get {return $"{PickedQuantity} / {OpenQuantity}";}}
Then in the setter for PickedQuantity and OpenQuantity, you will want to call whatever PropertyChanged method you have set up to notify the bindings of a property change and pass in the FullQuantity property name so elements that are bound to FullQuantity get updated when either PickedQuantity or OpenQuantity are changed.
This way, you are only binding one label's text to one property and that label would get updated when either of the two quantity properties are changed.
Note: I am unfamiliar with Prism, but this approach should work regardless of the Mvvm framework in use.
Your PickListViewModel should expose a collection property whose items are of type PickLineViewModel (not PickLine).
Whether you need an ObservableCollection<PickLineViewModel> depends on where changes can happen - in service / model that initially created the PickLines or in the GUI or both. In any way, you have to make sure the changes are propagated from one side (the collection of view models) to the other (the collection of models). Google wrapping observable collection as a starter (hint: avoid two-way sync if possible). These blog posts are old but still relevant and make a good reading. A trivial wrapping is described in this answer.

Why should I avoid instantiating a CollectionView directly?

The documentation of the CollectionView class says:
You should not create objects of this class in your code. To create a collection view for a collection that only implements IEnumerable, create a CollectionViewSource object, add your collection to the Source property, and get the collection view from the View property.
In other words, I am supposed to write:
var cvs = new CollectionViewSource();
cvs.Source = myData.Where(d => someCondition(d));
var view = cvs.View;
instead of
var view = new CollectionView(myData.Where(d => someCondition(d));
However, the documention fails to explain why I should do that. Both options seem to work.
What bad things will happen if I choose option 2 over option 1?
There are a few specializations of the CollectionView class, like for example ListCollectionView. Whenever you bind to some collection, there is a view automatically generated for you based on the type of the source collection. Instead of explicitly creating a CollectionView or a CollectionViewSource, you can get the default view of a collection by using the static CollectionViewSource.GetDefaultView method:
ICollectionView view = CollectionViewSource.GetDefaultView(myData);
It will return a ListCollectionView if myData implements IList.
It's perfectly fine to expose an ICollectionView from a view model if you perform the filtering or sorting in there. A CollectionViewSource is mainly used when you want to perform the filtering, sorting or grouping of a source collection in the view.
Binding CollectionView may present problems down the road if you want to change the way your data is displayed.
From the CollectionViewSource documentation:
Because a view does not change the underlying source collection, a source collection can have multiple views associated with it. By using views, you can display the same data in different ways. For example, you can use two views on a collection of Task objects to show tasks sorted by priority on one part of the page and grouped by area on another part of the page.
Binding directly to a single CollectionView limits the ways you can display your data.In short, CollectionViewSource plays nicer with your collections in XAML when binding.

WPF MVVM Change Model

I have a UserControl where the data gets passed in via a dependency property.
The UserControl is backed by a view model and the data is assigned to a property of the view model.
The XAML binds to both properties in the view model as well as properties within the passed in data.
The problem is if the user changes the data then databinding with the UI breaks. The UI is still bound to the original data object.
How can I cause the binding to get refreshed? INotifyPropertyChanged is implemented throughout, but it is not the property that is getting stale, but the binding. Basically, how do you go about replacing a backing model?
Since I'm not sure my description is clear I will try to describe it again in pseudo code.
<MyControl Source="{Binding Data}"/>
Where source is a dependency property of MyControl. In the PropertyChangedCallback this data is handed to the view model.
MyViewModel.Data = Source;
Within the MyControl XAML things are bound to this model.
{Binding Path=MyViewModel.Data.Item}
If you are wondering why the dependency property is defined in the UserControl, it is because it is a reusable control and the end user should not know about the view model.
"The UserControl is backed by a view model" and thus breaketh the application.
The UserControl's DataContext should be the Model. Period. If you need to perform UI logic, do it in the codebehind. Need something else? Create DependencyProperties on the surface of your UserControl to supply them.
When you create a ViewModel specifically for your UserControl, you break the natural flow of the DataContext (at this point, probably the Model the UC is designed to work with) and binding within the UserControl. It's pointless in most cases and harmful in some (as you have noticed).
My canonical answer on the subject contains more details.
Are you trying to swap-out the DataContext of the usercontrol during an application's session?
If you are, then I am fairly confident that this will not work.
Have you considered spinning up a new instance of that user control with the other DataConext?

How can I replace the default CollectionView of an enumerable collection?

When binding an enumerable to an ItemsSource or similar, the binding uses the enumerable's default view, which I know you can get using the following code...
var defaultView = CollectionViewSource.GetDefaultView(someCollection);
This has worked great for us, allowing us to, for instance, add sorting right to the default view of various collections.
However, we have a specialized Collection class which requires a specialized ListCollectionView subclass to work properly. That said, how can we change its default view so that is returned when someone binds directly to the collection?
For a work-around, we created a new property called MainView which holds our custom ListCollectionView, then we bind things like ItemsSource to that, but that means consumers of our collection have to be explicitly told not to bind directly to the collection but rather to the MainView property or they'll get the default view. Considering the standard is just to bind to the collection directly, that's a potential issue we're trying to avoid.
So again, how can I specify my own ListCollectionView subclass as the default view for our custom collection?
On your collection, implement the interface ICollectionViewFactory. Here is an example:
public class MyCollection : ObservableCollection<MyItem>, ICollectionViewFactory
{
public ICollectionView CreateView()
{
return new MyListCollectionView(this);
}
}

Determining out the correct property return type for a data binding when using MVVM Light in a C#/WPF app?

I have a C# Windows Phone 7.1 project that uses the MVVM Light toolkit. On one application page in the app I have a Telerik data bound list box that is bound to a property in my view model. At first that property had a return type of:
List<string>
With that return type the property did not show up in the list of candidate elements in the Path list box, when I activated the Create Data Binding dialog box in order to assign the list box's ItemsSource property. However, when I changed that property's return type to:
ObservableCollection<string>
it showed up immediately. In the future, how can I determine the correct return type for a particular control's ItemsSource or other data bindable property?
Your question is little confusing. Both are completely okay and work well for different scenarios. You use list<string> when you don't plan to change collection, and ObservableCollection otherwise.
Now, you can use MSDN(see ItemsSource property to see what property needs to be what.
As you can see from documentation, ItemsSorce has to be IEnumerable, meaning all types that derive from it, can be used.
Now as for the real question, why didn't your property show up in the IDE, I can only guess that Microsoft wants you to use ObservableCollection always, but I don't agree with this. So that's why it's best to manually do the bindings to properties through Xaml.

Categories