web-dev-qa-db-ja.com

コレクション内の要素の変更も監視するObservableCollection

次の特徴を持つコレクション(BCLまたはその他)はありますか?

コレクションが変更された場合はイベントを送信し、コレクション内の要素のいずれかがPropertyChangedイベントを送信した場合はイベントを送信します。一種のObservableCollection<T> どこ T: INotifyPropertyChangedそして、コレクションは要素の変更も監視しています。

監視可能なコレクションを自分でラップし、コレクション内の要素が追加/削除されたときにイベントをサブスクライブ/サブスクライブ解除することはできますが、既存のコレクションがこれをすでに行っているかどうか疑問に思っていましたか?

32
soren.enemaerke

自分で簡単な実装を行いました:

public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        Unsubscribe(e.OldItems);
        Subscribe(e.NewItems);
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        foreach(T element in this)
            element.PropertyChanged -= ContainedElementChanged;

        base.ClearItems();
    }

    private void Subscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged += ContainedElementChanged;
        }
    }

    private void Unsubscribe(IList iList)
    {
        if (iList != null)
        {
            foreach (T element in iList)
                element.PropertyChanged -= ContainedElementChanged;
        }
    }

    private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        OnPropertyChanged(e);
    }
}

確かに、実際に変更されたプロパティが含まれている要素にあるときに、コレクションでPropertyChangedを起動することは、混乱を招き、誤解を招く可能性がありますが、それは私の特定の目的に適合します。 ContainerElementChanged内で代わりに発生する新しいイベントで拡張できます。

考え?

編集:BCL ObservableCollectionは明示的な実装を通じてのみINotifyPropertyChangedインターフェイスを公開するため、次のようにイベントにアタッチするにはキャストを提供する必要があることに注意してください。

ObservableCollectionEx<Element> collection = new ObservableCollectionEx<Element>();
((INotifyPropertyChanged)collection).PropertyChanged += (x,y) => ReactToChange();

EDIT2:ClearItemsの処理を追加しました。Joshに感謝します。

EDIT3:PropertyChangedの正しい購読解除を追加しました。Markに感謝します。

EDIT4:うわー、これは本当にあなたが行くように学ぶ:)。 KPは、含まれている要素が変更されたときに、要素ではなくコレクションを送信者としてイベントが発生したことを指摘しました。彼は、newでマークされたクラスでPropertyChangedイベントを宣言することを提案しました。これにはいくつかの問題があり、以下のサンプルで説明しようとします。

  // work on original instance
  ObservableCollection<TestObject> col = new ObservableCollectionEx<TestObject>();
  ((INotifyPropertyChanged)col).PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // no event raised
  test.Info = "NewValue"; //Info property changed raised

  // working on explicit instance
  ObservableCollectionEx<TestObject> col = new ObservableCollectionEx<TestObject>();
  col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName); };

  var test = new TestObject();
  col.Add(test); // Count and Item [] property changed raised
  test.Info = "NewValue"; //no event raised

サンプルから、イベントを「オーバーライド」すると、受信するイベントが決まるため、イベントをサブスクライブするときに使用する変数のタイプに細心の注意を払う必要があるという副作用があることがわかります。

43
soren.enemaerke

@ soren.enemaerke:あなたの回答投稿にこのコメントをしたと思いますが、できません(理由はわかりません。おそらく、担当者があまりいないためです)。とにかく、あなたが投稿したコードでは、新しいラムダインラインを作成し、そのイベントハンドラーを削除しようとしているため、購読解除が正しく機能するとは思わないことを述べておきたいと思いました。

Add/removeイベントハンドラーの行を次のように変更します。

element.PropertyChanged += ContainedElementChanged;

そして

element.PropertyChanged -= ContainedElementChanged;

次に、ContainedElementChangedメソッドのシグネチャを次のように変更します。

private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)

これにより、削除が追加と同じハンドラーに対するものであることが認識され、正しく削除されます。これが誰かに役立つことを願っています:)

7
Mark Whitfeld

フレームワークに組み込まれているものを使用したい場合は、 FreezableCollection を使用できます。次に、 変更されたイベント をリッスンする必要があります。

Freezableまたはそれに含まれるオブジェクトが変更されたときに発生します。

これが小さなサンプルです。 collection_Changedメソッドは2回呼び出されます。

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        FreezableCollection<SolidColorBrush> collection = new FreezableCollection<SolidColorBrush>();
        collection.Changed += collection_Changed;
        SolidColorBrush brush = new SolidColorBrush(Colors.Red);
        collection.Add(brush);
        brush.Color = Colors.Blue;
    }

    private void collection_Changed(object sender, EventArgs e)
    {
    }
}
3
Todd White

ReactiveUI'sReactiveCollectionを使用します:

reactiveCollection.Changed.Subscribe(_ => ...);
1
Lukas Cenovsky

Rxx 2.0には 演算子 これと一緒に 変換演算子 for ObservableCollection<T>はあなたの目標を達成するのを簡単にします。

ObservableCollection<MyClass> collection = ...;

var changes = collection.AsCollectionNotifications<MyClass>();
var itemChanges = changes.PropertyChanges();
var deepItemChanges = changes.PropertyChanges(
  item => item.ChildItems.AsCollectionNotifications<MyChildClass>());

MyClassおよびMyChildClassでは、次のプロパティ変更通知パターンがサポートされています。

  • INotifyPropertyChanged
  • [プロパティ]変更されたイベントパターン(レガシー、コンポーネントモデルで使用)
  • WPF依存関係のプロパティ
0
Dave Sexton

それを行う最も簡単な方法はただ行うことです

using System.ComponentModel;
public class Example
{
    BindingList<Foo> _collection;

    public Example()
    {
        _collection = new BindingList<Foo>();
        _collection.ListChanged += Collection_ListChanged;
    }

    void Collection_ListChanged(object sender, ListChangedEventArgs e)
    {
        MessageBox.Show(e.ListChangedType.ToString());
    }

}

BindingList .net sence2.0にあるクラス。コレクション内のアイテムがListChangedを起動するたびに、それはINotifyPropertyChangedイベントを起動します。

0

C5 Generic Collection Library をチェックしてください。そのすべてのコレクションには、アイテムが追加、削除、挿入、クリアされたとき、またはコレクションが変更されたときのコールバックを添付するために使用できるイベントが含まれています。

私はそのライブラリのいくつかの拡張機能に取り組んでいます ここ 近い将来、追加または変更をキャンセルできる「プレビュー」イベントが可能になるはずです。

0
Marcus Griep

@ soren.enemaerke回答のコメントセクションで判読できなくなるため、適切なコードを投稿するためにこれを返信しました。このソリューションで私が抱えていた唯一の問題は、PropertyChangedイベントをトリガーする特定の要素が失われ、PropertyChanged呼び出しで知る方法がないことです。

_col.PropertyChanged += (s, e) => { Trace.WriteLine("Changed " + e.PropertyName)
_

これを修正するために、新しいクラスPropertyChangedEventArgsExを作成し、クラス内のContainedElementChangedメソッドを変更しました。

新しいクラス

_public class PropertyChangedEventArgsEx : PropertyChangedEventArgs
{
    public object Sender { get; private set; }

    public PropertyChangedEventArgsEx(string propertyName, object sender) 
        : base(propertyName)
    {
        this.Sender = sender;
    }
}
_

クラスの変更

_ private void ContainedElementChanged(object sender, PropertyChangedEventArgs e)
    {
        var ex = new PropertyChangedEventArgsEx(e.PropertyName, sender);
        OnPropertyChanged(ex);
    }
_

この後、Sendereにキャストすることにより、col.PropertyChanged += (s, e)の実際のPropertyChangedEventArgsEx要素を取得できます。

_((INotifyPropertyChanged)col).PropertyChanged += (s, e) =>
        {
            var argsEx = (PropertyChangedEventArgsEx)e;
            Trace.WriteLine(argsEx.Sender.ToString());
        };
_

ここでも、sは要素のコレクションであり、イベントをトリガーした実際の要素ではないことに注意してください。したがって、Senderクラスの新しいPropertyChangedEventArgsExプロパティ。

0
Ovi