Get Deleted Item in ItemChanging event of BindingL

2020-02-26 04:22发布

问题:

I am using Binding List in my application along with ItemChanged event.

Is there any way I could know the previous values of properties in ItemChanged event. Currently I am adding a separate property named 'OldValue' to achieve this.

Is there any way to know about the deleted item in item changed event. I am not able to find any way to know which item has been deleted from the list.

回答1:

If I understand correctly, you want to get info about item which was deleted from 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 overriden, so BEFORE removing item from 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 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;
    }
}
}

ASNWER 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 old value of item you can override 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 object at 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, 4-th 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());
    }
}
}


回答2:

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


回答3:

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; } }
}


回答4:

Actually, deletion happens before the event fires. So, you cannot get to the item being removed. You definitely need some additional logic for that You can, however, inherit from BindingList, and override RemoveItem:

    public class RemoveAndBind<T> : BindingList<T>
    {
         protected override void RemoveItem(int index)
         {
            if (FireBeforeRemove != null)
             FireBeforeRemove(this,new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
            base.RemoveItem(index);
         }

        public event EventHandler<ListChangedEventArgs> FireBeforeRemove;
    }

Replicate the BindingList constructors. Don't make it cancellable to avoid misconceptions. You may also find some help here: http://connect.microsoft.com/VisualStudio/feedback/details/148506/listchangedtype-itemdeleted-is-useless-because-listchangedeventargs-newindex-is-already-gone

Hope this helps.



回答5:

In the specific case you're using this BindingList with a DataGridView, you can use the UserDeletingRow event from the datagrid, where:

private void myGrid_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
{
    ItemType DeletedItem = (ItemType)e.Row.DataBoundItem;

    //if you want to cancel deletion
    e.Cancel = true;
}