Get Deleted Item in ItemChanging event of BindingList
Asked Answered
F

5

28

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.

Fivefold answered 28/4, 2014 at 10:54 Comment(2)
Where did you add the OldValue property? Did you implement your own derived class inheriting BindingList?Tractate
@Groo No, I don't have any old value property. I am expecting to get the old value somehow.Fivefold
S
48

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

before delete

after delete

deleted

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

before event raised changed

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());
        }
    }
}
Szabadka answered 3/5, 2014 at 20:45 Comment(2)
Thanks for your answer, its working. 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.Fivefold
What if I want to call remove instead of RemoveAt? I mean remove by item instead of by index? (I am not doing this in a datagrid etc like the question)Erena
D
12

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
            }
        }
    }
Dihydric answered 31/7, 2014 at 2:40 Comment(1)
Nice. I encapsulated it as public class BindingListWithCollectionChanged<T> : BindingList<T> { public BindingListWithCollectionChanged(IList<T> list) : this(new ObservableCollection<T>(list)) { } BindingListWithCollectionChanged(ObservableCollection<T> oc) : base(oc) { oc.CollectionChanged += (s, e) => CollectionChanged?.Invoke(s, e); } public event EventHandler<NotifyCollectionChangedEventArgs> CollectionChanged; }Lag
C
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; } }
}
Chimb answered 4/5, 2014 at 7:6 Comment(0)
C
2

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.

Clink answered 9/5, 2014 at 7:24 Comment(1)
Very nice. Using C# 6 syntax and naming consistent with AddingNew :- public class BindListWithRemoving<T> : BindingList<T> { public BindListWithRemoving(IList<T> list) : base(list) { } protected override void RemoveItem(int index) { Removing?.Invoke(this, new ListChangedEventArgs(ListChangedType.ItemDeleted, index)); base.RemoveItem(index); } public event EventHandler<ListChangedEventArgs> Removing; }Lag
H
2

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;
}
Histiocyte answered 16/7, 2018 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.