Since hours I am working on a very hard problem:
How is a DataGrid that is bound to an ObservableCollection correctly updated when another ObservableCollection that is inside the ObservableCollection the DataGrid is bound to changes ?
So far the DataGrid onnly refreshes when i click on the corresponding cell.
I have prepared a complete source code example to illustrate the following (very simple) situation:
There is a ViewModel that holds a List. This List is an ObservableCollection and hold two things: An integer and another List (again an ObservableCollection) that holds four integers. Then there is a DataGrid that has two columns. One column for the integer number and one column for the list of integers. This little application has buttons to modify the integers in the nested list i.e. adding +1 to one of the four integers. The GOAL is that the modification of the nested list is getting reflected by the DataGrid. The PROBLEM so far is, that this only happens on a with an external trigger (e.g. a click on the corresponding cell, or a click on one column header that sorts a column etc.)
So here is the complete code:
This is the ViewModel code that the DataGrid is bound to:
public class ViewModel: INotifyPropertyChanged {
private Items items;
public Items Items {
get { return items; }
set {
items = value;
firePropertyChanged("Items");
}
}
public ViewModel() {
Items = new Items();
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Items: ObservableCollection<Item> {
public Items()
: base() {
this.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
private void OnCollectionChanged(object o, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
public class List: ObservableCollection<NumberItem> {
public List()
: base() {
this.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
}
private void OnCollectionChanged(object o, NotifyCollectionChangedEventArgs e) {
if (e.NewItems != null) {
foreach (Object item in e.NewItems) {
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
if (e.OldItems != null) {
foreach (Object item in e.OldItems) {
(item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
public class NumberItem : INotifyPropertyChanged {
private int number;
public int Number {
get { return number; }
set {
number = value;
firePropertyChanged("Number");
}
}
public NumberItem(int i) {
Number = i;
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item : INotifyPropertyChanged {
private List list;
public List List {
get { return list; }
set {
list = value;
firePropertyChanged("List");
}
}
private int numberOne;
public int NumberOne {
get { return numberOne; }
set {
numberOne = value;
firePropertyChanged("NumberOne");
}
}
private void firePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
/// <summary>
/// This converter simply transforms the list of integers into a string.
/// </summary>
public class Converter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
List l = (List)value;
string s = "";
return s + l[0].Number + " " + l[1].Number + " " + l[2].Number + " " + l[3].Number;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return null;
}
}
The code of the buttons that manipulate the integers of the nested list is the following:
private void plus1L(object sender, RoutedEventArgs e) {
vm.Items[0].List[0].Number += 1;
}
And finally this is the XAML where the DataGrid is getting bound:
<sdk:DataGrid x:Name="dg" Margin="17,139,21,0" ItemsSource="{Binding Items}" AutoGenerateColumns="False" VerticalAlignment="Top" Height="164" d:LayoutOverrides="Width, HorizontalMargin">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="A" Header="A" Binding="{Binding NumberOne}"/>
<sdk:DataGridTextColumn x:Name="List" Header="List" Binding="{Binding List, Converter={StaticResource Converter}}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>*emphasized text*