I wasn't expecting this to work but I wanted to try it. I have a bool that should tell me if the user has selected anything in my application (inside a particular control).
I wanted to bind something to that bool:
private bool _isAnythingSelected;
public bool IsAnythingSelected
{
get
{
_isAnythingSelected = (MyModel.Series.Where(p => p.IsSelected && p.GetType() == typeof(LineSeries)).Count() > 0);
return _isAnythingSelected;
}
set
{
_isAnythingSelected = value;
RaisePropertyChanged("IsAnythingSelected");
}
}
This is not working as I would like it, and I understand why it is so. My question is, how should I implement this selection thing without going to every method that allows user to select things? Thank you.
To update IsAnythingSelected properly, you have to handle two kinds of notifications:
notification about MyModel.Series collection changed;
notification about MyModel.Series collection item property changed.
The first one can be achieved "out of the box" with ObservableCollection<T> (or any other collection, that implements INotifyCollectionChanged.
The second one requires a custom solution (at least, I don't know any existing "out of the box" one).
You can combine ObservableCollection<T> with item property changed handling this way:
class MyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
private void Initialize()
{
// initial PropertyChanged subscription
foreach (var item in Items)
{
SubscribeItemPropertyChanged(item);
}
}
private void SubscribeItemPropertyChanged(object item)
{
((INotifyPropertyChanged)item).PropertyChanged += HandleItemPropertyChanged;
}
private void UnSubscribeItemPropertyChanged(object item)
{
((INotifyPropertyChanged)item).PropertyChanged -= HandleItemPropertyChanged;
}
protected virtual void HandleItemPropertyChanged(object sender, PropertyChangedEventArgs args)
{
var handler = ItemPropertyChanged;
if (handler != null)
{
handler(sender, args);
}
}
protected override void ClearItems()
{
// we should unsubscribe from INotifyPropertyChanged.PropertyChanged event for each item
Items.ToList().ForEach(item => Remove(item));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
// subscribe for new items property changing;
// un-subscribe for old items property changing
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SubscribeItemPropertyChanged(e.NewItems[0]);
break;
case NotifyCollectionChangedAction.Remove:
UnSubscribeItemPropertyChanged(e.OldItems[0]);
break;
case NotifyCollectionChangedAction.Replace:
SubscribeItemPropertyChanged(e.NewItems[0]);
UnSubscribeItemPropertyChanged(e.OldItems[0]);
break;
}
}
public MyObservableCollection()
: base()
{
}
public MyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
Initialize();
}
public MyObservableCollection(List<T> list)
: base(list)
{
Initialize();
}
public EventHandler<PropertyChangedEventArgs> ItemPropertyChanged;
}
...and make MyModel.Series a MyObservableCollection<T> instance.
Then, your class, which contains IsAnythingSelected, will look like this:
// somewhere in code, where `MyModel` being initialized:
MyModel.Series.CollectionChanged += (sender, args) => RaisePropertyChanged("IsAnythingSelected");
MyModel.Series.ItemPropertyChanged += (sender, args) => RaisePropertyChanged("IsAnythingSelected");
public bool IsAnythingSelected
{
get
{
return MyModel.Series.Any(p => p.IsSelected && p.GetType() == typeof(LineSeries));
}
}
You even don't need IsAnythingSelected to be read-write property in this case. Just notify, that it was changed, and binding engine will re-read its value.
Related
I created a Class EventList inheriting List which fires an Event each time something is Added, Inserted or Removed:
public class EventList<T> : List<T>
{
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
public new void Add(T item)
{
base.Add(item);
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
...
}
At the Moment I use it as a Property like this:
public EventList List
{
get { return m_List; }
set
{
m_List.ListChanged -= List_ListChanged;
m_List = value;
m_List.ListChanged += List_ListChanged;
List_ListChanged();
}
}
Now my Problem is, can I somehow handle if a new Object is referred to it or prevent that, so I do not have to do the event wiring stuff in the setter?
Of course, I can change the property to "private set" but I would like to be able to use the class as variable as well.
You seldom create a new instance of a collection class in a class. Instantiate it once and clear it instead of creating a new list. (and use the ObservableCollection since it already has the INotifyCollectionChanged interface inherited)
private readonly ObservableCollection<T> list;
public ctor() {
list = new ObservableCollection<T>();
list.CollectionChanged += listChanged;
}
public ObservableCollection<T> List { get { return list; } }
public void Clear() { list.Clear(); }
private void listChanged(object sender, NotifyCollectionChangedEventArgs args) {
// list changed
}
This way you only have to hook up events once, and can "reset it" by calling the clear method instead of checking for null or equality to the former list in the set accessor for the property.
With the changes in C#6 you can assign a get property from a constructor without the backing field (the backing field is implicit)
So the code above can be simplified to
public ctor() {
List = new ObservableCollection<T>();
List.CollectionChanged += OnListChanged;
}
public ObservableCollection<T> List { get; }
public void Clear()
{
List.Clear();
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs args)
{
// react to list changed
}
ObservableCollection is a List with a CollectionChanged event
ObservableCollection.CollectionChanged Event
For how to wire up the event handler see answer from Patrick. +1
Not sure what you are looking for but I use this for a collection with one event that fires on add, remove, and change.
public class ObservableCollection<T>: INotifyPropertyChanged
{
private BindingList<T> ts = new BindingList<T>();
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged( String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public BindingList<T> Ts
{
get { return ts; }
set
{
if (value != ts)
{
Ts = value;
if (Ts != null)
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
NotifyPropertyChanged("Ts");
}
}
}
private static void OnListChanged(ObservableCollection<T> vm)
{
// this will fire on add, remove, and change
// if want to prevent an insert this in not the right spot for that
// the OPs use of word prevent is not clear
// -1 don't be a hater
vm.NotifyPropertyChanged("Ts");
}
public ObservableCollection()
{
ts.ListChanged += delegate(object sender, ListChangedEventArgs args)
{
OnListChanged(this);
};
}
}
If you do not want to or can not convert to an Observable Collection, try this:
public class EventList<T> : IList<T> /* NOTE: Changed your List<T> to IList<T> */
{
private List<T> list; // initialize this in your constructor.
public event ListChangedEventDelegate ListChanged;
public delegate void ListChangedEventDelegate();
private void notify()
{
if (ListChanged != null
&& ListChanged.GetInvocationList().Any())
{
ListChanged();
}
}
public new void Add(T item)
{
list.Add(item);
notify();
}
public List<T> Items {
get { return list; }
set {
list = value;
notify();
}
}
...
}
Now, for your property, you should be able to reduce your code to this:
public EventList List
{
get { return m_List.Items; }
set
{
//m_List.ListChanged -= List_ListChanged;
m_List.Items = value;
//m_List.ListChanged += List_ListChanged;
//List_ListChanged();
}
}
Why? Setting anything in the EventList.Items will call your private notify() routine.
I have a Solution for when someone calls the Generic method from IList.add(object). So that you also get notified.
using System;
using System.Collections;
using System.Collections.Generic;
namespace YourNamespace
{
public class ObjectDoesNotMatchTargetBaseTypeException : Exception
{
public ObjectDoesNotMatchTargetBaseTypeException(Type targetType, object actualObject)
: base(string.Format("Expected base type ({0}) does not match actual objects type ({1}).",
targetType, actualObject.GetType()))
{
}
}
/// <summary>
/// Allows you to react, when items were added or removed to a generic List.
/// </summary>
public abstract class NoisyList<TItemType> : List<TItemType>, IList
{
#region Public Methods
/******************************************/
int IList.Add(object item)
{
CheckTargetType(item);
Add((TItemType)item);
return Count - 1;
}
void IList.Remove(object item)
{
CheckTargetType(item);
Remove((TItemType)item);
}
public new void Add(TItemType item)
{
base.Add(item);
OnItemAdded(item);
}
public new bool Remove(TItemType item)
{
var result = base.Remove(item);
OnItemRemoved(item);
return result;
}
#endregion
# region Private Methods
/******************************************/
private static void CheckTargetType(object item)
{
var targetType = typeof(TItemType);
if (item.GetType().IsSubclassOf(targetType))
throw new ObjectDoesNotMatchTargetBaseTypeException(targetType, item);
}
#endregion
#region Abstract Methods
/******************************************/
protected abstract void OnItemAdded(TItemType addedItem);
protected abstract void OnItemRemoved(TItemType removedItem);
#endregion
}
}
If an ObservableCollection is not the solution for you, you can try that:
A) Implement a custom EventArgs that will contain the new Count attribute when an event will be fired.
public class ChangeListCountEventArgs : EventArgs
{
public int NewCount
{
get;
set;
}
public ChangeListCountEventArgs(int newCount)
{
NewCount = newCount;
}
}
B) Implement a custom List that inherits from List and redefine the Count attribute and the constructors according to your needs:
public class CustomList<T> : List<T>
{
public event EventHandler<ChangeListCountEventArgs> ListCountChanged;
public new int Count
{
get
{
ListCountChanged?.Invoke(this, new ChangeListCountEventArgs(base.Count));
return base.Count;
}
}
public CustomList()
{ }
public CustomList(List<T> list) : base(list)
{ }
public CustomList(CustomList<T> list) : base(list)
{ }
}
C) Finally subscribe to your event:
var myList = new CustomList<YourObject>();
myList.ListCountChanged += (obj, e) =>
{
// get the count thanks to e.NewCount
};
I have implemented CollectionChanged for an ObservableCollection just like here:
Implementing CollectionChanged
Is there a possibility in the OnCollectionChanged method to find out what the name of the changed property is?
EDIT:
A history should be written if there is any change in the class.
There are three cases I want to achieve:
"Normal" property is changed (string, int, ...): this is already
working
Add and remove in collections: would be working if I know the name of the changed collection
Property inside a collection is changed: same problem as 2) I don't know the name (and index) of the ch
public class ParentClass : BaseModel
{
public ParentClass()
{
Children = new ObservableCollection<SomeModel>();
Children.CollectionChanged += Oberservable_CollectionChanged;
}
private string id;
public string Id
{
get
{
return id;
}
set
{
string oldId = id;
id = value;
OnPropertyChanged(oldArticleId,id);
}
}
public ObservableCollection<SomeModel> Children { get; set; }
protected virtual void OnPropertyChanged(object oldValue, object newValue, [CallerMemberName] string propertyName = null)
{
//1) Entry is added to history (this part is working)
}
protected void Oberservable_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (INotifyPropertyChanged added in e.NewItems)
{
added.PropertyChanged += OnPropertyChangedHandler;
OnCollectionChanged(CollectionChangedModel.Action.Added, added);
}
}
if (e.OldItems != null)
{
foreach (INotifyPropertyChanged removed in e.OldItems)
{
removed.PropertyChanged -= OnPropertyChangedHandler;
OnCollectionChanged(CollectionChangedModel.Action.Removed, removed);
}
}
}
protected virtual void OnCollectionChanged(CollectionChangedModel.Action action, object value)
{
//2) TODO: History should be written, but I don't have the property name
}
public void OnPropertyChangedHandler(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
//3) TODO: History about changed property in child-class (not working because of missing property name and index)
}
}
This is correct:
Children = new ObservableCollection<SomeModel>();
Children.CollectionChanged += Oberservable_CollectionChanged;
Though you have to ensure nobody will change collection, e.g. like this:
public ObservableCollection<SomeModel> Children { get; }
or rather making it a full property and subscribing/unsubscribing in the setter.
And now regarding Oberservable_CollectionChanged handler:
Add and remove in collections: would be working if I know the name of the changed collection
Add sender to your event.
Wrap arguments into ...EventArgs class (make it immutable), see msdn.
public event EventHandler<MyCollectionChangedEventArgs> CollectionChanged;
protected virtual void OnCollectionChanged(object sender, MyCollectionChangedEventArgs e)
{
//2) TODO: History should be written, but I don't have the property name
// sender is name
// e.Action and e.Value are parameters
}
Property inside a collection is changed: same problem as 2) I don't know the name (and index) of the ch
Wrap event handlers into instance containing event handler and value you need (I am leaving that task for you). If unsubscribing is not required, this can be easily achieved by using lamdba closures:
foreach (var added in e.NewItems.OfType<INotifyPropertyChanged>)
{
added.PropertyChanged += (s, e) =>
{
// you have `sender` here in addition to `s`, clever?
};
}
i'm profiling a silverlight component wrote by someone else.
I found many hotspots and bottlenecks, now i came across this one:
public static class CollectionExtensions
{
public static void AddRange<T>(this ObservableCollection<T> collection, IEnumerable<T> items)
{
foreach (var item in items)
{
collection.Add(item);
}
}
}
this extension method, of course, add the AddRange method to an ObservableCollection, but it's pretty intensive in calculation.
Does anyone have a better implementation, or any suggestion on how increase performance of this piece of cose?
Thank you
Calling Add multiple times results in the INotifyCollectionChanged being raised multiple times often causing the UI to redraw itself.
While Lee's answer is technically correct that raising a Reset event is the correct approach once all items have been added, I have found from experience that many grid controls (for example) do not actively support the Reset event.
The option that is most universally supported is to modify the collection away from the ObservableCollection and recreate the ObservableCollection property itself.
In other words with your ObservableCollection defined as follows on your VM...
private ObservableCollection<MyItem> _items;
public ObservableCollection<MyItem> Items {
get { return _items;}
set
{
_items = value;
OnPropertyChanged(()=> Items);
}
}
...add your new items as follows...
var tempColl = _items.ToList();
tempColl.AddRange(newItems);
Items = new ObservableCollection(tempColl);
Another thing to bear in mind about this technique is that it is thread-safe because you can add items to the ObservableCollection from a background thread if you recreate the ObservableCollection. A normal ObservableCollection cannot have items added to it via the Add method from a non-Dispatcher thread.
This is due to the ObservableCollection firing the PropertyChanged event each time an item is added to the collection. Preventing this event from firing while bulk-adding items is what you want to look at. Here is an elegant solution, though I have not tried this myself.
https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/
The cost here is generally due to the change notification that is raised for each individual add. What can be preferable to do is to create a new collection implementation that is optimized for accepting ranges of data. Instead of raising change notifications for each change, and then the Binding engine processing each as single updates, you can add all the values, then raise a single event. This event can either have the big hammer of being a Reset, or you can provide the items that changed, and the index at which they changed from.
This is an example that uses a single Reset notification on its AddRange method:
/// <summary>
/// An implementation of <seealso cref="ObservableCollection{T}"/> that provides the ability to suppress
/// change notifications. In sub-classes that allows performing batch work and raising notifications
/// on completion of work. Standard usage takes advantage of this feature by providing AddRange method.
/// </summary>
/// <typeparam name="T">The type of elements in the list.</typeparam>
public class ObservableList<T> : ObservableCollection<T>
{
#region Fields
private readonly Queue<PropertyChangedEventArgs> _notifications = new Queue<PropertyChangedEventArgs>();
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionNotifications = new Queue<NotifyCollectionChangedEventArgs>();
private int _notificationSupressionDepth;
#endregion
public ObservableList()
{
}
public ObservableList(IEnumerable<T> collection)
: base(collection)
{
}
public void AddRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void RemoveRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
foreach (var item in list)
{
Remove(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void ReplaceRange(IEnumerable<T> list)
{
using (SupressNotifications())
{
Clear();
foreach (var item in list)
{
Add(item);
}
}
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnCollectionChanged(e);
}
else
{
//We cant filter duplicate Collection change events as this will break how UI controls work. -LC
_collectionNotifications.Enqueue(e);
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (_notificationSupressionDepth == 0)
{
base.OnPropertyChanged(e);
}
else
{
if (!_notifications.Contains(e, NotifyEventComparer.Instance))
{
_notifications.Enqueue(e);
}
}
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected IDisposable QueueNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
TryNotify();
});
}
protected IDisposable SupressNotifications()
{
_notificationSupressionDepth++;
return Disposable.Create(() =>
{
_notificationSupressionDepth--;
});
}
private void TryNotify()
{
if (_notificationSupressionDepth == 0)
{
while (_collectionNotifications.Count > 0)
{
var collectionNotification = _collectionNotifications.Dequeue();
base.OnCollectionChanged(collectionNotification);
}
while (_notifications.Count > 0)
{
var notification = _notifications.Dequeue();
base.OnPropertyChanged(notification);
}
}
}
}
EDIT: Adding the missing NotifyEventComparer class and an example Disposable.Create method
public sealed class NotifyEventComparer : IEqualityComparer<PropertyChangedEventArgs>
{
public static readonly NotifyEventComparer Instance = new NotifyEventComparer();
bool IEqualityComparer<PropertyChangedEventArgs>.Equals(PropertyChangedEventArgs x, PropertyChangedEventArgs y)
{
return x.PropertyName == y.PropertyName;
}
int IEqualityComparer<PropertyChangedEventArgs>.GetHashCode(PropertyChangedEventArgs obj)
{
return obj.PropertyName.GetHashCode();
}
}
//Either use Rx to access Disposable.Create or this simple implementation will do
public static class Disposable
{
public static IDisposable Create(Action dispose)
{
if (dispose == null)
throw new ArgumentNullException("dispose");
return new AnonymousDisposable(dispose);
}
private sealed class AnonymousDisposable : IDisposable
{
private Action _dispose;
public AnonymousDisposable(Action dispose)
{
_dispose = dispose;
}
public void Dispose()
{
var dispose = Interlocked.Exchange(ref _dispose, null);
if (dispose != null)
{
dispose();
}
}
}
}
You can see the AddRange method implementation here (for List):
http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
There is already an accepted answer in this thread, but for all who are looking for a good implementation of ObservableRangeCollection supporting AddRange and ReplaceRange with single CollectionChanged notification I would really recommend this piece of code written by James Montemagno.
I have implemented an ObservableCollection to support bulk addition like below and bind to a WPF UI -
public void AddRange(IEnumerable<T> list)
{
lock (_lock)
{
if (list == null)
{
throw new ArgumentNullException("list");
}
_suspendCollectionChangeNotification = true;
var newItems = new List<T>();
foreach (T item in list)
{
if (!Contains(item))
{
Add(item);
newItems.Add(item);
}
}
_suspendCollectionChangeNotification = false;
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems);
OnCollectionChanged(arg); //NotifyCollectionChangedAction.Reset works!!!
}
}
}
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChanged(e);
}
internal void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (IsCollectionChangeSuspended)
{
return;
}
NotifyCollectionChangedEventHandler handler = CollectionChanged;
if (handler != null)
{
if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.DataBind,handler, this, e);
}
else
{
handler(this, e);
}
}
}
private bool IsCollectionChangeSuspended
{
get { return _suspendCollectionChangeNotification; }
}
I get this error - {"Collection was modified; enumeration operation may not execute."}
But, if I change the NotifyCollectionChangedAction.Add to NotifyCollectionChangedAction.Reset and do not pass any changed list, then it binds correctly to the UI. But, I want to use NotifyCollectionChangedAction.Add such that I can observe on the changes.
Can anyone please correct me ?
If you use the internal IList<T> to add the items it wont notify all the events like using Add method,
The Add method works like this:
CheckReentrancy
InsertItem
OnPropertyChanged("Count");
OnPropertyChanged("Item[]");
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
So if you skip the Add method and add the items directly to the underlying collection you may get this to wok.
Example: (untested)
public void AddRange(IEnumerable<T> rangeItems)
{
foreach (var item in rangeItems)
{
Items.Add(item);
}
base.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
base.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, rangeItems);
OnCollectionChanged(arg);
}
I've seen lots of talk about this question but maybe I'm just too much of a newbie to get it. If I have an observable collection that is a collection of "PersonNames" as in the msdn example (http: //msdn.microsoft.com/en-us/library/ms748365.aspx), I get updates to my View if a PersonName is added or removed, etc. I want to get an update to my View when I change a property in the PersonName as well. Like if I change the first name. I can implement OnPropertyChanged for each property and have this class derive from INotifyPropertyChanged and that seems to get called as expected.
My question is, how does the View get the updated data from the ObservableCollection as the property changed does not cause any event for the ObservableCollection?
This is probably something really simple but why I can't seem to find an example surprises me. Can anyone shed any light on this for me or have any pointers to examples I would greatly appreciate it. We have this scenario in multiple places in our current WPF app and are struggling with figuring it out.
"Generally, the code responsible for displaying the data adds a PropertyChanged event handler to each object currently displayed onscreen."
Could someone please give me an example of what this means? My View binds to my ViewModel which has a ObservableCollection. This collection is made up of a RowViewModel which has properties that support the PropertiesChanged event. But I can't figure out how to make the collection update itself so my view will be updated.
Here is how you would attach/detach to each item's PropertyChanged event.
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();
}
We wrote this in the WPF-chat:
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);
}
}
Weak events
Keeps track of the same item being added multiple times to the collection
It ~bubbles~ up the property changed events of the children.
The static class is just for convenience.
Use it like this:
var listener = OcPropertyChangedListener.Create(yourCollection);
listener.PropertyChanged += (sender, args) => { //do you stuff}
Bill,
I'm sure that you have found a workaround or solution to your issue by now, but I posted this for anyone with this common issue. You can substitute this class for ObservableCollections that are collections of objects that implement INotifyPropertyChanged. It is kind of draconian, because it says that the list needs to Reset rather than find the one property/item that has changed, but for small lists the performance hit should be unoticable.
Marc
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);
}
}
}
As you found out, there is no collection-level event that indicates that a property of an item in the collection has changed. Generally, the code responsible for displaying the data adds a PropertyChanged event handler to each object currently displayed onscreen.
Instead of ObservableCollection simply use the BindingList<T>.
The following code shows a DataGrid binding to a List and to item's properties.
<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; }
}
}
Following is the code giving a simple explanation of answer by #Stack and showing how BindingList is observing if it has a item changed and shows ObservableCollection will not observe the change inside an item.
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));
}
}
}
}