2011-12-20 13 views
12

Estoy implementando el patrón de observador para nuestra aplicación, actualmente jugando con el RX Framework.Mejor PropertyChanged y PropertyChanging manejo de eventos

Actualmente tengo un ejemplo que tiene este aspecto:

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

(que tienen una similar para "PropertyChanging")

Los EventArgs no me dan mucho. Lo que me gustaría es una extensión de EventArgs que me permita ver los valores anteriores y nuevos, así como la capacidad de marcar el evento en el oyente "cambiante", de modo que el cambio no persista realmente. ¿Cómo puede hacerse esto? Gracias.

+0

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

Respuesta

22

Creo que se trata de cómo implementar las interfaces INotifyPropertyChanging e INotifyPropertyChanged.

Desafortunadamente, las clases PropertyChangingEventArgs y PropertyChangedEventArgs no proporcionan un valor de antes y después de la propiedad o la posibilidad de cancelar el cambio, pero puede derivar sus propias clases de eventos args que proporcionan esa funcionalidad.

Primero, defina las siguientes clases de eventos args. Tenga en cuenta que estos derivan de la clase PropertyChangingEventArgs y de la clase PropertyChangedEventArgs. Esto nos permite pasar estos objetos como argumentos a los delegados PropertyChangingEventHandler y 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; 
    } 
} 

A continuación, se tendría que utilizar estas clases en su implementación de las interfaces INotifyPropertyChanging y INotifyPropertyChanged. Un ejemplo de aplicación es el siguiente:

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

Nota, sus controladores de eventos para el PropertyChanging y PropertyChanged eventos todavía tendrá que tomar la clase PropertyChangingEventArgs original y clase PropertyChangedEventArgs como parámetros, en lugar de una versión más específica. Sin embargo, podrá convertir los objetos de args de evento a sus tipos más específicos para acceder a las nuevas propiedades.

A continuación se muestra un ejemplo de controladores de eventos para estos eventos:

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

Esto es extremadamente útil, ¡muchas gracias! – user981225

+0

¿Sería posible implementar esto usando un DynamicProxy, para evitar todo ese código en los métodos setter? – user981225

+0

@ user981225 - No estoy familiarizado con DynamicProxy (como en [Castle] (http://www.castleproject.org/dynamicproxy/index.html)?), Pero supongo que te refieres a tener un enfoque de AOP o de inyección de políticas . Estoy de acuerdo con su línea de pensamiento y definitivamente buscaría algún mecanismo (AOP, inyección de políticas, o de otro tipo) para consolidar ese código de establecimiento de la propiedad para que no tenga muchos códigos redundantes. Sin embargo, no tengo ningún mecanismo en particular para recomendarlo. –

2

La respuesta aceptada es muy malo, puede hacerlo simplemente con Tampón().

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 ... un par de problemas: 1) Los eventos incorporados PropertyChanged/PropertyChanging no proporcionan los valores nuevos u originales de la propiedad, lo que sería necesario para que funcione este enfoque "Buffer". 2) Este enfoque requiere que el evento se active más de una vez para que pueda comparar el valor de la propiedad a medida que cambia entre dos eventos. Es posible que el OP desee poder observar los valores anteriores y actuales de la propiedad para un solo evento. 3) El OP quiere poder cancelar el cambio al valor de la propiedad. El evento incorporado PropertyChanging no proporciona un mecanismo de cancelación. –

0

Para cualquier persona que no quieren lo mejor de ambos RX y ser capaz de cancelar aquí es un híbrido de ambos de estas ideas

La materia clase base 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; 
    } 
} 

Entonces tengo estas extensiones de pareja

uno para obtener el nombre de la propiedad del árbol de expresión

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

} 

y luego la parte 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; } 
    } 

} 

Entonces ejemplo de uso será así

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

funciona un convite

Cuestiones relacionadas