Proper way to use CollectionViewSource in ViewModel

Learn proper way to use collectionviewsource in viewmodel with practical examples, diagrams, and best practices. Covers c#, .net, wpf development techniques with visual explanations.

Mastering CollectionViewSource in MVVM for WPF Data Binding

Hero image for Proper way to use CollectionViewSource in ViewModel

Learn the proper way to integrate CollectionViewSource into your WPF MVVM applications for flexible and efficient data presentation, including filtering, sorting, and grouping.

In WPF applications following the Model-View-ViewModel (MVVM) pattern, presenting collections of data often requires more than just binding directly to an ObservableCollection. Features like sorting, filtering, and grouping are common requirements. While these can sometimes be implemented directly in the ViewModel, WPF provides a powerful, declarative solution: CollectionViewSource. This article will guide you through the proper way to use CollectionViewSource within your MVVM architecture, ensuring a clean separation of concerns and maintainable code.

Why Use CollectionViewSource in MVVM?

The CollectionViewSource acts as a proxy between your raw data collection (typically in the ViewModel) and the UI controls (in the View). It provides a flexible way to apply sorting, filtering, and grouping logic without modifying the underlying collection itself. This is crucial for MVVM because it keeps the ViewModel focused on data and business logic, while the View (or a component closely tied to the View) handles presentation concerns.

Directly manipulating an ObservableCollection in the ViewModel for sorting or filtering can lead to complex logic and potential performance issues if the collection is large or changes frequently. CollectionViewSource abstracts these presentation-specific operations, making your ViewModel cleaner and your View more declarative.

flowchart TD
    A[ViewModel: ObservableCollection<T>] --> B[CollectionViewSource]
    B --> C{Sorting?}
    B --> D{Filtering?}
    B --> E{Grouping?}
    C --> F[View: ItemsControl (e.g., ListBox)]
    D --> F
    E --> F
    F -- Binds to --> B
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style F fill:#ccf,stroke:#333,stroke-width:2px

Data Flow with CollectionViewSource in MVVM

Implementing CollectionViewSource

The most common and recommended approach for using CollectionViewSource in MVVM is to declare it in the View's XAML. This keeps presentation logic within the View layer, where it belongs. The CollectionViewSource then binds to a collection property exposed by your ViewModel.

using System.Collections.ObjectModel;
using System.ComponentModel;

namespace WpfApp.ViewModels
{
    public class Item : INotifyPropertyChanged
    {
        public string Name { get; set; }
        public int Value { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public class MainViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Item> Items { get; set; }

        public MainViewModel()
        {
            Items = new ObservableCollection<Item>
            {
                new Item { Name = "Apple", Value = 10 },
                new Item { Name = "Banana", Value = 5 },
                new Item { Name = "Cherry", Value = 15 },
                new Item { Name = "Date", Value = 5 },
                new Item { Name = "Elderberry", Value = 10 }
            };
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

ViewModel exposing an ObservableCollection of Item objects.

<Window x:Class="WpfApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:WpfApp.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <CollectionViewSource x:Key="MyItemsViewSource"
                              Source="{Binding Items}">
            <!-- Optional: Add sorting, filtering, grouping here -->
            <CollectionViewSource.SortDescriptions>
                <componentModel:SortDescription PropertyName="Value" Direction="Descending"/>
                <componentModel:SortDescription PropertyName="Name" Direction="Ascending"/>
            </CollectionViewSource.SortDescriptions>
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Value"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>

    <Grid>
        <ListBox ItemsSource="{Binding Source={StaticResource MyItemsViewSource}}">
            <ListBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock FontWeight="Bold" Text="{Binding Name, StringFormat='Value: {0}'}"/>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListBox.GroupStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Value}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml demonstrating CollectionViewSource with sorting and grouping.

Handling Filtering with CollectionViewSource

Filtering is a common requirement. CollectionViewSource provides a Filter event that you can subscribe to in your View's code-behind. This allows you to apply custom filtering logic based on user input or other UI-driven criteria. The ViewModel remains unaware of the filtering details, maintaining its purity.

<Window x:Class="WpfApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:WpfApp.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:MainViewModel/>
    </Window.DataContext>

    <Window.Resources>
        <CollectionViewSource x:Key="MyItemsViewSource"
                              Source="{Binding Items}"
                              Filter="MyItemsViewSource_Filter"/>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox x:Name="FilterTextBox" Grid.Row="0" TextChanged="FilterTextBox_TextChanged" Margin="5"/>
        <ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource MyItemsViewSource}}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Value}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml with a TextBox for filtering and a CollectionViewSource filter event.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using WpfApp.ViewModels;

namespace WpfApp.Views
{
    public partial class MainWindow : Window
    {
        private CollectionViewSource _itemsViewSource;

        public MainWindow()
        {
            InitializeComponent();
            _itemsViewSource = (CollectionViewSource)FindResource("MyItemsViewSource");
        }

        private void MyItemsViewSource_Filter(object sender, FilterEventArgs e)
        {
            var item = e.Item as Item;
            if (item == null) return;

            string filterText = FilterTextBox.Text.ToLower();
            if (string.IsNullOrWhiteSpace(filterText))
            {
                e.Accepted = true;
            }
            else
            {
                e.Accepted = item.Name.ToLower().Contains(filterText);
            }
        }

        private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            _itemsViewSource.View.Refresh();
        }
    }
}

Code-behind for MainWindow.xaml handling the filter logic.

Advanced Scenarios and Best Practices

While declaring CollectionViewSource in XAML is generally preferred, there are scenarios where you might need more dynamic control. For instance, if your filtering criteria are complex and driven by multiple ViewModel properties, you might expose a ICollectionView property from your ViewModel and initialize it there.

ViewModel-driven ICollectionView:

In this approach, the ViewModel creates and manages an ICollectionView instance (often obtained from CollectionViewSource.GetDefaultView or by instantiating ListCollectionView). This allows the ViewModel to directly apply sorting, filtering, and grouping based on its internal state or commands. The View then binds directly to this ICollectionView property.

This pattern is useful when the filtering/sorting logic is tightly coupled with the ViewModel's business rules and not just simple UI-driven presentation. However, it does blur the lines slightly, as the ViewModel is now aware of ICollectionView, which is a WPF-specific interface. A common compromise is to use an interface like ICollectionViewModel that exposes the ICollectionView and other presentation-related properties, keeping the core ViewModel cleaner.

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;

namespace WpfApp.ViewModels
{
    public class AdvancedMainViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Item> _allItems;
        public ICollectionView FilteredItemsView { get; private set; }

        private string _filterText;
        public string FilterText
        {
            get => _filterText;
            set
            {
                if (_filterText != value)
                {
                    _filterText = value;
                    OnPropertyChanged(nameof(FilterText));
                    FilteredItemsView.Refresh(); // Trigger filter re-evaluation
                }
            }
        }

        public AdvancedMainViewModel()
        {
            _allItems = new ObservableCollection<Item>
            {
                new Item { Name = "Apple", Value = 10 },
                new Item { Name = "Banana", Value = 5 },
                new Item { Name = "Cherry", Value = 15 },
                new Item { Name = "Date", Value = 5 },
                new Item { Name = "Elderberry", Value = 10 }
            };

            FilteredItemsView = CollectionViewSource.GetDefaultView(_allItems);
            FilteredItemsView.Filter = OnFilterPredicate;
            FilteredItemsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
        }

        private bool OnFilterPredicate(object obj)
        {
            if (string.IsNullOrWhiteSpace(FilterText)) return true;
            var item = obj as Item;
            return item != null && item.Name.ToLower().Contains(FilterText.ToLower());
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

ViewModel managing an ICollectionView for dynamic filtering and sorting.

<Window x:Class="WpfApp.Views.AdvancedMainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:WpfApp.ViewModels"
        mc:Ignorable="d"
        Title="AdvancedMainWindow" Height="450" Width="800">

    <Window.DataContext>
        <vm:AdvancedMainViewModel/>
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
        <ListBox Grid.Row="1" ItemsSource="{Binding FilteredItemsView}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}" Width="100"/>
                        <TextBlock Text="{Binding Value}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

AdvancedMainWindow.xaml binding directly to the ViewModel's ICollectionView.