2011-12-20 4 views
12

私たちのアプリケーションのためのオブザーバーパターンを実装しています - 現在RXフレームワークで遊んでいます。より良いPropertyChangedとPropertyChangingのイベント処理

私は現在、このようになります例があります(私は「PropertyChanging」のための同様のものを持っている)

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged") 
    .Where(e => e.EventArgs.PropertyName == "City") 
    .ObserveOn(Scheduler.ThreadPool) 
    .Subscribe(search => OnNewSearch(search.EventArgs)); 

のEventArgsは私に多くを与えていません。私が望むのは、EventArgsを拡張して、以前の値と新しい値を見ることができるだけでなく、変更が実際には持続しないように '変化する'リスナーでイベントをマークする機能です。これはどうすればできますか?ありがとう。

+0

[this](https: //github.com/dotnet/corefx/issues/19627) – Shimmy

答えて

22

私は、INotifyPropertyChangingとINotifyPropertyChangedインターフェイスをどのように実装するのかが分かります。

残念ながら、PropertyChangingEventArgsクラスとPropertyChangedEventArgsクラスでは、プロパティの前後の値や変更を取り消すことはできませんが、その機能を提供する独自のイベントargsクラスを派生させることができます。

まず、次のイベント引数クラスを定義します。これらはPropertyChangingEventArgsクラスとPropertyChangedEventArgsクラスから派生しています。これにより、これらのオブジェクトをPropertyChangingEventHandlerおよびPropertyChangedEventHandlerデリゲートの引数として渡すことができます。

class PropertyChangingCancelEventArgs : PropertyChangingEventArgs 
{ 
    public bool Cancel { get; set; } 

    public PropertyChangingCancelEventArgs(string propertyName) 
     : base(propertyName) 
    { 
    } 
} 

class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs 
{ 
    public T OriginalValue { get; private set; } 

    public T NewValue { get; private set; } 

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue) 
     : base(propertyName) 
    { 
     this.OriginalValue = originalValue; 
     this.NewValue = newValue; 
    } 
} 

class PropertyChangedEventArgs<T> : PropertyChangedEventArgs 
{ 
    public T PreviousValue { get; private set; } 

    public T CurrentValue { get; private set; } 

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue) 
     : base(propertyName) 
    { 
     this.PreviousValue = previousValue; 
     this.CurrentValue = currentValue; 
    } 
} 

次に、あなたはINotifyPropertyChangingとINotifyPropertyChangedのインタフェースの実装では、これらのクラスを使用する必要があります。実装の例は以下の通りです:

class Example : INotifyPropertyChanging, INotifyPropertyChanged 
{ 
    public event PropertyChangingEventHandler PropertyChanging; 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue) 
    { 
     var handler = this.PropertyChanging; 
     if (handler != null) 
     { 
      var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue); 
      handler(this, args); 
      return !args.Cancel; 
     } 
     return true; 
    } 

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue) 
    { 
     var handler = this.PropertyChanged; 
     if (handler != null) 
      handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue)); 
    } 

    int _ExampleValue; 

    public int ExampleValue 
    { 
     get { return _ExampleValue; } 
     set 
     { 
      if (_ExampleValue != value) 
      { 
       if (this.OnPropertyChanging("ExampleValue", _ExampleValue, value)) 
       { 
        var previousValue = _ExampleValue; 
        _ExampleValue = value; 
        this.OnPropertyChanged("ExampleValue", previousValue, value); 
       } 
      } 
     } 
    } 
} 

注意、PropertyChangingとのPropertyChangedイベントのためのあなたのイベントハンドラは、まだパラメータではなく、より具体的なバージョンとして、元PropertyChangingEventArgsクラスとPropertyChangedEventArgsクラスを取る必要があります。ただし、新しいプロパティにアクセスするために、イベントのargsオブジェクトをより具体的な型にキャストすることができます。以下は

これらのイベントのイベントハンドラの例です。

class Program 
{ 
    static void Main(string[] args) 
    { 
     var exampleObject = new Example(); 

     exampleObject.PropertyChanging += new PropertyChangingEventHandler(exampleObject_PropertyChanging); 
     exampleObject.PropertyChanged += new PropertyChangedEventHandler(exampleObject_PropertyChanged); 

     exampleObject.ExampleValue = 123; 
     exampleObject.ExampleValue = 100; 
    } 

    static void exampleObject_PropertyChanging(object sender, PropertyChangingEventArgs e) 
    { 
     if (e.PropertyName == "ExampleValue") 
     { 
      int originalValue = ((PropertyChangingCancelEventArgs<int>)e).OriginalValue; 
      int newValue = ((PropertyChangingCancelEventArgs<int>)e).NewValue; 

      // do not allow the property to be changed if the new value is less than the original value 
      if(newValue < originalValue) 
       ((PropertyChangingCancelEventArgs)e).Cancel = true; 
     } 

    } 

    static void exampleObject_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "ExampleValue") 
     { 
      int previousValue = ((PropertyChangedEventArgs<int>)e).PreviousValue; 
      int currentValue = ((PropertyChangedEventArgs<int>)e).CurrentValue; 
     } 
    } 
} 
+0

これは非常に役に立ちます。ありがとうございます! – user981225

+0

DynamicProxyを使用してこれを実装して、セッターメソッドのすべてのコードを回避できますか? – user981225

+0

@ user981225 - DynamicProxy([Castle](http://www.castleproject.org/dynamicproxy/index.html)など)には慣れていませんが、AOPやポリシーインジェクションアプローチをとっていると思います。私はあなたの考え方に同意し、私は間違いなくあなたが冗長なコードをたくさん持っていないようにそのプロパティ設定コードを統合するためのいくつかのメカニズム(AOP、ポリシーインジェクション、またはその他)を探します。私は、しかし、特にお勧めする特定のメカニズムがありません。 –

2

受け入れ応答が本当に悪いです、あなたは、単にバッファ(とそれを行うことができます)。両方のRXの最高の、ここでキャンセルすることができることは、これらのアイデアの両方のハイブリッド

ViewModelに基本クラスのもの

public abstract class INPCBase : INotifyPropertyChanged, INotifyPropertyChanging 
{ 
    public event PropertyChangingEventHandler PropertyChanging; 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected bool OnPropertyChanging<T>(string propertyName, T originalValue, T newValue) 
    { 
     var handler = this.PropertyChanging; 
     if (handler != null) 
     { 
      var args = new PropertyChangingCancelEventArgs<T>(propertyName, originalValue, newValue); 
      handler(this, args); 
      return !args.Cancel; 
     } 
     return true; 
    } 

    protected void OnPropertyChanged<T>(string propertyName, T previousValue, T currentValue) 
    { 
     var handler = this.PropertyChanged; 
     if (handler != null) 
      handler(this, new PropertyChangedEventArgs<T>(propertyName, previousValue, currentValue)); 
    } 
} 


public class PropertyChangingCancelEventArgs : PropertyChangingEventArgs 
{ 
    public bool Cancel { get; set; } 

    public PropertyChangingCancelEventArgs(string propertyName) 
     : base(propertyName) 
    { 
    } 
} 

public class PropertyChangingCancelEventArgs<T> : PropertyChangingCancelEventArgs 
{ 
    public T OriginalValue { get; private set; } 

    public T NewValue { get; private set; } 

    public PropertyChangingCancelEventArgs(string propertyName, T originalValue, T newValue) 
     : base(propertyName) 
    { 
     this.OriginalValue = originalValue; 
     this.NewValue = newValue; 
    } 
} 

public class PropertyChangedEventArgs<T> : PropertyChangedEventArgs 
{ 
    public T PreviousValue { get; private set; } 

    public T CurrentValue { get; private set; } 

    public PropertyChangedEventArgs(string propertyName, T previousValue, T currentValue) 
     : base(propertyName) 
    { 
     this.PreviousValue = previousValue; 
     this.CurrentValue = currentValue; 
    } 
} 

ですその後、私は持っていたいん誰のために

Observable.FromEventPattern<PropertyChangedEventArgs>(Instance.Address, "PropertyChanged") 
    .Where(e => e.EventArgs.PropertyName == "City") 
    .Buffer(2,1) //Take 2 events at a time, every 1 event 
    .ObserveOn(Scheduler.ThreadPool) 
    .Subscribe(search => ...); //search[0] is old value, search[1] is new value 
+2

Mmm ...いくつかの問題:1)組み込みのPropertyChanged/PropertyChangingイベントは、この「バッファ」アプローチが機能するために必要なプロパティの新しい値または元の値を提供しません。 2)この方法では、イベントが2回以上発生する必要があります。そのため、2つのイベント間でプロパティの値が変化するときに比較できます。 OPは、単一のイベントについてのみ、プロパティの以前の値および現在の値を観察できるようにすることができます。 3)OPはプロパティ値の変更を取り消すことができるようにしたい。組み込みのPropertyChangingイベントは、キャンセルメカニズムを提供しません。 –

0

これらのカップルの拡張機能。

一つの式ツリーからプロパティ名を取得する

public static class ExpressionExtensions 
{ 

    public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> expression) 
    { 
     var memberExpression = expression.Body as MemberExpression; 
     if (memberExpression == null) 
     { 
      var unaryExpression = expression.Body as UnaryExpression; 
      if (unaryExpression != null) 
      { 
       if (unaryExpression.NodeType == ExpressionType.ArrayLength) 
        return "Length"; 
       memberExpression = unaryExpression.Operand as MemberExpression; 

       if (memberExpression == null) 
       { 
        var methodCallExpression = unaryExpression.Operand as MethodCallExpression; 
        if (methodCallExpression == null) 
         throw new NotImplementedException(); 

        var arg = (ConstantExpression)methodCallExpression.Arguments[2]; 
        return ((MethodInfo)arg.Value).Name; 
       } 
      } 
      else 
       throw new NotImplementedException(); 

     } 

     var propertyName = memberExpression.Member.Name; 
     return propertyName; 

    } 

    public static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty>> expression) 
    { 
     var memberExpression = expression.Body as MemberExpression; 

     if (memberExpression == null) 
     { 
      var unaryExpression = expression.Body as UnaryExpression; 

      if (unaryExpression != null) 
      { 
       if (unaryExpression.NodeType == ExpressionType.ArrayLength) 
        return "Length"; 
       memberExpression = unaryExpression.Operand as MemberExpression; 

       if (memberExpression == null) 
       { 
        var methodCallExpression = unaryExpression.Operand as MethodCallExpression; 
        if (methodCallExpression == null) 
         throw new NotImplementedException(); 

        var arg = (ConstantExpression)methodCallExpression.Arguments[2]; 
        return ((MethodInfo)arg.Value).Name; 
       } 
      } 
      else 
       throw new NotImplementedException(); 
     } 
     var propertyName = memberExpression.Member.Name; 
     return propertyName; 

    } 

    public static String PropertyToString<R>(this Expression<Func<R>> action) 
    { 
     MemberExpression ex = (MemberExpression)action.Body; 
     return ex.Member.Name; 
    } 

    public static void CheckIsNotNull<R>(this Expression<Func<R>> action, string message) 
    { 
     MemberExpression ex = (MemberExpression)action.Body; 
     string memberName = ex.Member.Name; 
     if (action.Compile()() == null) 
     { 
      throw new ArgumentNullException(memberName, message); 
     } 
    } 

} 

そしてRxの部分

public static class ObservableExtensions 
{ 

    public static IObservable<ItemPropertyChangingEvent<TItem, TProperty>> ObserveSpecificPropertyChanging<TItem, TProperty>(
     this TItem target, Expression<Func<TItem, TProperty>> propertyName) where TItem : INotifyPropertyChanging 
    { 
     var property = propertyName.GetPropertyName(); 

     return ObserveSpecificPropertyChanging(target, property) 
       .Select(i => new ItemPropertyChangingEvent<TItem, TProperty>() 
       { 
        OriginalEventArgs = (PropertyChangingCancelEventArgs<TProperty>)i.OriginalEventArgs, 
        Property = i.Property, 
        Sender = i.Sender 
       }); 
    } 

    public static IObservable<ItemPropertyChangingEvent<TItem>> ObserveSpecificPropertyChanging<TItem>(
     this TItem target, string propertyName = null) where TItem : INotifyPropertyChanging 
    { 

     return Observable.Create<ItemPropertyChangingEvent<TItem>>(obs => 
     { 
      Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>(); 
      PropertyChangingEventHandler handler = null; 

      handler = (s, a) => 
      { 
       if (propertyName == null || propertyName == a.PropertyName) 
       { 
        PropertyInfo prop; 
        if (!properties.TryGetValue(a.PropertyName, out prop)) 
        { 
         prop = target.GetType().GetProperty(a.PropertyName); 
         properties.Add(a.PropertyName, prop); 
        } 
        var change = new ItemPropertyChangingEvent<TItem>() 
        { 
         Sender = target, 
         Property = prop, 
         OriginalEventArgs = a, 
        }; 

        obs.OnNext(change); 
       } 
      }; 

      target.PropertyChanging += handler; 

      return() => 
      { 
       target.PropertyChanging -= handler; 
      }; 
     }); 
    } 



    public class ItemPropertyChangingEvent<TSender> 
    { 
     public TSender Sender { get; set; } 
     public PropertyInfo Property { get; set; } 
     public PropertyChangingEventArgs OriginalEventArgs { get; set; } 

     public override string ToString() 
     { 
      return string.Format("Sender: {0}, Property: {1}", Sender, Property); 
     } 
    } 


    public class ItemPropertyChangingEvent<TSender, TProperty> 
    { 
     public TSender Sender { get; set; } 
     public PropertyInfo Property { get; set; } 
     public PropertyChangingCancelEventArgs<TProperty> OriginalEventArgs { get; set; } 
    } 

} 

この

public class MainWindowViewModel : INPCBase 
{ 
    private string field1; 
    private string field2; 


    public MainWindowViewModel() 
    { 
     field1 = "Hello"; 
     field2 = "World"; 

     this.ObserveSpecificPropertyChanging(x => x.Field2) 
      .Subscribe(x => 
      { 
       if (x.OriginalEventArgs.NewValue == "DOG") 
       { 
        x.OriginalEventArgs.Cancel = true; 
       } 
      }); 

    } 

    public string Field1 
    { 
     get 
     { 
      return field1; 
     } 
     set 
     { 
      if (field1 != value) 
      { 
       if (this.OnPropertyChanging("Field1", field1, value)) 
       { 
        var previousValue = field1; 
        field1 = value; 
        this.OnPropertyChanged("Field1", previousValue, value); 
       } 
      } 
     } 
    } 


    public string Field2 
    { 
     get 
     { 
      return field2; 
     } 
     set 
     { 
      if (field2 != value) 
      { 
       if (this.OnPropertyChanging("Field2", field2, value)) 
       { 
        var previousValue = field2; 
        field2 = value; 
        this.OnPropertyChanged("Field2", previousValue, value); 
       } 
      } 
     } 
    } 
} 

が御馳走を作品のように次に使用例は次のようになります

関連する問題