【问题标题】:Notification of DataGrid when Collection inside Collection changes当 Collection 内的 Collection 发生变化时 DataGrid 的通知
【发布时间】:2012-02-29 04:39:34
【问题描述】:

因为我一直在研究一个非常困难的问题:

ObservableCollection 内的另一个 ObservableCollection 时,DataGrid 如何正确更新 绑定到 ObservableCollection > DataGrid 是否会发生变化?

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

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

有一个 ViewModel 包含一个 List。这个 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*

【问题讨论】:

    标签: wpf silverlight data-binding datagrid inotifypropertychanged


    【解决方案1】:

    为什么人们坚持创建继承自ObservableCollection&lt;SomeObject&gt; 的类?他们认为使用ObservableCollection&lt;Item&gt; 作为数据类型并使用内置更改通知有什么问题吗???

    无论如何,这样做:

    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 应该监视对集合的更改,而不是对收藏。

    要更新 UI,您需要为 InnerCollection 发出 PropertyChange 通知。

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

    如果InnerCollection 包含实现INotifyPropertyChanged 的对象,您可以订阅它们的PropertyChanged 事件,以便在其中一项更改时为InnerCollection 引发PropertyChanged 事件。

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

    【讨论】:

    • 你好 Rachel :) 很高兴再次阅读你的文章 :) H.B.整天陪伴我解决我们现在解决的绑定问题..不过也感谢您给我正确的提示 (myItem.RaisePropertyChanged("InnerCollection");) ...关于 ObservableCollection 继承:我认为这是必要的用一些能力扩展集合,但老实说,我这样做主要是为了支持一般的编程风格(即,如果我想扩展我能够)并且因为 ItemList items 看起来比 ObservableCollection items 在大多数情况下 :)
    • @Marc 我想如果您将List&lt;int&gt; 扩展到它自己的类中,那么我可以看到您的推理,但是如果您不这样做(我也不会!)那么它就不需要是自己的班级。类型名称说明了一切:它是一个可观察的集合,它只是一个带有更改通知的集合。很高兴你把它解决了。
    • @Rachel 和 (@)H.B.你知道吗,我真的很喜欢你们:)
    • @Marc 我不知道为什么......我们都倾向于对 SO 问题感到沮丧,它显示在我们的答案/cmets 中(HB 比我多 ^_^)
    【解决方案2】:

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

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

    【讨论】:

    • 是的,你告诉我的。我做了你告诉我的事情;)但它仍然不起作用。以上是我的完整源代码 - 如果我在将其包含在源代码中之前的几个小时就理解了你的话。
    • 好的,现在可以了。由于树木太多,我只是失去了森林的焦点。几个小时盯着同一个问题也不是一个好方法。尽管如此,如果我有可能我会付给你一杯啤酒和/或和你一起喝……但在这里我只能说:谢谢! :)
    • ...但我想我已经惹恼了你,以至于你会在没有我的情况下喝你的啤酒;)
    • @Marc:不客气 :) 不用担心,但是您可以在其他问题中澄清问题,因为您没有给我完整的图片,它会保存我想我们俩都有点麻烦。
    猜你喜欢
    • 1970-01-01
    • 2013-03-22
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 2017-10-18
    • 2022-07-22
    • 2017-08-15
    • 1970-01-01
    相关资源
    最近更新 更多