2009-05-23 1 views
36

Я видел много разговоров об этом вопросе, но, возможно, я просто слишком много новичок, чтобы получить его. Если у меня есть наблюдаемая коллекция, которая представляет собой коллекцию «PersonNames», как в примере msdn (http: //msdn.microsoft.com/en-us/library/ms748365.aspx), я получаю обновления для моего представления, если добавлен или удален PersonName и т. Д. Я хочу получить обновление для моего представления, когда я измените свойство в PersonName. Как если бы я изменил имя. Я могу реализовать OnPropertyChanged для каждого свойства и получить этот класс от INotifyPropertyChanged и который, как представляется, называется как ожидается. Мой вопрос в том, как представление получает обновленные данные из ObservableCollection, поскольку измененное свойство не вызывает никаких событий для ObservableCollection. Это, наверное, что-то действительно простое, но почему я не могу найти пример, это меня удивляет. Может ли кто-нибудь пролить свет на это для меня или иметь какие-либо указания на примеры, которых я бы очень признателен. Мы имеем этот сценарий в нескольких местах в нашем текущем приложении WPF и боремся с его выяснением.ObservableCollection и Item PropertyChanged


«Как правило, код отвечает за отображение данных добавляет обработчик события к PropertyChanged каждому объекту в данный момент отображается на экране.»

Может кто-нибудь, пожалуйста, дайте мне пример того, что это значит? Мой вид привязывается к моему ViewModel, который имеет ObservableCollection. Эта коллекция состоит из RowViewModel, которая имеет свойства, которые поддерживают событие PropertiesChanged. Но я не могу понять, как сделать само обновление коллекции, поэтому мое представление будет обновлено.

ответ

4

Как вы узнали, нет события уровня коллекции, которое указывает, что свойство элемента в коллекции изменилось. Как правило, код, ответственный за отображение данных, добавляет обработчик события PropertyChanged в каждый объект, отображаемый на экране в настоящее время.

+0

Спасибо. Я использую WPF и имею DataGrid, чей ItemsSource привязан к XAML к ObservableCollection. Итак, мне нужно добавить код где-нибудь в моем ViewModel, чтобы обрабатывать событие PropertyChanged, чтобы View View знал, чтобы обновить DataGrid? И тогда мне нужно удалить и добавить элемент в коллекцию, чтобы получить его в представлении, чтобы его обновить? Кажется, что счетчик интуитивно понятен (но это не значит, что это не так :) –

+0

DataGrid делает это автоматически, если элементы в ObservableCollection реализуют INotifyPropertyChanged (или являются DependencyObjects). – Goblin

52

Вот как вы присоедините/отделитесь от события PropertyChanged каждого элемента.

ObservableCollection<INotifyPropertyChanged> items = new ObservableCollection<INotifyPropertyChanged>(); 
items.CollectionChanged += items_CollectionChanged; 

static void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
{ 
    if (e.OldItems != null) 
    { 
     foreach (INotifyPropertyChanged item in e.OldItems) 
      item.PropertyChanged -= item_PropertyChanged; 
    } 
    if (e.NewItems != null) 
    { 
     foreach (INotifyPropertyChanged item in e.NewItems) 
      item.PropertyChanged += item_PropertyChanged; 
    } 
} 

static void item_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    throw new NotImplementedException(); 
} 
+1

Это красиво. Я искал это некоторое время, и это действительно помогло мне. Большое спасибо. – pqsk

+1

Почему две измененные функции статичны? –

+1

Прошло некоторое время, и я не могу найти источник, который я абстрагировал для этого образца. Я думаю, что я использовал свойства зависимостей WPF, которые были статическими. Я не вижу причин, почему эти функции не могут быть в каждом экземпляре. – chilltemp

13

Билл,

Я уверен, что вы нашли обходной путь или решение вашей проблемы сейчас, но я это для тех, кто с этой общей проблемой. Вы можете заменить этот класс на ObservableCollections, который представляет собой коллекцию объектов, которые реализуют INotifyPropertyChanged. Это своего рода драконов, потому что в нем говорится, что для списка необходимо Сбросить, а не найти одно свойство/элемент, который изменился, но для небольших списков удар производительности должен быть неприемлемым.

Марк

using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.ComponentModel; 

namespace WCIOPublishing.Helpers 
{ 
    public class ObservableCollectionWithItemNotify<T> : ObservableCollection<T> where T: INotifyPropertyChanged 
    { 

     public ObservableCollectionWithItemNotify() 
     { 
      this.CollectionChanged += items_CollectionChanged; 
     } 


     public ObservableCollectionWithItemNotify(IEnumerable<T> collection) :base(collection) 
     { 
      this.CollectionChanged += items_CollectionChanged; 
      foreach (INotifyPropertyChanged item in collection) 
       item.PropertyChanged += item_PropertyChanged; 

     } 

     private void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      if(e != null) 
      { 
       if(e.OldItems!=null) 
        foreach (INotifyPropertyChanged item in e.OldItems) 
         item.PropertyChanged -= item_PropertyChanged; 

       if(e.NewItems!=null) 
        foreach (INotifyPropertyChanged item in e.NewItems) 
         item.PropertyChanged += item_PropertyChanged; 
      } 
     } 

     private void item_PropertyChanged(object sender, PropertyChangedEventArgs e) 
     { 
      var reset = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 
      this.OnCollectionChanged(reset); 

     } 

    } 
} 
+0

Это хорошо. Но, к счастью, вам не нужно Сбрасывать коллекцию, вы можете также заменить: var replace = new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, отправитель, отправитель, this.Items.IndexOf ((T) отправитель)); this.OnCollectionChanged (заменить); – infografnet

+0

@ Marc-Ziss Это приятное решение. – likebobby

+0

@infografnet Я не могу заставить его работать с Replace вместо этого. Может ли быть причиной того, что oldItem и newItem являются отправителями? Я убедился, что изменение свойства не проверяет, совпадает ли значение с предыдущим. – likebobby

19

Мы написали это в WPF-чат:

public class OcPropertyChangedListener<T> : INotifyPropertyChanged where T : INotifyPropertyChanged 
{ 
    private readonly ObservableCollection<T> _collection; 
    private readonly string _propertyName; 
    private readonly Dictionary<T, int> _items = new Dictionary<T, int>(new ObjectIdentityComparer()); 
    public OcPropertyChangedListener(ObservableCollection<T> collection, string propertyName = "") 
    { 
     _collection = collection; 
     _propertyName = propertyName ?? ""; 
     AddRange(collection); 
     CollectionChangedEventManager.AddHandler(collection, CollectionChanged); 
    } 

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Add: 
       AddRange(e.NewItems.Cast<T>()); 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       RemoveRange(e.OldItems.Cast<T>()); 
       break; 
      case NotifyCollectionChangedAction.Replace: 
       AddRange(e.NewItems.Cast<T>()); 
       RemoveRange(e.OldItems.Cast<T>()); 
       break; 
      case NotifyCollectionChangedAction.Move: 
       break; 
      case NotifyCollectionChangedAction.Reset: 
       Reset(); 
       break; 
      default: 
       throw new ArgumentOutOfRangeException(); 
     } 

    } 

    private void AddRange(IEnumerable<T> newItems) 
    { 
     foreach (T item in newItems) 
     { 
      if (_items.ContainsKey(item)) 
      { 
       _items[item]++; 
      } 
      else 
      { 
       _items.Add(item, 1); 
       PropertyChangedEventManager.AddHandler(item, ChildPropertyChanged, _propertyName); 
      } 
     } 
    } 

    private void RemoveRange(IEnumerable<T> oldItems) 
    { 
     foreach (T item in oldItems) 
     { 
      _items[item]--; 
      if (_items[item] == 0) 
      { 
       _items.Remove(item); 
       PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName); 
      } 
     } 
    } 

    private void Reset() 
    { 
     foreach (T item in _items.Keys.ToList()) 
     { 
      PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName); 
      _items.Remove(item); 
     } 
     AddRange(_collection); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) 
      handler(sender, e); 
    } 

    private class ObjectIdentityComparer : IEqualityComparer<T> 
    { 
     public bool Equals(T x, T y) 
     { 
      return object.ReferenceEquals(x, y); 
     } 
     public int GetHashCode(T obj) 
     { 
      return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
     } 
    } 
} 

public static class OcPropertyChangedListener 
{ 
    public static OcPropertyChangedListener<T> Create<T>(ObservableCollection<T> collection, string propertyName = "") where T : INotifyPropertyChanged 
    { 
     return new OcPropertyChangedListener<T>(collection, propertyName); 
    } 
} 
  • Слабые события
  • Отслеживает одного и того же элемента, добавляемого несколько раз в коллекцию
  • Это ~ пузыри ~ вверх по изменению свойств событий детей.
  • Статический класс предназначен только для удобства.

Используйте это так:

var listener = OcPropertyChangedListener.Create(yourCollection); 
listener.PropertyChanged += (sender, args) => { //do you stuff} 
+0

Очень приятно - мне особенно нравится, что он использует слабые события для отслеживания элементов, поскольку это устраняет значительную сложность отписки и делает его более полезным. –

+0

Учитывая все различные решения, я думаю, что это лучшая реализация. Молодец Йохан. – Maverik

+0

Действительно чистое решение, и работает красиво. – Ted

1

Вместо ObservableCollection просто использовать BindingList<T>.
В следующем коде показана привязка DataGrid к списку и свойствам элемента.

<Window x:Class="WpfApplication1.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="MainWindow" Height="350" Width="525"> 
    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" > 
     <DataGrid.Columns> 
      <DataGridTextColumn Header="Values" Binding="{Binding Value}" /> 
     </DataGrid.Columns> 
    </DataGrid> 
</Window> 

using System; 
using System.ComponentModel; 
using System.Windows; 
using System.Windows.Threading; 

namespace WpfApplication1 { 
    public partial class MainWindow : Window { 
     public MainWindow() { 
      var c = new BindingList<Data>(); 
      this.DataContext = c; 
      // add new item to list on each timer tick 
      var t = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) }; 
      t.Tick += (s, e) => { 
       if (c.Count >= 10) t.Stop(); 
       c.Add(new Data()); 
      }; 
      t.Start(); 
     } 
    } 

    public class Data : INotifyPropertyChanged { 
     public event PropertyChangedEventHandler PropertyChanged = delegate { }; 
     System.Timers.Timer t; 
     static Random r = new Random(); 
     public Data() { 
      // update value on each timer tick 
      t = new System.Timers.Timer() { Interval = r.Next(500, 1000) }; 
      t.Elapsed += (s, e) => { 
       Value = DateTime.Now.Ticks; 
       this.PropertyChanged(this, new PropertyChangedEventArgs("Value")); 
      }; 
      t.Start(); 
     } 
     public long Value { get; private set; } 
    } 
} 
1

Ниже приводится код дает простое объяснение ответа на @Stack и показывая, как BindingList наблюдает, если у него есть пункт изменен и показывает ObservableCollection не будет наблюдать за изменением внутри элемента.

using System; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 

namespace BindingListExample 
{ 
    class Program 
    { 
     public ObservableCollection<MyStruct> oc = new ObservableCollection<MyStruct>(); 
     public System.ComponentModel.BindingList<MyStruct> bl = new BindingList<MyStruct>(); 

     public Program() 
     { 
      oc.Add(new MyStruct()); 
      oc.CollectionChanged += CollectionChanged; 

      bl.Add(new MyStruct()); 
      bl.ListChanged += ListChanged; 
     } 

     void ListChanged(object sender, ListChangedEventArgs e) 
     { 
      //Observe when the IsActive value is changed this event is triggered. 
      Console.WriteLine(e.ListChangedType.ToString()); 
     } 

     void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 
     { 
      //Observe when the IsActive value is changed this event is not triggered. 
      Console.WriteLine(e.Action.ToString()); 
     } 

     static void Main(string[] args) 
     { 
      Program pm = new Program(); 
      pm.bl[0].IsActive = false; 
     } 
    } 

    public class MyStruct : INotifyPropertyChanged 
    { 
     public event PropertyChangedEventHandler PropertyChanged; 
     private bool isactive; 
     public bool IsActive 
     { 
      get { return isactive; } 
      set 
      { 
       isactive = value; 
       NotifyPropertyChanged("IsActive"); 
      } 
     } 

     private void NotifyPropertyChanged(String PropertyName) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(PropertyName)); 
      } 
     } 
    } 
}