ObservableCollection<> CollectionChanged Not Firing - c#

I have a datagrid in a WPF app that is bound to an ObservableCollection like so
<DataGrid ItemsSource="{Binding Spring.SpringData, Mode=OneWay}" />
My data displays fine, and I can edit the data in my grid, but it does not fire the PublishSpringChange event when I manually edit the data in the grid. The underlying data changes, but the event does not fire, what am I missing?
With a model of Spring that has the following
public class Spring : INotifyPropertyChanged
{
private ObservableCollection<SpringData> _SpringData;
public ObservableCollection<SpringData> SpringData
{
get { return _SpringData; }
}
public Spring()
{
....
_SpringData = new ObservableCollection<SpringData>();
SpringData.CollectionChanged += PublishSpringChange;
...
}
private void PublishSpringChange(object sender, NotifyCollectionChangedEventArgs e)
{
// Code that does not run!
}
}
with a SpringData class of
public class SpringData: BindableBase
{
private double _force;
private double _displacement;
public SpringData(double displacement, double force)
{
Displacement = displacement;
Force = force;
}
public double Displacement
{
get { return _displacement; }
set { SetProperty(ref _displacement, value); }
}
public double Force
{
get { return _force; }
set { SetProperty(ref _force, value); }
}
}

INotifyCollectionChanged only fires when you actually modify the collection. This is when you Add, Remove, Move, Replace or Reset items in the collection. It will not fire when one of the properties in a SpringData object is changed.
In order to listen to changes for a SpringData object, assuming it implements INotifyPropertyChanged, you will need to hook up listeners to the PropertyChanged event of each of the items.

Its quite useful to have a single handler for all properties changing sometimes. Here's how you can do it.
Handle CollectionChanged as you are above:
_SpringData = new ObservableCollection<SpringData>();
SpringData.CollectionChanged += PublishSpringChange;
Now for all added and removed objects to the collection add a handler to PropertyChanged:
private void PublishSpringChange(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (INotifyPropertyChanged added in e.NewItems)
{
added.PropertyChanged += SpringDataOnPropertyChanged;
}
foreach (INotifyPropertyChanged removed in e.OldItems)
{
removed.PropertyChanged -= SpringDataOnPropertyChanged;
}
}
private SpringDataOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
//your code here
}

Related

CollectionChanged - Name of Collection

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?
};
}

Sorting observable collection in Radgridview

I am using telerik Radgridview in silverlight application. In that i have bind the itemsource with observable collection. Whenever the collection is changed, the radgridview.ItemSource is bound to observable collection. Everythings works fine but whenever item is added to collection, it is just append in the grid. I want to sort the collection after adding. Is there any simple way out for that?
Depends a on your implementation details, but I think you should be able to do it most cases with the OrderBy in LINQ and the CollectionChanged event. You just have to resubscribe to the event. Here's the code that worked for me in quick test:
public class BindingViewModel : ViewModelBase
{
public BindingViewModel()
{
Items = new ObservableCollection<int>();
Items.CollectionChanged += Items_CollectionChanged;
}
public void Add(int value)
{
Items.Add(value);
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Items = new ObservableCollection<int>(Items.OrderBy(x => x));
Items.CollectionChanged += Items_CollectionChanged;
}
private ObservableCollection<int> _items;
public ObservableCollection<int> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged("Items");
}
}
}

Binding a property that autoupdates

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.

ObservableCollection and Item PropertyChanged

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

WPF: Adding an element to a databound Collection (a Dependency Property)

I have this DependencyProperty which holds an entity with a property that is a collection (ShoutBox.Entities):
public static readonly DependencyProperty ShoutBoxProperty = DependencyProperty.Register("ShoutBox",typeof (ShoutBox),typeof (ShoutBoxViewerControl));
public ShoutBox ShoutBox
{
get { return (ShoutBox) GetValue(ShoutBoxProperty); }
set { SetValue(ShoutBoxProperty, value); }
}
It is being binded in xaml like such:
<ItemsControl ItemsSource="{Binding ShoutBox.Entries}">
.
.
</ItemsControl>
When I bind it the first time, it works as expected but there are times when I need to add items to the collection (with a method that is in the same control), like such:
public void AddNewEntry(ShoutBoxEntry newEntry)
{
Dispatcher.Invoke(new Action(() =>{
ShoutBox.Entries.Add(newEntry); //Adding directly the the Dependency property
}));
}
The problem is that when I add a new element with the above method, the item isn't being displayed in the ItemsControl.
My question is, why isn't the new element that I am adding isn't being displayed in the ItemsControl ?
[Edit]
Entries (ShoutBox.Entries) is of type List<ShoutBoxEntry>
What is the type of Entries? It either needs to be ObservableCollection or implement ICollectionChanged. Otherwise the binding doesn't know that a new item has been added.
Changing the type of Entries should indeed solve the problem...
If you want to avoid the explicit call to Dispatcher.Invoke, I wrote a collection that raises the CollectionChanged and PropertyChanged events on the thread that created the collection :
public class AsyncObservableCollection<T> : ObservableCollection<T>
{
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
public AsyncObservableCollection()
{
}
public AsyncObservableCollection(IEnumerable<T> list)
: base(list)
{
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
RaiseCollectionChanged(e);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post(RaiseCollectionChanged, e);
}
}
private void RaiseCollectionChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the PropertyChanged event on the current thread
RaisePropertyChanged(e);
}
else
{
// Post the PropertyChanged event on the creator thread
_synchronizationContext.Post(RaisePropertyChanged, e);
}
}
private void RaisePropertyChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnPropertyChanged((PropertyChangedEventArgs)param);
}
}
More details can be found here :
http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/

Categories

Resources