WPF Grouping using CollectionViewSource and DataBinding

collectionviewsource, data-binding, wpf

Solution

I have found a hack way around this problem.

private CollectionViewSource _viewSource;

private void ModifyData()
{
    // Modify some data

    // This will cause the ListView to refresh its data and update the UI
    // and also cause the converter to be called to reformat the data.
    _viewSource.View.Refresh();
}

Hope that helps.

Problem

I'm binding a `CollectionViewSource` to a `ListView` to group items. It all works great except when I update the `ObservableCollection` the `CollectionViewSource` is based on. If I update a value of an object in the collection the UI is never updated. Here is an example: ``` <ListView x:Name="MyListView" Margin="0,0,0,65"> <ListView.GroupStyle> <GroupStyle> <GroupStyle.ContainerStyle> <Style TargetType="{x:Type GroupItem}"> <Setter Property="Margin" Value="0,0,0,5"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type GroupItem}"> <Expander IsExpanded="true" BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1"> <Expander.Header> <DockPanel> <TextBlock FontWeight="Bold" Text="{Binding Name}" Margin="5,0,0,0" Width="80"/> <TextBlock FontWeight="Bold" Width="60" TextAlignment="Center" Margin="16,0,0,0" Text="{Binding Items, Converter={StaticResource Converter2}}" /> </DockPanel> </Expander.Header> <ItemsPresenter /> </Expander> </ControlTemplate> </Setter.Value> </Setter> </Style> </GroupStyle.ContainerStyle> </GroupStyle> </ListView.GroupStyle> <ListView.View> <GridView> <GridViewColumn Width="300" Header="Amount" > <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Amount}" Margin="80,0,0,0"/> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView> ``` You'll notice it's calling a converter in the group and giving it the items collection. This is so the converter can calculate the average of the rows and return that result: ``` public class AverageConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { IEnumerable<object> rows = (IEnumerable<object>) value; double average = rows.Average(a => ((DisplayRow) a).Amount); return average; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } ``` In the code behind I add the rows and create the `CollectionViewSource`: private readonly ObservableCollection displayRows = new ObservableCollection(); ``` public Window1() { InitializeComponent(); displayRows.Add(new DisplayRow { Title = "Sales", Amount=16} ); displayRows.Add(new DisplayRow { Title = "Marketing", Amount=14} ); displayRows.Add(new DisplayRow { Title = "Technology", Amount=13} ); displayRows.Add(new DisplayRow { Title = "Sales", Amount=11} ); displayRows.Add(new DisplayRow { Title = "Marketing", Amount=13} ); displayRows.Add(new DisplayRow { Title = "Technology", Amount=12} ); CollectionViewSource viewSource = new CollectionViewSource { Source = displayRows }; viewSource.GroupDescriptions.Add(new PropertyGroupDescription("Title")); MyListView.ItemsSource = viewSource.View; } ``` The `DisplayRow` object implements `INotifyPropertyChanged`, and is just a simple class. Everything works well and the display is the way I want, but if I change a value in the `ObservableCollection` the UI doesn't change. If I add an element to the collection I can see it appear on the screen but the converter is never called to recompute the average. Any ideas?

Original source