Collection内的Collection更改时DataGrid的通知

时间:2012-02-07 18:15:52

标签: wpf silverlight data-binding datagrid inotifypropertychanged

我几个小时以来一直在努力解决一个非常棘手的问题:

ObservableCollection DataGrid 绑定到 ObservableCollection 正确更新 > DataGrid 是否会发生变化?

到目前为止,当我点击相应的单元格时,DataGrid只会刷新。

我准备了一个完整的源代码示例来说明以下(非常简单)的情况:

有一个包含List的ViewModel。这个List是一个ObservableCollection,它包含两个东西:一个整数和另一个包含四个整数的List(同样是一个ObservableCollection)。 然后有一个DataGrid有两列。一列用于整数,一列用于整数列表。 这个小应用程序有按钮来修改嵌套列表中的整数,即向四个整数中的一个添加+1。 GOAL 是嵌套列表的修改由DataGrid反映出来。 到目前为止,问题是,这只发生在带有外部触发器的情况下(例如,单击相应的单元格,或单击对列进行排序的一个列标题等)。

所以这是完整的代码:

这是DataGrid绑定的ViewModel代码:

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;
    }
}

操纵嵌套列表整数的按钮代码如下:

private void plus1L(object sender, RoutedEventArgs e) {

        vm.Items[0].List[0].Number += 1;

    }

最后这是DataGrid绑定的XAML:

<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*

2 个答案:

答案 0 :(得分:3)

I already told you当您更改任何时,您只需触发List属性的更改事件,这并不难......

修改:在处理程序中,您以某种方式更改列表 ,但您什么都不做。

private void plus1L(object sender, RoutedEventArgs e)
{
    vm.Items[0].List[0].Number += 1;
    vm.Items[0].OnPropertyChanged("List"); // This is needed if you bind to List.
}

更明确地说明:绑定不关心绑定到的属性路径以外的任何内容。在绑定属性中发生的所有内容都是未知的,因此您需要转发内部更改。

答案 1 :(得分:1)

为什么人们坚持创建继承自ObservableCollection<SomeObject>的类?他们认为使用ObservableCollection<Item>作为数据类型并使用内置更改通知会出现问题???

无论如何,这样做:

public class SomeViewModel : INotifyPropertyChanged
{
    public ObservableCollection<MyItem> OuterCollection { get; set; }
}

public class MyItem : INotifyPropertyChanged
{
    public int SomeInt { get; set; }
    public ObservableCollection<int> InnerCollection { get; set; }
}

您的XAML看起来很正常,但是如果更改InnerCollection中的值,则WPF不知道它,因为ObservableCollection应该监视集合的更改,而不是更改到集合中的项目。

要更新用户界面,您需要针对PropertyChange提出InnerCollection通知。

myItem.InnerCollection[0]++;
myItem.RaisePropertyChanged("InnerCollection");

如果InnerCollection包含实施INotifyPropertyChanged的对象,您可以订阅他们的PropertyChanged个事件,以便在PropertyChanged之前提升InnerCollection个事件物品变化。

void SomeConstructor()
{
    InnerCollection = new ObservableCollection<SomeItem>();
    InnerCollection.CollectionChanged += InnerCollection_CollectionChanged;
}

void InnerCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        for each (SomeItem item in e.NewItems)
            item.PropertyChanged += SomeItem_PropertyChanged;

    if (e.OldItems!= null)
        for each (SomeItem item in e.OldItems)
            item.PropertyChanged -= SomeItem_PropertyChanged;
}

void SomeItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    RaisePropertyChanged("InnerCollection");
}