Get Deleted Item in ItemChanging event of BindingList

前端 未结 5 1126
生来不讨喜
生来不讨喜 2020-12-29 22:23

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. Curren

相关标签:
5条回答
  • 2020-12-29 22:42

    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.

    0 讨论(0)
  • 2020-12-29 22:44

    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;
    }
    
    0 讨论(0)
  • 2020-12-29 22:51

    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
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-29 23:02

    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());
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-29 23:04

    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; } }
    }
    
    0 讨论(0)
提交回复
热议问题