How can I get the CollectionView that is defined in XAML
Asked Answered
C

3

9

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?

Carltoncarly answered 15/6, 2012 at 7:48 Comment(3)
You are going about it the wrong way. A VM should have no knowledge of the view. If you want to update the view, ensure the property it binds to is either an ObservableCollection or your code raises NotifyPropertyChanged explicitly when modifying the collection.Prot
@PanagiotisKanavos: The stuff in the list view actually is in an ObservableCollection and the items in the UI do update on a property change. But the grouping does not respect that. A known workaround is to force the update, i.e. CollectionViewSource.GetDefaultView(…).Refresh.Carltoncarly
In .NET 4.5, this will be remedied with the ICollectionViewLiveShaping.Carltoncarly
C
2

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?

Carltoncarly answered 27/6, 2012 at 6:7 Comment(1)
As this is more complete (and working) than the base of J. Lennon, I mark this (but he got the bounty).Carltoncarly
R
6

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.

Reduction answered 15/6, 2012 at 8:1 Comment(5)
That's a good way to do it, but it does not answer the question. I'd still like to know.Carltoncarly
What do you mean by "refresh the view" and why do you want to do it? If the underlying collection exposed by your VM implements INotifyCollectionChanged (such as with ObservableCollection<T>), then the collection view created by your view should stay up to date, thus saving you the trouble of "refreshing the view".Reduction
I should have qualified the above. As long as you're only adding/removing items, the collection view should stay sorted. If you're modifying the underlying property by which you're sorting, that's one valid reason why you'd need to "refresh". If you really want the view to create the CV rather than the VM, then you'll have to raise an event in your VM whenever the view should refresh. IMHO, it's just cleaner having the VM handle it all, since it's business logic anyways.Reduction
Yes, underlying collection is ObservableCollection<T>. But no, it does not completely update. The sorted list aaa, bbb, eee turns to aaa, zzz, eee automatically on property change. To get the resort, you have to tell it. So yes, this is the valid reason you're mentioning.Carltoncarly
+1 for your second comment which is another way to do it. Nevertheless, I'm cannot believe that the “right” CV is really inaccessible.Carltoncarly
C
6

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.

Chlorothiazide answered 26/6, 2012 at 15:15 Comment(2)
This was also brought up by a colleague. The problem is that in the VM, I don't get to the FindResource of any FrameworkElement (separate assemblies, only Vs reference VMs).Carltoncarly
As your answer comes closest, you shall have the bounty. Spend it wisely. ;-)Carltoncarly
C
2

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?

Carltoncarly answered 27/6, 2012 at 6:7 Comment(1)
As this is more complete (and working) than the base of J. Lennon, I mark this (but he got the bounty).Carltoncarly

© 2022 - 2024 — McMap. All rights reserved.