在 BindingList 的 ItemChanging 事件中获取已删除的项

28

我在我的应用程序中使用了Binding List和ItemChanged事件。

有没有办法在ItemChanged事件中知道属性的先前值。目前,我正在添加一个名为“OldValue”的单独属性来实现此操作。

是否有任何方法可以在项更改事件中知道已删除的项。我无法找到任何方法来知道从列表中删除了哪个项。


你在哪里添加了“OldValue”属性?你实现了继承“BindingList”的自己的派生类吗? - vgru
@Groo 不,我没有任何旧值属性。我希望能够以某种方式获取旧值。 - Manvinder
5个回答

48

如果我理解正确,您想获取有关从绑定列表中删除的项目的信息。

我认为最简单的方法是创建自己的绑定列表,它派生自绑定列表。

在其中,您将覆盖RemoveItem方法,因此在从绑定列表中删除项目之前,您将能够触发包含即将被删除的项目的事件。

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

为了举例,我创建了一个名为myInt的类型,实现了INotifyPropertyChanged接口 - 接口只是为了在绑定列表中添加/删除元素后使dataGridView刷新。

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

我用整数(准确地说是myInts)初始化了绑定列表,然后将列表绑定到dataGridView(用于展示目的),并订阅了我的BeforeRemove事件。

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;

如果 BeforeRemove 事件被触发,那么我将拥有一个被删除的项目。

void bindingList_BeforeRemove(Form1.myInt deletedItem)
{
    MessageBox.Show("You've just deleted item with value " + deletedItem.myIntProp.ToString());
}
以下是整个示例代码(在表单上放置3个按钮和一个dataGridView)- 按钮1初始化绑定列表,按钮2向列表添加项目,按钮3从绑定列表中删除项目 删除前 删除后 已删除
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;
        }
    }
}

回复评论的答案

“问题的另一部分是=>有没有办法知道在列表中被更改的项目的旧值?ListChangedEvent不共享任何内容。”

要查看该项的旧值,您可以重写SetItem方法。

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

当指定索引位置的对象发生变化时,它会触发,像这样:

bindingList[dataGridView1.SelectedRows[0].Index] = new myInt(new Random().Next());

棘手的部分在于,如果您尝试直接修改项目属性

bindingList[dataGridView1.SelectedRows[0].Index].myIntProp = new Random().Next();

SetItem不会触发事件,必须替换整个对象。

因此,我们需要另一个委托和事件来处理这个问题。

public delegate void myIntDelegateChanged(myInt oldItem, myInt newItem);
public event myIntDelegateChanged myIntOldNew;

然后我们可以订阅这个。

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

之前 引发事件 已更改

更新后的代码(需要4个按钮,第四个修改所选项目)

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

谢谢你的回答,它有效了。问题的另一部分是=> 有没有办法知道在列表中更改的项目的旧值?在ListChangedEvent中没有共享任何内容。 - Manvinder
如果我想调用remove而不是RemoveAt呢?我的意思是按项目而不是按索引删除?(我的操作不是在datagrid等上) - John Demetriou

12

这个问题的另一种解决方法是用 BindingList 包装一个 ObservableCollection。以下代码对我有效 -

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

1
不错。我将其封装为`public class BindingListWithCollectionChanged<T> : BindingList<T> { public BindingListWithCollectionChanged(IList<T> list) : this(new ObservableCollection<T>(list)) { } BindingListWithCollectionChanged(ObservableCollection oc) : base(oc) { oc.CollectionChanged += (s, e) => CollectionChanged?.Invoke(s, e); } public event EventHandler CollectionChanged; }`。 - Ruben Bartelink

3
这是一个非常古老的8年问题,微软不想解决它(我猜是因为回归风险的原因)。这是连接到它的链接: ListChangedType.ItemDeleted is useless because ListChangedEventArgs.NewIndex is already gone 有各种各样的解决方法。最后一个由 If-Zen 提出的建议(2013/12/28)似乎相当不错,这里引用一下稍微修改过的版本:
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; } }
}

2
在您使用BindingListDataGridView的特定情况下,您可以使用datagrid的UserDeletingRow事件,其中:

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

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

2

实际上,删除操作发生在事件触发之前。因此,您无法获取要删除的项。 您肯定需要一些额外的逻辑来处理这个问题。 但是,您可以继承自BindingList,并重写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;
    }

复制BindingList的构造函数。为了避免误解,不要使其可取消。您可以在这里找到一些帮助: http://connect.microsoft.com/VisualStudio/feedback/details/148506/listchangedtype-itemdeleted-is-useless-because-listchangedeventargs-newindex-is-already-gone 希望这能帮助到您。

1
非常好。使用C# 6语法和与“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 Removing; }` - Ruben Bartelink

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接