Implementing Collectionchanged

Implementing CollectionChanged

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.

c# ObservableCollection: How to implement CollectionChanged event

e is of Type NotifyCollectionChangedEventArgs It contains the NewItems, OldItems and the Action.

c.CollectionChanged += (sender, e) => { 
Console.WriteLine($"{e.Action}");
// check e.Action and draw your stuff
};

https://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedeventargs(v=vs.110).aspx

Sketch for the solution mentioned in the comments

class MyObject : INotifyPropertyChanged
{
private string _a;
public event PropertyChangedEventHandler PropertyChanged;

public string A
{
get { return _a; }
set
{
_a = value;
OnPropertyChanged();
}
}

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

class Pagey : ContentPage
{
private ObservableCollection<MyObject> c = new ObservableCollection<MyObject>();

// somwhere in your code
c.CollectionChanged += OnCollectionChanged;

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.NewItems != null)
{
foreach (var item in args.NewItems.Cast<MyObject>())
{
item.PropertyChanged += OnChanged;
}
}

if(args.OldItems != null)
{
foreach (var item in args.OldItems.Cast<MyObject>())
{
item.PropertyChanged -= OnChanged;
}
}

Redraw();
}

private void OnChanged(object sender, PropertyChangedEventArgs e)
{
Redraw();
}

private void Redraw()
{

}
}

Implementing (not handling) CollectionChanged event

As per INotifyCollectionChanged, it requires a public event member called CollectionChanged to be in the implementing class. I.e:

public class MyCollection : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
// ...
}

EDIT: Overriding a non virtual property using the new keyword:

public class MyListView : ListView
{

public new MyCollection Columns { get; set; }
//...

}

However you have to be careful. If a reference type refers to ListView.Columns it will be a different collection than MyCollection.Columns and cause unexpected behaviour. When using new to override a base property you should set/get the base property, this preserves the integrity of the collections, I.e:

public class MyListView : ListView
{
public new MyCollection Columns
{
get
{
return base.Columns as MyCollection;
}
set
{
base.Columns = value;
}
}
}

CollectionChanged - Name of Collection

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:


  1. 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
}

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

How does WPF handle CollectionChanged events for custom collections?

The answer is WPF will listening to CollectionChanged and act accordingly even the collection didn't implement IList interface. WPF will map data collection to its view(s), and a view always create a IndexedEnumerable wrapper to ensure that it can access the collection through an index. So, if you bind a collection to a WPF implemented dependency property like ItemsControl.ItemsSource, you don't need to implement IList to let INotifyCollectionChanged work. But if you bind a collection to you own property, instead of using the source directly, you should create a view by using CollectionViewSource.

ObservableCollection and CollectionChanged Event

The problem is that you're assigning your private member to a new instance of an ObservableCollection that you're getting back from your method. Therefore, what's happening is, you're hooking up to the event of one collection, but then blowing away that instance and replacing it with a new instance you never hooked up an event handler to. Here's what you can do. Create a class that inherits from ObservableCollection and adds an addrange method:

public class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool supressEvents = false;

public void AddRange(IEnumerable<T> items)
{
supressEvents = true;
foreach (var item in items)
{
base.Add(item);
}
this.supressEvents = false;
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items.ToList()));

}

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!this.surpressEvents)
{
base.OnCollectionChanged(e);
}
}
}

Then, you can change your class to this:

private RangeObservableCollection<InventoryBTO> _inventoryRecords;
public RangeObservableCollection<InventoryBTO> InventoryRecords
{
get { return _inventoryRecords; }
set { _inventoryRecords = value; }
}

private InventoryBTO _selectedRecord;
public InventoryBTO SelectedRecord
{
get { return _selectedRecord; }
set
{
if (_selectedRecord != value)
{
_selectedRecord = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedRecord"));
}
}
}

public InventoryViewModel()
{
if (_inventoryRecords == null)
{
InventoryRecords = new ObservableCollection<InventoryBTO>();
this.InventoryRecords.CollectionChanged += new NotifyCollectionChangedEventHandler(InventoryRecords_CollectionChanged);
}

this.InventoryRecords.AddRange(InventoryListBTO.GetAllInventoryRecords());
}

void InventoryRecords_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//e.NewItems will be an IList of all the items that were added in the AddRange method...
}

C# - Check if ObservableCollection CollectionChanged Event has finished executing

I haven't used an ObservableCollection yet so there's the possibility I'm a touch off-base, but hey.

Likely, your weakness is that you're viewing this backwards. CollectionChanged is what should be fired to let your external object accessing the collection know that the collection is updated, not to be used to update the actual collection (via dumping it out to a file or rebuilding it from an XML file).

To wit:

This external class should have a setup something along the lines of this:

    public class WeaponsEditor
{
private WeaponsDatabase DB;
public WeaponsEditor()
{
DB = new WeaponsDatabase();
DB.CollectionChanged += CollectionChanged;
}

private object CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// respond to the updated database
}
}

Basically, the CollectionChanged event is for other objects to respond to a changed collection, not for your class to make its updates. If the Weapons Editor object is manipulating the collection directly, that's your problem; you should have it call a custom method in WeaponsDatabase, which will forward the request to the collection and then call the Serialize/Deserialize methods immediately after.



Related Topics



Leave a reply



Submit