Уведомление о DataGrid при изменении Коллекции внутри Коллекции - PullRequest
1 голос
/ 07 февраля 2012

Так как я часами работаю над очень сложной проблемой:

Какова DataGrid , которая связана с ObservableCollection правильно , обновлена ​​, когда другая ObservableCollection, которая находится внутри ObservableCollection DataGrid связан с изменениями?

Пока DataGrid обновляется только когда я щелкаю по соответствующей ячейке.

Я подготовил полный пример исходного кода, чтобы проиллюстрировать следующую (очень простую) ситуацию:

Существует ViewModel, которая содержит список. Этот список является ObservableCollection и содержит две вещи: целое число и другой список (снова ObservableCollection), который содержит четыре целых числа. Затем существует DataGrid, который имеет два столбца. Один столбец для целого числа и один столбец для списка целых чисел. Это маленькое приложение имеет кнопки для изменения целых чисел во вложенном списке, то есть добавления +1 к одному из четырех целых чисел. GOAL заключается в том, что изменение вложенного списка отражается в DataGrid. Пока что ПРОБЛЕМА состоит в том, что это происходит только с внешним триггером (например, щелчок по соответствующей ячейке или щелчок по заголовку одного столбца, который сортирует столбец и т. Д.)

Итак, вот полный код:

Это код ViewModel, к которому привязана DataGrid:

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;

    }

И, наконец, это XAML, где DataGrid становится связанным:

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

3 голосов
/ 07 февраля 2012

Я уже говорил вам , что вам просто нужно инициировать события изменения для свойства 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 голос
/ 07 февраля 2012

Почему люди настаивают на создании классов, которые наследуются от 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");
}
...