2010-09-08 13 views
7

Ha habido muchos artículos sobre cómo usar la reflexión y LINQ para generar eventos PropertyChanged de forma segura, sin utilizar cadenas.Manejo de propiedades Cambiado de forma segura

¿Pero hay alguna forma de consumir eventos PropertyChanged de forma segura? Actualmente, estoy haciendo esto

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Property1": 
      ... 
     case "Property2": 
      ... 

     ....    
    } 
} 

¿Hay alguna manera de evitar las cadenas dura codificantes en una sentencia switch para manejar las diferentes propiedades? ¿Algún enfoque LINQ similar o basado en la reflexión?

Respuesta

3

Vamos a declarar un método que puede convertir una expresión lambda en un objeto de reflexión PropertyInfo (taken from my answer here):

public static PropertyInfo GetProperty<T>(Expression<Func<T>> expr) 
{ 
    var member = expr.Body as MemberExpression; 
    if (member == null) 
     throw new InvalidOperationException("Expression is not a member access expression."); 
    var property = member.Member as PropertyInfo; 
    if (property == null) 
     throw new InvalidOperationException("Member in expression is not a property."); 
    return property; 
} 

Y luego vamos a utilizar para obtener los nombres de las propiedades:

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    if (e.PropertyName == GetProperty(() => Property1).Name) 
    { 
     // ... 
    } 
    else if (e.PropertyName == GetProperty(() => Property2).Name) 
    { 
     // ... 
    } 
} 

Desafortunadamente no puede usar una declaración switch porque los nombres de las propiedades ya no son constantes de tiempo de compilación.

+0

(Aunque, francamente, si realmente desea la seguridad de tipos adecuada, tal vez debería considerar el uso de 'no PropertyChangedEventArgs' en absoluto y en lugar de declarar una propia que contiene el objeto' PropertyInfo' lugar de una cadena.) – Timwi

1

Josh Smith's MVVM Foundation incluye una clase PropertyObserver que hace lo que desea.

+0

Su código todavía usa cadenas para pasar el nombre de la propiedad, simplemente agrega una verificación para verificar que ese sea un nombre de propiedad válido: if (TypeDescriptor.GetProperties (this) [propertyName] == null) –

+0

+ 1 para apuntar a un gran conjunto de artículos: excelente código y explicaciones bien compiladas –

1

Evito el interruptor combinando el patrón de comando y alguna lógica de Expresión. Encapsula la acción de caso en un comando. Lo ilustraré utilizando una estructura de controlador de vista de modelo. código del mundo real - WinForms, pero es la misma idea

el ejemplo carga un árbol en una vista, cuando se establece la propiedad del árbol en el modelo.

una costumbre ICommand

void Execute(); 
string PropertyName { get; } 

Comando hormigón

public TreeChangedCommand(TreeModel model, ISelectTreeView selectTreeView,Expression<Func<object>> propertyExpression) 
    { 
     _model = model; 
     _selectTreeView = selectTreeView; 

     var body = propertyExpression.Body as MemberExpression; 
     _propertyName = body.Member.Name; 

    } 

controlador constructor

//handle notify changed event from model 
    _model.PropertyChanged += _model_PropertyChanged; 
    //init commands 
    commands = new List<ICommand>(); 
    commands.Add(new TreeChangedCommand(_model,_mainView,()=>_model.Tree)); 

manejador PropertyChanged

void _model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 
{ 
    //find the corresponding command and execute it. (instead of the switch) 
    commands.FirstOrDefault(cmd=>cmd.PropertyName.Equals(e.PropertyName)).Execute(); 
} 
1

Una solución reciente que he llegado con es encapsular la lógica de distribución de eventos en una clase dedicada.

La clase tiene un método público denominado Handle que tiene la misma firma que el delegado PropertyChangedEventHandler lo que significa que se puede suscribir a la PropertyChanged caso de cualquier clase que implementa la interfaz INotifyPropertyChanged.

La clase acepta delegados como el DelegateCommand de uso frecuente utilizado por la mayoría de las implementaciones de WPF, lo que significa que se puede usar sin tener que crear subclases.

La clase tiene el siguiente aspecto:

public class PropertyChangedHandler 
{ 
    private readonly Action<string> handler; 
    private readonly Predicate<string> condition; 
    private readonly IEnumerable<string> properties; 

    public PropertyChangedHandler(Action<string> handler, 
     Predicate<string> condition, IEnumerable<string> properties) 
    { 
     this.handler = handler; 
     this.condition = condition; 
     this.properties = properties; 
    } 

    public void Handle(object sender, PropertyChangedEventArgs e) 
    { 
     string property = e.PropertyName ?? string.Empty; 

     if (this.Observes(property) && this.ShouldHandle(property)) 
     { 
      handler(property); 
     } 
    } 

    private bool ShouldHandle(string property) 
    { 
     return condition == null ? true : condition(property); 
    } 

    private bool Observes(string property) 
    { 
     return string.IsNullOrEmpty(property) ? true : 
      !properties.Any() ? true : properties.Contains(property); 
    } 
} 

entonces se puede inscribir una propiedad controlador de eventos cambiada así:

var eventHandler = new PropertyChangedHandler(
    handler: p => { /* event handler logic... */ }, 
    condition: p => { /* determine if handler is invoked... */ }, 
    properties: new string[] { "Foo", "Bar" } 
); 

aViewModel.PropertyChanged += eventHandler.Handle; 

El PropertyChangedHandler se encarga de comprobar el PropertyName del PropertyChangedEventArgs y asegura que handler es invocado por los cambios de propiedad correctos.

Observe que PropertyChangedHandler también acepta un predicado para que el delegado del controlador se pueda enviar condicionalmente. La clase también le permite especificar múltiples propiedades para que un solo controlador pueda vincularse a múltiples propiedades de una sola vez.

Esto puede ampliarse fácilmente utilizando algunos métodos de extensiones para un registro de controlador más conveniente que le permite crear el controlador de eventos y suscribirse al evento PropertyChanged en una sola llamada a método y especificar las propiedades usando expresiones en lugar de cadenas para lograr algo se parece a esto:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    condition: p => handlerCondition, 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

esto es básicamente diciendo que cuando cualquiera de los Foo, Bar o Baz propiedades cambian handlerMethod se invocará si handlerCondition es cierto.

Se proporcionan sobrecargas del método OnPropertychanged para cubrir los diferentes requisitos de registro de eventos.

Si, por ejemplo, desea registrar un controlador que se detiene por cualquier evento de cambio de propiedad y siempre se ejecuta sólo tiene que hacer lo siguiente:

aViewModel.OnPropertyChanged(p => handlerMethod()); 

Si, por ejemplo, desea registrar un controlador que se ejecuta siempre, pero sólo para un único cambio de propiedad específica que puede hacer lo siguiente:

aViewModel.OnPropertyChanged(
    handler: p => handlerMethod(), 
    properties: aViewModel.GetProperties(p => p.Foo) 
); 

he encontrado este enfoque muy útil cuando se escriben las aplicaciones WPF MVVM. Imagine que tiene un escenario en el que desea invalidar un comando cuando cualquiera de las tres propiedades cambia. Utilizando el método normal que tendría que hacer algo como esto:

void PropertyChangedHandler(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case "Foo": 
     case "Bar": 
     case "Baz": 
      FooBarBazCommand.Invalidate(); 
      break; 
     ....    
    } 
} 

Si cambia el nombre de cualquiera de las propiedades modelo de vista que tendrá que acordarse de actualizar el controlador de eventos para seleccionar las propiedades correctas.

Utilización de la clase PropertyChangedHandler especificado anteriormente se puede lograr el mismo resultado con lo siguiente:

aViewModel.OnPropertyChanged(
    handler: p => FooBarBazCommand.Invalidate(), 
    properties: aViewModel.GetProperties(
     p => p.Foo, 
     p => p.Bar, 
     p => p.Baz 
    ) 
); 

Esto ahora tiene tiempo de compilación de seguridad así que si cualquiera de las propiedades modelo de vista se cambia el nombre del programa fallará para compilar.

2

Con C# 6.0 puede usar nameof. También puede hacer referencia a la propiedad de una clase sin crear una instancia de esa clase.

void model_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
    switch (e.PropertyName) 
    { 
     case nameof(ClassName.Property1): 
      ... 
     case nameof(ClassName.Property2): 
      ... 

     ....    
    } 
} 
Cuestiones relacionadas