Proper way to use CollectionViewSource in ViewModel
Categories:
Mastering CollectionViewSource in MVVM for WPF Data Binding

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.
CollectionViewSource
is typically declared in XAML, you can also instantiate and configure it in code-behind if dynamic changes to its properties (like Source
or Filter
) are frequently driven by UI interactions. However, for static configurations, XAML is preferred for clarity.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.
Filter
event handler. This handler should primarily focus on presentation-specific filtering based on the current UI state. If filtering requires significant data processing or interaction with services, consider exposing a filtered collection from your ViewModel, or using a more advanced filtering mechanism that can be bound to ViewModel properties.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.