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:
- 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?
};
}
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
What Is the Easiest Way to Subtract Time in C#
File Getting Copied to Syswow64 Instead of System32
What's the Best Way to Pass Event to Viewmodel
Parsing a JSON File with .Net Core 3.0/System.Text.JSON
Retry a Task Multiple Times Based on User Input in Case of an Exception in Task
Jquery Ajax Call to Httpget Webmethod (C#) Not Working
Why Enumerable.Cast Raises an Invalidcastexception
Graphics.Drawstring VS Textrenderer.Drawtextwhich Can Deliver Better Quality
Getting Individual Windows Application Current Volume Output Level as Visualized in Audio Mixer
Distinct in Linq Based on Only One Field of the Table
Initialization Order of Static Fields in Static Class
String Interning in .Net Framework - What Are the Benefits and When to Use Interning
Make ASP.NET Wcf Convert Dictionary to JSON, Omitting "Key" & "Value" Tags