Получить удаленный элемент в событии ItemChanging BindingList

Я использую Binding List в своем приложении вместе с событием ItemChanged.

Могу ли я каким-либо образом узнать предыдущие значения свойств в событии ItemChanged. В настоящее время я добавляю отдельное свойство с именем OldValue для достижения этой цели.

Есть ли способ узнать об удаленном элементе в событии изменения элемента. Я не могу найти способ узнать, какой элемент был удален из списка.


person MegaMind    schedule 28.04.2014    source источник
comment
Где вы добавили свойство OldValue? Вы реализовали свой собственный производный класс, наследующий BindingList?   -  person Groo    schedule 03.05.2014
comment
@Groo Нет, у меня нет старого значения свойства. Я ожидаю получить старое значение каким-то образом.   -  person MegaMind    schedule 04.05.2014


Ответы (5)


Если я правильно понимаю, вы хотите получить информацию об элементе, который был удален из списка привязки.

Я думаю, что самым простым способом сделать это будет создание собственного списка привязок, который является производным от списка привязок.

Внутри у вас будет переопределенный метод 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());
}

beforeвызвано событие изменено

Обновленный код (требуется 4 кнопки, 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());
        }
    }
}
person a''    schedule 03.05.2014
comment
Спасибо за ваш ответ, он работает. Другая часть вопроса => Есть ли способ узнать старое значение элемента, который был изменен в списке? В ListChangedEvent ничего не разделяет. - person MegaMind; 04.05.2014
comment
Что, если я хочу вызвать remove вместо RemoveAt? Я имею в виду удаление по элементу, а не по индексу? (Я не делаю этого в сетке данных и т. д., как вопрос) - person John Demetriou; 11.09.2018

Альтернативный подход к этой проблеме — обернуть ObservableCollection в BindingList. Этот код работает для меня -

    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
            }
        }
    }
person Glen    schedule 31.07.2014
comment
Хороший. Я инкапсулировал это как 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; } - person Ruben Bartelink; 08.06.2016

Это очень старая 8-летняя проблема, которую Microsoft не хочет исправлять (я думаю, из-за риска регрессии). Вот ссылка для подключения к нему: ListChangedType.ItemDeleted бесполезен, поскольку ListChangedEventArgs.NewIndex уже отсутствует

Предлагаются различные обходные пути. Последний от 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; } }
}
person Simon Mourier    schedule 04.05.2014

В конкретном случае, когда вы используете это BindingList с DataGridView, вы можете использовать событие UserDeletingRow из сетки данных, где:

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

    //if you want to cancel deletion
    e.Cancel = true;
}
person Daniel Möller    schedule 16.07.2018

На самом деле удаление происходит до того, как событие сработает. Таким образом, вы не можете добраться до удаляемого элемента. Вам определенно нужна дополнительная логика для этого. Однако вы можете наследоваться от 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

Надеюсь это поможет.

person Gaurav Deochakke    schedule 09.05.2014
comment
Очень хорошо. Использование синтаксиса 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<ListChangedEventArgs> Removing; } - person Ruben Bartelink; 08.06.2016