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.
(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