WPF Grouping using CollectionViewSource and DataBinding
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?
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.
Without refreshing the whole view, this can still be handled, I implemented this.
Creating databindings inside a CollectionView will allow change notifications for the groups.
Check out http://stumblingaroundinwpf.blogspot.com/2009/11/building-smarter-wpf-collectionview-one.html
If the data source is an ObservableCollection, the View will be updated whenever the collection is changed i.e. when there are items added or removed from it. If you want the View to be updated when the underlying data is edited, then that class must implement the INotifyPropertyChanged interface.
In your case DisplayRow class must implement INotifyPropertyChanged and displayRows should be an ObservableCollection.
This is the official way to do it, from what I understand. I might be wrong.
精彩评论