I wanted to bind to an ObservableCollection
in XAML and also apply the grouping there. In principle, this worked fine.
<UserControl.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding Path=TestTemplates}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Title"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="TestCategory"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
Then the data binding expression became ItemsSource="{Binding Source={StaticResource ResourceKey=cvs}}"
instead of ItemsSource="{Binding Path=TestTemplates}"
.
At first, everything seemed cool, until I wanted to refresh the UI from the view model. The problem is, that CollectionViewSource.GetDefaultView(TestTemplates)
returned a different view than the one from XAML where the grouping was applied. Thus, I could not set selection or do anything useful with it.
I could fix it by binding the list again directly to the view model's property and setting up the grouping in the code-behind. But I'm not that happy with this solution.
private void UserControlLoaded(object sender, RoutedEventArgs e)
{
IEnumerable source = TemplateList.ItemsSource;
var cvs = (CollectionView)CollectionViewSource.GetDefaultView(source);
if (cvs != null)
{
cvs.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
cvs.GroupDescriptions.Add(new PropertyGroupDescription("TestCategory"));
}
}
I assume, the reason for that is already given by John Skeet here.
Nevertheless, I would expect that there should be a way to get the right view. Am I wrong?
ICollectionView will give you the ability to filter, sort, or group the collection in addition to propogating INotifyCollectionChanged events if the underlying collection implements it.
CollectionViewSource is a proxy for a CollectionView class, or a class derived from CollectionView. CollectionViewSource enables XAML code to set the commonly used CollectionView properties, passing these settings to the underlying view.
ICollectionViews are used for filtering, sorting, grouping and also selecting items in list controls (selecting applies only for controls that inherit from the Selector base class such as ListBox, ComboBox etc..).
You could not just do that?
var _viewSource = this.FindResource("cvs") as CollectionViewSource;
If the data is connected, I assume that will have an updated view.
I tend to just expose the collection view from the VM rather than have the view define it:
public ICollection<Employee> Employees
{
get { ... }
}
public ICollectionView EmployeesView
{
get { ... }
}
That way your VM has full control over what is exposed to the view. It can, for example, change the sort order in response to some user action.
Found a way, based on J. Lennon's answer. If I pass something that has access to the resources with my command, then I can look up the CollectionViewSource
there.
In XAML (CollectionViewResource
as above):
<Button Command="{Binding Command}" CommandParameter="{Binding RelativeSource={RelativeSource Self}}">Do it!</Button>
And in the VM code:
private void Execute(object parm)
{
var fe = (FrameworkElement)parm;
var cvs = (CollectionViewSource)fe.FindResource("cvs");
cvs.View.Refresh();
}
The Execute
is the one that is given to the RelayCommand.
This would answer the question, but I don't like it very much. Opinions?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With