Reputation: 3105
I have added CollectionChanged eventhandler(onCollectionChanged)
to one of the ObservableCollection
property.
I have found out that onCollectionChanged
method gets invoked only in case of add items or remove items to the collection, but not in the case of collection item gets edited.
I would like to know how to send the list/collection of newly added, removed and edited items in a single collection.
Thanks.
Upvotes: 33
Views: 108484
Reputation: 2729
You have to add a PropertyChanged
listener to each item (which must implement INotifyPropertyChanged
) to get notification about editing objects in a observable list.
public ObservableCollection<Item> Names { get; set; }
public List<Item> ModifiedItems { get; set; }
public ViewModel()
{
this.ModifiedItems = new List<Item>();
this.Names = new ObservableCollection<Item>();
this.Names.CollectionChanged += this.OnCollectionChanged;
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
newItem.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (e.OldItems != null)
{
foreach(Item oldItem in e.OldItems)
{
ModifiedItems.Add(oldItem);
oldItem.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Item item = sender as Item;
if(item != null)
ModifiedItems.Add(item);
}
Maybe you have to check if some item is already in the ModifedItems-List (with List's method Contains(object obj)) and only add a new item if the result of that method is false.
The class Item
must implement INotifyPropertyChanged
. See this example to know how. As Robert Rossney said you can also make that with IEditableObject
- if you have that requirement.
Upvotes: 57
Reputation: 1026
My edit to 'this answer' is rejected! So I put my edit here:
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Item newItem in e.NewItems)
{
ModifiedItems.Add(newItem);
//Add listener for each item on PropertyChanged event
if (e.Action == NotifyCollectionChangedAction.Add)
newItem.PropertyChanged += this.ListTagInfo_PropertyChanged;
else if (e.Action == NotifyCollectionChangedAction.Remove)
newItem.PropertyChanged -= this.ListTagInfo_PropertyChanged;
}
}
// MSDN: OldItems:Gets the list of items affected by a Replace, Remove, or Move action.
//if (e.OldItems != null) <--- removed
}
Upvotes: 7
Reputation: 287
The easiest solution I found to this limitation, is to remove the item using RemoveAt(index)
then add the modified item on the same index using InsertAt(index)
and thus the ObservableCollection will notify the View.
Upvotes: 0
Reputation: 1
Use the following code:
-my Model:
public class IceCream: INotifyPropertyChanged
{
private int liczba;
public int Liczba
{
get { return liczba; }
set { liczba = value;
Zmiana("Liczba");
}
}
public IceCream(){}
//in the same class implement the below-it will be responsible for track a changes
public event PropertyChangedEventHandler PropertyChanged;
private void Zmiana(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in my class PersonList implement method responsible for active increasing the value of one after button click in AppBarControl
async private void Add_Click(object sender, RoutedEventArgs e)
{
List<IceCream> items = new List<IceCream>();
foreach (IceCream item in IceCreamList.SelectedItems)
{
int i=Flavors.IndexOf(item);
Flavors[i].Liczba =item.Liczba+ 1;
//Flavors.Remove(item);
//item.Liczba += 1;
// items.Add(item);
// Flavors.Add(item);
}
MessageDialog d = new MessageDialog("Zwiększono liczbę o jeden");
d.Content = "Zwiększono liczbę o jeden";
await d.ShowAsync();
IceCreamList.SelectedIndex = -1;
}
}
I hope that it will be useful for someone to Note that:
private ObservableCollection<IceCream> Flavors;
-
Upvotes: 0
Reputation: 29073
in winforms, BindingList
is standard practice. in WPF & Silverlight, you are usually stuck working with ObservableCollection
and need to listen for PropertyChanged
on each item
Upvotes: 2
Reputation: 24713
INotifyCollectionChanged
is not one in the same with INotiftyPropertyChanged
. Changing properties of underlying objects does not in any way suggest the collection has changed.
One way to achieve this behavior is to create a custom collection which will interrogate the object once added and register for the INotifyPropertyChanged.PropertyChanged
event; it would then need to de-register appropriately when an item is removed.
One caveat with this approach is when your objects are nested N levels deep. To solve this you will need to essentially interrogate each property using reflection to determine if it is perhaps yet another collection implementing INotifyCollectionChanged
or other container which will need to be traversed.
Here is a rudimentary un-tested example...
public class ObservableCollectionExt<T> : ObservableCollection<T>
{
public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;
protected override void SetItem(int index, T item)
{
base.SetItem(index, item);
if(item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
protected override void ClearItems()
{
for (int i = 0; i < this.Items.Count; i++)
DeRegisterINotifyPropertyChanged(this.IndexOf(this.Items[i]));
base.ClearItems();
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
RegisterINotifyPropertyChanged(item);
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
DeRegisterINotifyPropertyChanged(index);
}
private void RegisterINotifyPropertyChanged(T item)
{
if (item is INotifyPropertyChanged)
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
private void DeRegisterINotifyPropertyChanged(int index)
{
if (this.Items[index] is INotifyPropertyChanged)
(this.Items[index] as INotifyPropertyChanged).PropertyChanged -= OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
T item = (T)sender;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, item));
}
}
Upvotes: 2
Reputation: 96712
An ItemsControl
listens to CollectionChanged
to manage the display of the collection of items it's presenting on the screen. A ContentControl
listens to PropertyChanged
to manage the display of the specific item that it's presenting on the screen. It's pretty easy to keep the two concepts separate in your mind once you understand this.
Tracking whether or not an item is edited isn't something either of these interfaces does. Property changes aren't edits - that is, they don't necessarily represent some kind of user-initiated change to the state of the object. For instance, an object might have an ElapsedTime
property that's being continuously updated by a timer; the UI needs to be notified of these property-change events, but they certainly don't represent changes in the object's underlying data.
The standard way to track whether or not an object is edited is to first make that object implement IEditableObject
. You can then, internally to the object's class, decide what changes constitute an edit (i.e. require you to call BeginEdit
) and what changes don't. You can then implement a boolean IsDirty
property that gets set when BeginEdit
is called and cleared when EndEdit
or CancelEdit
is called. (I really don't understand why that property isn't part of IEditableObject
; I haven't yet implemented an editable object that didn't require it.)
Of course, there's no need to implement that second level of abstraction if you don't need it - you can certainly listen PropertyChanged
event and just assume that the object has been edited if it gets raised. It really depends on your requirements.
Upvotes: 11
Reputation: 57919
I think that populating the ObservableCollection
with items that implement INotifyPropertyChanged
will cause the CollectionChanged
event to fire when an item raises its PropertyChanged
notification.
On second thought, I think you need to use BindingList<T>
to get individual item changes to propagate in this way out-of-the-box.
Otherwise, you'll need to manually subscribe to each item's change notifications and raise the CollectionChanged
event. Note that if you're creating your own, derived ObservableCollection<T>
, you'll have to subscribe at instantiation and on Add()
and Insert()
, and unsubscribe on Remove()
, RemoveAt()
and Clear()
. Otherwise, you can subscribe to the CollectionChanged
event and use the added and removed items from the event args to subscribe/unsubscribe.
Upvotes: 2