ObservableCollection и Item PropertyChanged
Я видел много разговоров по этому вопросу, но, может быть, я слишком много новичка, чтобы понять это. Если у меня есть наблюдаемая коллекция, которая представляет собой коллекцию "PersonNames", как в примере с msdn ( http://msdn.microsoft.com/en-us/library/ms748365.aspx), я получаю обновления для своего View, если PersonName
добавлено или удалено и т. д. Я хочу получить обновление для своего просмотра, когда я изменяю свойство в PersonName
также. Например, если я поменяю имя. Я могу реализовать OnPropertyChanged
для каждого свойства и этот класс наследуется от INotifyPropertyChanged
и это, кажется, называют как ожидалось. Мой вопрос: как View получает обновленные данные из ObservableCollection
так как свойство изменилось, не вызывает никакого события для ObservableCollection
, Это, наверное, что-то действительно простое, но почему я не могу найти пример, удивляет меня. Может кто-нибудь пролить свет на это для меня или есть ссылки на примеры, я был бы очень признателен. У нас есть этот сценарий в нескольких местах в нашем текущем приложении WPF, и мы пытаемся понять его.
"Как правило, код, отвечающий за отображение данных, добавляет PropertyChanged
обработчик событий для каждого объекта, отображаемого в данный момент на экране."
Может ли кто-нибудь дать мне пример того, что это значит? Мой вид привязывается к моему ViewModel
который имеет ObservableCollection
, Эта коллекция состоит из RowViewModel
который имеет свойства, которые поддерживают PropertiesChanged
событие. Но я не могу понять, как сделать обновление самой коллекции, чтобы мой взгляд был обновлен.
6 ответов
Вот как вы можете прикрепить / отсоединить событие каждого элемента 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();
}
Мы написали это в 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}
Билл,
Я уверен, что вы уже нашли обходной путь или решение вашей проблемы, но я опубликовал это для всех, у кого есть эта общая проблема. Вы можете заменить этот класс 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);
}
}
}
Как вы выяснили, нет события уровня коллекции, которое бы указывало на то, что свойство элемента в коллекции изменилось. Как правило, код, отвечающий за отображение данных, добавляет обработчик события PropertyChanged к каждому объекту, отображаемому в данный момент на экране.
Вместо ObservableCollection просто используйте BindingList
В следующем коде показана привязка 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; }
}
}
Ниже приведен код, дающий простое объяснение ответа @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));
}
}
}
}