ネストされた(子)オブジェクトのINotifyPropertyChanged
イベントを処理するためのクリーンでelegantソリューションを探しています。コード例:
public class Person : INotifyPropertyChanged {
private string _firstName;
private int _age;
private Person _bestFriend;
public string FirstName {
get { return _firstName; }
set {
// Short implementation for simplicity reasons
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
public int Age {
get { return _age; }
set {
// Short implementation for simplicity reasons
_age = value;
RaisePropertyChanged("Age");
}
}
public Person BestFriend {
get { return _bestFriend; }
set {
// - Unsubscribe from _bestFriend's INotifyPropertyChanged Event
// if not null
_bestFriend = value;
RaisePropertyChanged("BestFriend");
// - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null
// - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like
// to have the RaisePropertyChanged("BestFriend") method invoked
// - Also, I guess some kind of *weak* event handler is required
// if a Person instance i beeing destroyed
}
}
// **INotifyPropertyChanged implementation**
// Implementation of RaisePropertyChanged method
}
BestFriend
プロパティとその値の設定に注目してください。今私はこれを手動で行うことができることを知っています、コメントに記載されているすべての手順を実装します。しかし、これは多くのコードになるでしょう。特に、このようにINotifyPropertyChanged
を実装する多くの子プロパティを計画している場合はなおさらです。もちろん、それらは常に同じタイプになるとは限りません。それらが共通しているのは、INotifyPropertyChanged
インターフェースだけです。
その理由は、私の実際のシナリオでは、いくつかのレイヤーにネストされたオブジェクトプロパティを持つ複雑な「アイテム」(カート内)オブジェクトがあり(アイテムには「ライセンス」オブジェクトがあり、それ自体が子オブジェクトを持つことができる)、私は価格を再計算できるようにするには、「アイテム」の単一の変更について通知を受ける必要があります。
これを解決するのに役立つヒントや実装はありますか?
残念ながら、目標を達成するためにPostSharpのようなビルド後の手順を使用することはできません。
すぐに使えるソリューションを見つけることができなかったので、Pieters(およびMarks)の提案(ありがとう!)に基づいてカスタム実装を行いました。
クラスを使用すると、深いオブジェクトツリーの変更が通知されます。これは、INotifyPropertyChanged
実装タイプとINotifyCollectionChanged
*実装コレクションで機能します(明らかに、私はObservableCollection
)。
これが非常にクリーンでエレガントなソリューションであることがわかったと思いますが、完全にはテストされていませんが、拡張の余地があります。使い方はかなり簡単です。静的なChangeListener
メソッドを使用してCreate
のインスタンスを作成し、INotifyPropertyChanged
を渡すだけです。
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
PropertyChangedEventArgs
はPropertyName
を提供します。これは常にオブジェクトの完全な「パス」になります。たとえば、個人の「BestFriend」名を変更すると、PropertyName
は「BestFriend.Name」になります。BestFriend
に子のコレクションがあり、年齢を変更すると、値は「BestFriend.Children []。Age」などになります。オブジェクトが破棄されるときはDispose
を忘れないでください。そうすれば、(できれば)すべてのイベントリスナーから完全にサブスクライブ解除されます。
.NET(4でテスト済み)およびSilverlight(4でテスト済み)でコンパイルされます。コードは3つのクラスに分かれているので、コードをGist 70545に投稿しました。 https://Gist.github.com/70545 **
*)コードが機能している1つの理由は、ObservableCollection
もINotifyPropertyChanged
を実装していることです。それ以外の場合、期待どおりに機能しません。これは既知の警告です
**) MITライセンス の下でリリースされた無料の使用
私が探しているのは、WPFバインディングのようなものだと思います。
INotifyPropertyChanged
の仕組みは、プロパティBestFriend
が変更されたときにRaisePropertyChanged("BestFriend");
がonlyである必要があることです。オブジェクト自体に何かが変更されたときではありません。
これを実装する方法は、2ステップのINotifyPropertyChanged
イベントハンドラーです。リスナーは、Person
の変更されたイベントに登録します。 BestFriend
が設定/変更されたら、BestFriend
Person
の変更されたイベントに登録します。次に、そのオブジェクトの変更されたイベントのリスニングを開始します。
これがまさに、WPFバインディングがこれを実装する方法です。ネストされたオブジェクトの変更のリスニングは、そのシステムを介して行われます。
これをPerson
に実装するときに機能しない理由は、レベルが非常に深くなる可能性があり、BestFriend
の変更されたイベントが何も意味しないためです(「何が変更されたのか?」 )。この問題は、循環関係がある場合に大きくなります。あなたの月の親友はあなたの親友の母親です。次に、プロパティの1つが変更されると、スタックオーバーフローが発生します。
したがって、これを解決するには、リスナーを作成できるクラスを作成します。たとえば、BestFriend.FirstName
でリスナーを構築します。次に、そのクラスは、Person
の変更されたイベントにイベントハンドラーを配置し、BestFriend
の変更をリッスンします。次に、それが変更されると、BestFriend
にリスナーを配置し、FirstName
の変更をリッスンします。次に、それが変更されると、イベントを発生させ、それを聞くことができます。これが基本的にWPFバインディングの動作方法です。
WPFバインディングの詳細については、 http://msdn.Microsoft.com/en-us/library/ms750413.aspx を参照してください。
興味深い解決策トーマス。
別の解決策を見つけました。それはプロパゲーターデザインパターンと呼ばれています。詳細については、Webを参照してください(たとえば、CodeProject: Propagator in C#-An Alternative to the Observer Design Pattern )。
基本的に、これは依存関係ネットワーク内のオブジェクトを更新するためのパターンです。オブジェクトのネットワークを通じて状態の変更をプッシュする必要がある場合に非常に役立ちます。状態の変化は、伝播関数のネットワークを通過するオブジェクト自体によって表されます。状態変化をオブジェクトとしてカプセル化することにより、プロパゲーターは疎結合になります。
再利用可能なPropagatorクラスのクラス図:
詳しくは CodeProject をご覧ください。
CodeProjectで私のソリューションをチェックアウトしてください: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator それはまさにあなたが必要とすることを行います-このビューモデルまたはネストされたビューモデルの関連する依存関係が変更されたときに依存プロパティを(エレガントな方法で)伝達するのに役立ちます。
public decimal ExchTotalPrice
{
get
{
RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice));
RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate));
return TotalPrice * ExchangeRate.Rate;
}
}
これを行うための簡単なヘルパーを作成しました。親ビューモデルでBubblePropertyChanged(x => x.BestFriend)を呼び出すだけです。 n.b.親にNotifyPropertyChagnedというメソッドがあることを前提としていますが、それを適応させることができます。
/// <summary>
/// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping
/// the naming hierarchy in place.
/// This is useful for nested view models.
/// </summary>
/// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
/// <returns></returns>
public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property)
{
// This step is relatively expensive but only called once during setup.
MemberExpression body = (MemberExpression)property.Body;
var prefix = body.Member.Name + ".";
INotifyPropertyChanged child = property.Compile().Invoke();
PropertyChangedEventHandler handler = (sender, e) =>
{
this.NotifyPropertyChanged(prefix + e.PropertyName);
};
child.PropertyChanged += handler;
return Disposable.Create(() => { child.PropertyChanged -= handler; });
}
私は1日間Webを検索していて、Sacha Barberから別の素晴らしい解決策を見つけました。
http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer
彼は連鎖プロパティオブザーバー内に弱い参照を作成しました。この機能を実装する別の優れた方法を見たい場合は、記事をご覧ください。
また、Reactive Extensions @ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/ を使用したすてきな実装についても触れておきます。
このソリューションは、オブザーバーの完全なチェーンではなく、1レベルのオブザーバーでのみ機能します。