ObservableCollection Collection Changed event not firing - c#

I have an observable collection in my ViewModel, bound to a datagrid. I want to implement some logic for refreshing the data in other windows based on changes to the collection/ updates to the database (using LINQ to SQL).
Here is my view model code:
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
}
void oFTrnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (FilesTransmitted f in e.NewItems)
f.PropertyChanged += FilesTransmitted_PropertyChanged;
}
if (e.OldItems != null)
{
foreach (FilesTransmitted f in e.OldItems)
f.PropertyChanged -= FilesTransmitted_PropertyChanged;
}
}
void FilesTransmitted_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "DocumentNumber")
{
_filesTransmittedChange = true;
}
_refreshViews = true;
}
and the ObservableCollection constructor:
class ObservableFilesTransmitted : ViewableCollection<FilesTransmitted>
{
public ObservableFilesTransmitted(DocControlDC dataDc, int ID)
{
foreach (FilesTransmitted ftran in dataDc.FilesTransmitteds.Where(x=>x.JobID==ID).OrderByDescending(x => x.TransmittalName))
{
this.Add(ftran);
}
}
}
The debugger does not stop in the oFTrnCollectionChanged. I think because the call to create the observable collection happens before I add the CollectionChanged event. But obvously I can't switch those two lines. I've looked at various StackOverflow and CodeProject topics on this, and it seems like what I have should work. Do I need to add and remove a dummy item just to get the CollectionChanged hander called? What am I missing?
It seems like perhaps I should have a constructor (for the observable collection) that does not add any members, and a function that adds the members from the database. Then I can call new, add the collectionchanged handler, and then fill the collection. I am hoping to avoid that level of rewrite though, but perhaps it's the only reasonable way.

When I run in to this the easiest way to solve it is just subscribe manually at the start.
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
foreach(var item in _oFTrn)
{
item.PropertyChanged += FilesTransmitted_PropertyChanged;
}
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
}
However a even better solution is instead of using a class derived from ObserveableCollection<T> use a class derived from BindingList<T>. Any member raising their PropertyChanged event will cause the collection to raise ListChanged with the change type of ItemChanged
public FTViewModel(int JobID)
{
_windowCloseAction = new DelegateCommand(OnWindowClose);
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnListChanged;
}
void oFTrnListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType == ListChangedType.ItemChanged)
{
if (e.PropertyDescriptor.Name == "DocumentNumber")
{
_filesTransmittedChange = true;
}
}
_refreshViews = true;
}

I have simply changed the ObservableCollection constructor and added a populate function:
New view model code:
public FTViewModel(int JobID)
{
_oFTrn = new ObservableFilesTransmitted(_dataDc, JobID);
_oFTrn.CollectionChanged += oFTrnCollectionChanged;
_oFTrn.FillCollection();
}
new ObservableCollection class:
class ObservableFilesTransmitted : ViewableCollection<FilesTransmitted>
{
DocControlDC _dc = null;
int _jobID = 0;
public ObservableFilesTransmitted(DocControlDC dataDc, int ID)
{
_dc = dataDc;
_jobID = ID;
}
public void FillCollection()
{
foreach (FilesTransmitted ftran in _dc.FilesTransmitteds.Where(x=>x.JobID==_jobID).OrderByDescending(x => x.TransmittalName))
{
this.Add(ftran);
}
}
}
And it all works as expected. But it gets called for each item added. I may play with the idea that I simply loop through the collection and add the propertychanged handler for each item in the viewmodel constructor. Seems like less of a performance hit that way.

Related

Update a ViewModel when an item in an observable collection is updated

Source code is here:
https://github.com/djangojazz/BubbleUpExample
The problem is I am wanting an ObservableCollection of a ViewModel to invoke an update when I update a property of an item in that collection. I can update the data that it is bound to just fine, but the ViewModel that holds the collection is not updating nor is the UI seeing it.
public int Amount
{
get { return _amount; }
set
{
_amount = value;
if (FakeRepo.Instance != null)
{
//The repo updates just fine, I need to somehow bubble this up to the
//collection's source that an item changed on it and do the updates there.
FakeRepo.Instance.UpdateTotals();
OnPropertyChanged("Trans");
}
OnPropertyChanged(nameof(Amount));
}
}
I basically need the member to tell the collection where ever it is called: "Hey I updated you, take notice and tell the parent you are a part of. I am just ignorant of bubble up routines or call backs to achieve this and the limited threads I found were slightly different than what I am doing. I know it could possible be done in many ways but I am having no luck.
In essence I just want to see step three in the picture below without having to click on the column first.
Provided that your underlying items adhere to INotifyPropertyChanged, you can use an observable collection that will bubble up the property changed notification such as the following.
public class ItemObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
foreach (INotifyPropertyChanged item in args.NewItems)
item.PropertyChanged += item_PropertyChanged;
if (args.OldItems != null)
foreach (INotifyPropertyChanged item in args.OldItems)
item.PropertyChanged -= item_PropertyChanged;
}
private void OnItemPropertyChanged(T sender, PropertyChangedEventArgs args)
{
if (ItemPropertyChanged != null)
ItemPropertyChanged(this, new ItemPropertyChangedEventArgs<T>(sender, args.PropertyName));
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnItemPropertyChanged((T)sender, e);
}
}
You should do two things to get it to work:
first: you should refactor the RunningTotal property so it can raise the property changed event. Like so:
private int _runningTotal;
public int RunningTotal
{
get => _runningTotal;
set
{
if (value == _runningTotal)
return;
_runningTotal = value;
OnPropertyChanged(nameof(RunningTotal));
}
}
Second thing you should do is calling the UpdateTotals after you add a DummyTransaction to the Trans. An option could be to refactor the AddToTrans method in the FakeRepo
public void AddToTrans(int id, string desc, int amount)
{
Trans.Add(new DummyTransaction(id, desc, amount));
UpdateTotals();
}

Static ObservableCollection Event is not firing

I have the following static ObservableCollection that gets updated using linq. Why the event is not firing ?
public static class myViewModel
{
private static ObservableCollection<ObjA> CollectionA = new ObservableCollection<ObjA>();
private static ObservableCollection<ObjB> CollectionB = new ObservableCollection<ObjB>();
static myViewModel()
{
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, True));
}
private static void myHandler(object sender, NotifyCollectionChangedEventArgs e)
{
//To do
throw new NotImplementedException();
}
private static void updateCollection()
{
foreach (var x in CollectionA)
{
CollectionA.field=5;
}
}
}
Step one: Give CollectionA an event handler.
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
Step Two: Discard CollectionA and replace it with a different collection that has no handler.
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, true));
See what you did there?
CollectionA returns a reference to a collection object. You aren't adding items to that collection object. You are replacing that collection object with a different collection object.
Add the items to the existing collection instead:
CollectionA.CollectionChanged += new NotifyCollectionChangedEventHandler(myHandler);
foreach (var x in CollectionB.Select(abc=> new ObjA(abc, true)))
{
CollectionA.Add(x);
}
If you really want to replace the collection all at once, you need to add the handler to the new collection:
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new ObjA(abc, true));
CollectionA.CollectionChanged += myHandler;
If myHandler has the correct parameters and return type, you don't need new NotifyCollectionChangedEventHandler.
The usual way to handle this type of thing is to make CollectionA a property which adds the handler itself:
private static ObservableCollection<ObjA> _collectionA;
public static ObservableCollection<ObjA> CollectionA {
get { return _collectionA; }
set {
if (_collectionA != value)
{
// Remove handler from old collection, if any
if (_collectionA != null)
{
_collectionA.CollectionChanged -= myHandler;
}
_collectionA = value;
if (_collectionA != null)
{
_collectionA.CollectionChanged += myHandler;
// Whatever myHandler does on new items, you probably want to do
// that here for each item in the new collection.
}
}
}
}
static myViewModel()
{
// Now, whenever you replace CollectionA, the setter will add the changed
// handler automatically and you don't have to think about it.
CollectionA = new ObservableCollection(CollectionB.Select(abc=> new(abc, True));
}
Update
Now, what what are we doing with the items? Maybe we want to know when their properties change. ObservableCollection won't do that for us, but we can wire it up ourselves.
It's fun to think about ways to refactor this code in a more conveniently reusable way.
private static ObservableCollection<ObjA> _collectionA;
public static ObservableCollection<ObjA> CollectionA
{
get { return _collectionA; }
set
{
if (_collectionA != value)
{
// Remove handler from old collection, if any
if (_collectionA != null)
{
_collectionA.CollectionChanged -= myHandler;
}
// 1. Remove property changed handlers from old collection items (if old collection not null)
// 2. Add property changed to new collection items (if new collection not null)
AddAndRemovePropertyChangedHandlers(_collectionA, value, ObjA_PropertyChanged);
_collectionA = value;
if (_collectionA != null)
{
_collectionA.CollectionChanged += myHandler;
}
}
}
}
// NotifyCollectionChangedEventArgs gives us non-generic IList rather than IEnumerable
// but all we're doing is foreach, so make it as general as possible.
protected static void AddAndRemovePropertyChangedHandlers(
System.Collections.IEnumerable oldItems,
System.Collections.IEnumerable newItems,
PropertyChangedEventHandler handler)
{
if (oldItems != null)
{
// Some items may not implement INotifyPropertyChanged.
foreach (INotifyPropertyChanged oldItem in oldItems.Cast<Object>()
.Where(item => item is INotifyPropertyChanged))
{
oldItem.PropertyChanged -= handler;
}
}
if (newItems != null)
{
foreach (INotifyPropertyChanged newItem in newItems.Cast<Object>()
.Where(item => item is INotifyPropertyChanged))
{
newItem.PropertyChanged += handler;
}
}
}
private static void ObjA_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private static void myHandler(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// If e.Action is Reset, you don't get the items that were removed. Oh well.
AddAndRemovePropertyChangedHandlers(e.OldItems, e.NewItems, ObjA_PropertyChanged);
}

Creating Items DP for charting user control

I am busy creating a user control that has some basic charting/graph functions. In essence I want to have an "Items" dependency property to which the user of the control can bind. The control will then display all the items and updates made to the source.
What I have done so far was to create an "Items" DP in my user control, code behind.
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items",
typeof(ObservableCollection<Polyline>),
typeof(RPGraph),
new FrameworkPropertyMetadata(
new ObservableCollection<Polyline>(),
new PropertyChangedCallback(OnItemsChanged)));
public ObservableCollection<Polyline> Items
{
get { return (ObservableCollection<Polyline>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
My first stumbling block was that "OnItemsChanged" didn't get called when my collection changed. After a couple of hours I found a stackoverflow post explaining why (ObservableCollection dependency property does not update when item in collection is deleted). Following this advice solved one part of my problem. Now I could Add new items (Polylines) to the ObservableCollection list. But what if I added an extra point or modified a point in the Polyline. Armed with the knowledge of the previous problem I found the Points.Changed event. I then subscribed to it and placed the update code in there.
This finally works, but man there must be a better or more elegant way of achieving this (as stated at the top), which I think all boils down to not using ObservableCollection? Any advice?
Below is the working OnItemChanged method (excuse the draft code, I just wanted to get it working :-) :
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as RPGraph;
foreach (Polyline poly in thisControl.Items)
thisControl.manager.Items.Add(poly.Points.ToArray());
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= Items_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<Polyline>)e.NewValue;
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += (o, t) => {
ObservableCollection<Polyline> items = o as ObservableCollection<Polyline>;
thisControl.manager.Items.Add(items[t.NewStartingIndex].Points.ToArray());
foreach (Polyline poly in items)
{
poly.Points.Changed += (n, m) => {
for (int i = 0; i < thisControl.manager.Items.Count; i++)
thisControl.manager.Items[i] = thisControl.Items[i].Points.ToArray();
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
}
You are completely right, an ObservableCollection does not notify when any of its items changes its property value.
You could extend the functionality of ObservableCollection adding notifications for these cases.
It may look like this:
public sealed class ObservableNotifiableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event ItemPropertyChangedEventHandler ItemPropertyChanged;
public event EventHandler CollectionCleared;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
{
foreach (INotifyPropertyChanged item in args.NewItems)
{
item.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (args.OldItems != null)
{
foreach (INotifyPropertyChanged item in args.OldItems)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
protected override void ClearItems()
{
foreach (INotifyPropertyChanged item in this.Items)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
base.ClearItems();
this.OnCollectionCleared();
}
private void OnCollectionCleared()
{
EventHandler eventHandler = this.CollectionCleared;
if (eventHandler != null)
{
eventHandler(this, EventArgs.Empty);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs args)
{
ItemPropertyChangedEventHandler eventHandler = this.ItemPropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new ItemPropertyChangedEventArgs(sender, args.PropertyName));
}
}
}
Then you can subscribe to the ItemPropertyChanged event and do your stuff.

Get changed item from CollectionChanged event using TrulyObservableCollection

I'm using a TrulyObservableCollection as a datasource in a WPF DataGrid. My class implements the PropertyChange event properly (I get notification when a property changes). The CollectionChanged event gets triggered as well. However, my issue lies in the connection between the PropertyChanged event and CollectionChanged event. I can see in the PropertyChanged event which item is being changed (in this case the sender object), however I can't seem to find a way to see which one is changed from within the CollectionChanged event. The sender object is the whole collection. What's the best way to see which item has changed in the CollectionChanged event? The relevant code snippets are below. Thank you for your help, and let me know if there needs to be some clarification.
Code for setting up the collection:
private void populateBret()
{
bretList = new TrulyObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
//Do stuff here with the specific item that has changed
}
Class that is used in the collection:
public class bretItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _blID;
public string _blGroup;
[DataMember]
public int blID
{
get { return _blID; }
set
{
_blID = value;
OnPropertyChanged("blID");
}
}
[DataMember]
public string blGroup
{
get { return _blGroup; }
set
{
_blGroup = value;
OnPropertyChanged("blGroup");
}
}
protected void OnPropertyChanged (String name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
TrulyObservableCollection class
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
: base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
public TrulyObservableCollection(List<T> list)
: base(list)
{
foreach (var item in list)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
}
void TrulyObservableCollection_CollectionChanged(object sender, 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);
}
}
EDIT:
In the item_PropertyChanged event the NotifyCollectionChangedEventArgs are set with NotifyCollectionChangedAction.Reset. This causes the OldItems and NewItems to be null, therefore I can't get the changed item in that case. I can't use .Add as the Datagrid is updated with an additional item. I can't appear to get .Replace to work either to get the changed item.
How about this:
In your ViewModel that contains the ObservableCollection of bretItem, the ViewModel subscribes to the CollectionChanged event of the ObservableCollection.
This will prevent the need of a new class TrulyObservableCollection derived from ObservableCollection that is coupled to the items within its collection.
Within the handler in your ViewModel, you can add and remove the PropertyChanged event handler as you are now. Since it is now your ViewModel that is being informed of the changes to objects within the collection, you can take the appropriate action.
public class BretListViewModel
{
private void populateBret()
{
bretList = new ObservableCollection<BestServiceLibrary.bretItem>(BestClass.BestService.getBretList().ToList());
bretList.CollectionChanged += bretList_CollectionChanged;
}
void bretList_CollectionChanged(object sender, System.Collections.Specialized.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)
{
var bret = sender as bretItem;
//Update the database now!
//One note:
//The ObservableCollection raises its change event as each item changes.
//You should consider a method of batching the changes (probably using an ICommand)
}
}
A Thing of Note:
As an aside, it looks like you are breaking the MVVM pattern based upon this snippet:
dgBretList.ItemsSource = bretList;
dgBretList.Items.Refresh();
You probably should consider loading your ViewModel and binding your View to it instead of coding logic in the code-behind of your View.
It's not appropriate to use the collection changed event in this way because it's only meant to be fired when adding/removing items from the collection. Which is why you've hit a wall. You're also in danger of breaking the Liskov substitution principle with this approach.
It's probably better to implement the INotifyPropertyChanged interface on your collection class and fire that event when one of your items fires its property changed event.

EntityFramework EntityCollection Observing CollectionChanged

I'm using EntityFramework database first in an application. I would like somehow to be notified of changes to an EntityCollection in my ViewModel. It doesn't directly support INotifyCollectionChanged (why?) and I haven't been successful in finding another solution.
Here's my latest attempt, which doesn't work because the ListChanged event doesn't appear to get raised:
public class EntityCollectionObserver<T> : ObservableCollection<T>, INotifyCollectionChanged where T : class
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public EntityCollectionObserver(EntityCollection<T> entityCollection)
: base(entityCollection)
{
IBindingList l = ((IBindingList)((IListSource)entityCollection).GetList());
l.ListChanged += new ListChangedEventHandler(OnInnerListChanged);
}
private void OnInnerListChanged(object sender, ListChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
Does anyone have any ideas how I might observe changes to an EntityCollection?
Dan
have you tried handling
AssociationChanged Occurs when a change is made to a related end. (Inherited from RelatedEnd.)
It gives arguments showing whether an element was added or deleted and also exposes the element.
Whilst it worked in the simple use case noted by #Aron, I couldn't get it to work properly in my actual application.
As it turns out, and for reasons I'm not sure - the inner IBindingList of an EntityCollection somehow, somewhere, can get changed. The reason my observers weren't being called is because they were looking for changes on an old IBindingList that wasn't even being used by the EntityCollection any more.
Here is the hack that got it working for me:
public class EntityCollectionObserver<T> : ObservableCollection<T> where T : class
{
private static List<Tuple<IBindingList, EntityCollection<T>, EntityCollectionObserver<T>>> InnerLists
= new List<Tuple<IBindingList, EntityCollection<T>, EntityCollectionObserver<T>>>();
public EntityCollectionObserver(EntityCollection<T> entityCollection)
: base(entityCollection)
{
IBindingList l = ((IBindingList)((IListSource)entityCollection).GetList());
l.ListChanged += new ListChangedEventHandler(OnInnerListChanged);
foreach (var x in InnerLists.Where(x => x.Item2 == entityCollection && x.Item1 != l))
{
x.Item3.ObserveThisListAswell(x.Item1);
}
InnerLists.Add(new Tuple<IBindingList, EntityCollection<T>, EntityCollectionObserver<T>>(l, entityCollection, this));
}
private void ObserveThisListAswell(IBindingList l)
{
l.ListChanged += new ListChangedEventHandler(OnInnerListChanged);
}
private void OnInnerListChanged(object sender, ListChangedEventArgs e)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
How are you mapping the event? Pasting your code and mapping the event like follows works for me.
static void Main(string[] args)
{
EntityCollection<string> col = new EntityCollection<string>();
EntityCollectionObserver<string> colObserver = new EntityCollectionObserver<string>(col);
colObserver.CollectionChanged += colObserver_CollectionChanged;
col.Add("foo");
}
static void colObserver_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Entity Collection Changed");
}

Categories