2011-12-20 20 views
12

Uygulamamız için gözlemci desenini uyguluyorum - şu anda RX Framework ile oynamak. Better PropertyChanged ve PropertyChanging olay işleme

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

(I "PropertyChanging" için benzer bir tane var)

EventArgs beni çok vermeyin:

Şu anda şöyle bir örnek var. İstediğim şey, EventArgs'ın bir önceki ve yeni değerleri görme yeteneğini vermesinin yanı sıra, 'değişmekte olan' dinleyicide olayı işaretleme yeteneğinin yanı sıra değişimin devam etmeyeceği bir uzantıdır. Bu nasıl yapılabilir? Teşekkürler.

+0

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

cevap

22

INotifyPropertyChanging ve INotifyPropertyChanged arabirimlerini nasıl uyguladığınıza inanıyorum.

PropertyChangingEventArgs ve PropertyChangedEventArgs sınıfları maalesef, mülkün öncesi ve sonrası değerini veya değişikliği iptal etme olanağı sağlamamaktadır, ancak bu işlevsellik sağlayan kendi olay hata sınıflarını türetebilirsiniz.

İlk olarak, aşağıdaki olay hata sınıflarını tanımlayın. Bunların PropertyChangingEventArgs sınıfından ve PropertyChangedEventArgs sınıfından türediğine dikkat edin. Bu, bu nesneleri PropertyChangingEventHandler ve PropertyChangedEventHandler temsilcilerine argüman olarak iletmemize olanak tanır.

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

Ardından, INotifyPropertyChanging ve INotifyPropertyChanged arayüzleri uygulamanızda bu sınıfları kullanmak gerekir. ,

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

Not PropertyChanging için olay işleyicileri ve olaylar hala oldukça daha spesifik bir sürümden daha, parametre olarak orijinal PropertyChangingEventArgs sınıfını ve PropertyChangedEventArgs sınıf almak gerekir PropertyChanged: Bir uygulamaya örnek şudur. Ancak, yeni özelliklere erişmek için olay args nesnelerini daha spesifik türlerinize atayabilirsiniz.

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

Bu çok yardımcı oldu, çok teşekkürler! – user981225

+0

Setter yöntemlerinde bu kodun tümünü önlemek için bunu bir DynamicProxy kullanarak uygulamak mümkün olabilir mi? – user981225

+0

@ user981225 - DynamicProxy'ye aşina değilim (örneğin, [Castle] (http://www.castleproject.org/dynamicproxy/index.html) gibi), ama AOP veya politika enjeksiyon yaklaşımı almayı kastediyorum. . Düşünme çizginize katılıyorum ve bu özellik setter kodunu pekiştirmek için bir çok mekanizmaya (AOP, ilke enjeksiyonuna veya başka bir şekilde) bakmaya çalışıyorum. Yine de, uygunsuz bir şey önermek için özel bir mekanizmam yok. –

2

kabul tepki gerçekten kötü, sadece Tampon ile() bunu yapabilir:

Aşağıda bu olaylar için olay işleyicileri bir örnektir. Her iki RX en iyisini istiyorum ve burada İptal edememek yok herkes için

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 ... birkaç sorun: 1) Yerleşik PropertyChanged/PropertyChanging olayları, bu "Buffer" yaklaşımının çalışması için gerekli olan yeni veya orijinal değerleri sağlamaz. 2) Bu yaklaşım, olayın bir kereden fazla ateşlenmesini gerektirir, böylece özellik değerini iki olay arasında değiştikçe karşılaştırabilirsiniz. OP, yalnızca bir etkinlik için mülkün önceki ve şimdiki değerlerini gözlemlemek isteyebilir. 3) OP, mülk değerindeki değişikliği iptal edebilmek ister. Yerleşik PropertyChanging olayı bir iptal mekanizması sağlamaz. –

0

bu fikirlerin ikisinin karışımı olan

ViewModel temel sınıf malzeme Sonra var

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

Bu çift uzantıları.

Bir

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

} 

Sonra örnek kullanımı bu

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

gibi olacak

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

} 

İfade ağacından özellik adı Sonra Rx kısmını almak için bir tedavi Works

İlgili konular