web-dev-qa-db-ja.com

BindingListのItemChangingイベントで削除されたアイテムを取得します

ItemChangedイベントとともにアプリケーションでバインディングリストを使用しています。

ItemChangedイベントのプロパティの以前の値を知る方法はありますか?現在、これを実現するために「OldValue」という名前の別のプロパティを追加しています。

アイテム変更イベントで削除されたアイテムを知る方法はありますか?リストから削除されたアイテムを知る方法が見つかりません。

23
MegaMind

私が正しく理解していれば、バインディングリストから削除されたアイテムに関する情報を取得したいと思います。

これを行う最も簡単な方法は、バインディングリストから派生した独自のバインディングリストを作成することだと思います。

内部では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;
    }

例として、INotifyPropertyChangedを実装するタイプmyIntを作成しました。インターフェイスは、バインディングリストから要素を追加/削除した後に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;
    }

バインディングリストをint(正確には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は入札リストからアイテムを削除します

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

コメントへの回答

"質問の他の部分は=>リストで変更されたアイテムの古い値を知る方法はありますか?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());
}

beforeevent raisedchanged

更新されたコード(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());
    }
}
}

この問題に対する別のアプローチは、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
            }
        }
    }
8
Glen

これは、Microsoftが修正したくない非常に古い8年の問題です(回帰リスクの理由から推測します)。これへの接続リンクは次のとおりです。 ListChangedEventArgs.NewIndexが既になくなっているため、ListChangedType.ItemDeletedは役に立ちません

さまざまな回避策が提案されています。 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; } }
}
3
Simon Mourier

実際、削除はイベントが発生する前に行われます。そのため、削除されているアイテムにアクセスすることはできません。そのためには、間違いなく追加のロジックが必要です。ただし、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

このBindingListDataGridViewとともに使用している特定のケースでは、データグリッドからUserDeletingRowイベントを使用できます。

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

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