Get Deleted Item in ItemChanging event of BindingList
An alternative approach to this problem is to wrap an ObservableCollection with a BindingList. This code works for me -
public void X()
{
ObservableCollection<object> oc = new ObservableCollection<object>();
BindingList<object> bl = new BindingList<object>(oc);
oc.CollectionChanged += oc_CollectionChanged;
bl.Add(new object());
bl.RemoveAt(0);
}
void oc_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object o in e.OldItems)
{
//o was deleted
}
}
}
This is a very old 8 years issue that Microsoft doesn't want to fix (I guess for regression risk reason). Here is the connect link to it:ListChangedType.ItemDeleted is useless because ListChangedEventArgs.NewIndex is already gone
There are various workaround proposed. The last one by If-Zen (2013/12/28) seems pretty decent, I'll quote it here with a slightly modified version:
public class MyBindingList<T> : BindingList<T>
{
public MyBindingList()
{
}
public MyBindingList(IList<T> list)
: base(list)
{
}
// TODO: add other constructors
protected override void RemoveItem(int index)
{
// NOTE: we could check if index is valid here before sending the event, this is arguable...
OnListChanged(new ListChangedEventArgsWithRemovedItem<T>(this[index], index));
// remove item without any duplicate event
bool b = RaiseListChangedEvents;
RaiseListChangedEvents = false;
try
{
base.RemoveItem(index);
}
finally
{
RaiseListChangedEvents = b;
}
}
}
public class ListChangedEventArgsWithRemovedItem : ListChangedEventArgs
{
public ListChangedEventArgsWithRemovedItem(object item, int index)
: base(ListChangedType.ItemDeleted, index, index)
{
if (item == null)
throw new ArgumentNullException("item");
Item = item;
}
public virtual object Item { get; protected set; }
}
public class ListChangedEventArgsWithRemovedItem<T> : ListChangedEventArgsWithRemovedItem
{
public ListChangedEventArgsWithRemovedItem(T item, int index)
: base(item, index)
{
}
public override object Item { get { return (T)base.Item; } protected set { base.Item = value; } }
}
If I understand correctly, you want to get info about the item which was deleted from the binding list.
I think the easiest way to do this will be creating your own binding list which derives from binding list.
Inside you'll have RemoveItem method overridden, so BEFORE removing an item from the binding list, you'll be able to fire event containing item which is going to be removed.
public class myBindingList<myInt> : BindingList<myInt>
{
protected override void RemoveItem(int itemIndex)
{
//itemIndex = index of item which is going to be removed
//get item from binding list at itemIndex position
myInt deletedItem = this.Items[itemIndex];
if (BeforeRemove != null)
{
//raise event containing item which is going to be removed
BeforeRemove(deletedItem);
}
//remove item from list
base.RemoveItem(itemIndex);
}
public delegate void myIntDelegate(myInt deletedItem);
public event myIntDelegate BeforeRemove;
}
For the sake of example, I created type myInt implementing INotifyPropertyChanged - interface is just to make dataGridView refresh after adding/deleting elements from a binding list.
public class myInt : INotifyPropertyChanged
{
public myInt(int myIntVal)
{
myIntProp = myIntVal;
}
private int iMyInt;
public int myIntProp {
get
{
return iMyInt;
}
set
{
iMyInt = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("myIntProp"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I'm initializing binding list with ints (myInts to be precise), then I'm binding list to dataGridView (for presentation purpose) and subscribing to my BeforeRemove event.
bindingList = new myBindingList<myInt>();
bindingList.Add(new myInt(8));
bindingList.Add(new myInt(9));
bindingList.Add(new myInt(11));
bindingList.Add(new myInt(12));
dataGridView1.DataSource = bindingList;
bindingList.BeforeRemove += bindingList_BeforeRemove;
If BeforeRemove event was raised I have item which was deleted
void bindingList_BeforeRemove(Form1.myInt deletedItem)
{
MessageBox.Show("You've just deleted item with value " + deletedItem.myIntProp.ToString());
}
Below is whole example code (drop 3 buttons and dataGridView on form) - button 1 initializes binding list, button 2 adds item to list, button 3 removes item from biding list
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace bindinglist
{
public partial class Form1 : Form
{
myBindingList<myInt> bindingList;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
bindingList = new myBindingList<myInt>();
bindingList.Add(new myInt(8));
bindingList.Add(new myInt(9));
bindingList.Add(new myInt(11));
bindingList.Add(new myInt(12));
dataGridView1.DataSource = bindingList;
bindingList.BeforeRemove += bindingList_BeforeRemove;
}
void bindingList_BeforeRemove(Form1.myInt deletedItem)
{
MessageBox.Show("You've just deleted item with value " + deletedItem.myIntProp.ToString());
}
private void button2_Click(object sender, EventArgs e)
{
bindingList.Add(new myInt(13));
}
private void button3_Click(object sender, EventArgs e)
{
bindingList.RemoveAt(dataGridView1.SelectedRows[0].Index);
}
public class myInt : INotifyPropertyChanged
{
public myInt(int myIntVal)
{
myIntProp = myIntVal;
}
private int iMyInt;
public int myIntProp {
get
{
return iMyInt;
}
set
{
iMyInt = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("myIntProp"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class myBindingList<myInt> : BindingList<myInt>
{
protected override void RemoveItem(int itemIndex)
{
myInt deletedItem = this.Items[itemIndex];
if (BeforeRemove != null)
{
BeforeRemove(deletedItem);
}
base.RemoveItem(itemIndex);
}
public delegate void myIntDelegate(myInt deletedItem);
public event myIntDelegate BeforeRemove;
}
}
}
ANSWER TO COMMENT
"The other part of the question is => Is there any way to know the old value of the item which is changed in the list? In ListChangedEvent does not share anything"
To see the old value of the item you can override the SetItem method
protected override void SetItem(int index, myInt item)
{
//here we still have old value at index
myInt oldMyInt = this.Items[index];
//new value
myInt newMyInt = item;
if (myIntOldNew != null)
{
//raise event
myIntOldNew(oldMyInt, newMyInt);
}
//update item at index position
base.SetItem(index, item);
}
It fires when an object at the specified index is changed, like this
bindingList[dataGridView1.SelectedRows[0].Index] = new myInt(new Random().Next());
The tricky part is, if you try to modify item's property directly
bindingList[dataGridView1.SelectedRows[0].Index].myIntProp = new Random().Next();
SetItem won't fire, it has to be a whole object replaced.
So we will need another delegate & event to handle this
public delegate void myIntDelegateChanged(myInt oldItem, myInt newItem);
public event myIntDelegateChanged myIntOldNew;
Then we can subscribe to this
bindingList.myIntOldNew += bindingList_myIntOldNew;
and handle it
void bindingList_myIntOldNew(Form1.myInt oldItem, Form1.myInt newItem)
{
MessageBox.Show("You've just CHANGED item with value " + oldItem.myIntProp.ToString() + " to " + newItem.myIntProp.ToString());
}
Updated code (4 buttons required, 4th modifies selected item)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace bindinglist
{
public partial class Form1 : Form
{
myBindingList<myInt> bindingList;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
bindingList = new myBindingList<myInt>();
bindingList.Add(new myInt(8));
bindingList.Add(new myInt(9));
bindingList.Add(new myInt(11));
bindingList.Add(new myInt(12));
dataGridView1.DataSource = bindingList;
bindingList.BeforeRemove += bindingList_BeforeRemove;
bindingList.myIntOldNew += bindingList_myIntOldNew;
}
void bindingList_myIntOldNew(Form1.myInt oldItem, Form1.myInt newItem)
{
MessageBox.Show("You've just CHANGED item with value " + oldItem.myIntProp.ToString() + " to " + newItem.myIntProp.ToString());
}
void bindingList_BeforeRemove(Form1.myInt deletedItem)
{
MessageBox.Show("You've just deleted item with value " + deletedItem.myIntProp.ToString());
}
private void button2_Click(object sender, EventArgs e)
{
bindingList.Add(new myInt(13));
}
private void button3_Click(object sender, EventArgs e)
{
bindingList.RemoveAt(dataGridView1.SelectedRows[0].Index);
}
public class myInt : INotifyPropertyChanged
{
public myInt(int myIntVal)
{
myIntProp = myIntVal;
}
private int iMyInt;
public int myIntProp {
get
{
return iMyInt;
}
set
{
iMyInt = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("myIntProp"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class myBindingList<myInt> : BindingList<myInt>
{
protected override void SetItem(int index, myInt item)
{
myInt oldMyInt = this.Items[index];
myInt newMyInt = item;
if (myIntOldNew != null)
{
myIntOldNew(oldMyInt, newMyInt);
}
base.SetItem(index, item);
}
protected override void RemoveItem(int itemIndex)
{
myInt deletedItem = this.Items[itemIndex];
if (BeforeRemove != null)
{
BeforeRemove(deletedItem);
}
base.RemoveItem(itemIndex);
}
public delegate void myIntDelegateChanged(myInt oldItem, myInt newItem);
public event myIntDelegateChanged myIntOldNew;
public delegate void myIntDelegate(myInt deletedItem);
public event myIntDelegate BeforeRemove;
}
private void button4_Click(object sender, EventArgs e)
{
bindingList[dataGridView1.SelectedRows[0].Index] = new myInt(new Random().Next());
}
}
}