c# - propertychangedeventargs - Suscribirse a INotifyPropertyChanged para objetos anidados(secundarios)
onpropertychanged c# (6)
Como no pude encontrar una solución lista para usar, realicé una implementación personalizada basada en las sugerencias de Pieters (y Marks) (¡gracias!).
Al usar las clases, se le notificará acerca de cualquier cambio en un árbol de objetos profundo, esto funciona para cualquier implementación de INotifyCollectionChanged
y INotifyCollectionChanged
implementación de INotifyCollectionChanged
* (Obviamente, estoy usando el ObservableCollection
para eso).
Espero que esta sea una solución bastante limpia y elegante, aunque no está totalmente probada y hay espacio para mejoras. Es bastante fácil de usar, solo crea una instancia de ChangeListener
usando su método Create
estático y pasando tu INotifyPropertyChanged
:
var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged +=
new PropertyChangedEventHandler(listener_PropertyChanged);
the PropertyChangedEventArgs
proporciona un PropertyName
que siempre será la "ruta" completa de tus objetos. Por ejemplo, si cambia el nombre de "BestFriend" de sus Personas, PropertyName
será "BestFriend.Name", si BestFriend
tiene una colección de Children y usted cambia su edad, el valor será "BestFriend.Children []. Age" y así. No te olvides de Dispose
cuando se destruye tu objeto, entonces (con suerte) se dará de baja completamente de todos los oyentes del evento.
Se compila en .NET (probado en 4) y Silverlight (probado en 4). Debido a que el código está dividido en tres clases, he publicado el código para ir al 705450 donde puedes obtenerlo todo: https://gist.github.com/705450 **
*) Uno de los motivos por los que el código está funcionando es que el ObservableCollection
también implementa INotifyPropertyChanged
, de lo contrario no funcionaría como se desea, esta es una advertencia conocida
**) Uso gratis, publicado bajo licencia MIT
Estoy buscando una solución limpia y elegante para manejar el evento INotifyPropertyChanged
de objetos anidados (secundarios). Código de ejemplo:
public class Person : INotifyPropertyChanged {
private string _firstName;
private int _age;
private Person _bestFriend;
public string FirstName {
get { return _firstName; }
set {
// Short implementation for simplicity reasons
_firstName = value;
RaisePropertyChanged("FirstName");
}
}
public int Age {
get { return _age; }
set {
// Short implementation for simplicity reasons
_age = value;
RaisePropertyChanged("Age");
}
}
public Person BestFriend {
get { return _bestFriend; }
set {
// - Unsubscribe from _bestFriend''s INotifyPropertyChanged Event
// if not null
_bestFriend = value;
RaisePropertyChanged("BestFriend");
// - Subscribe to _bestFriend''s INotifyPropertyChanged Event if not null
// - When _bestFriend''s INotifyPropertyChanged Event is fired, i''d like
// to have the RaisePropertyChanged("BestFriend") method invoked
// - Also, I guess some kind of *weak* event handler is required
// if a Person instance i beeing destroyed
}
}
// **INotifyPropertyChanged implementation**
// Implementation of RaisePropertyChanged method
}
Concéntrese en la Propiedad BestFriend
y en su valor. Ahora sé que podría hacerlo manualmente , implementando todos los pasos descritos en los comentarios. Pero esto va a ser un montón de código, especialmente cuando estoy planeando tener muchas propiedades secundarias implementando INotifyPropertyChanged
esta manera. Por supuesto, no van a ser siempre del mismo tipo, lo único que tienen en común es la interfaz INotifyPropertyChanged
.
La razón es que, en mi situación real, tengo un objeto complejo "Artículo" (en carrito) que tiene propiedades de objeto anidadas en varias capas (el artículo tiene un objeto "Licencia", que puede tener objetos secundarios otra vez) y yo necesita ser notificado sobre cualquier cambio individual del "Artículo" para poder volver a calcular el precio.
¿Tienes algunos buenos consejos o incluso alguna implementación para ayudarme a resolver esto?
Lamentablemente, no puedo / me permite usar pasos posteriores a la compilación como PostSharp para lograr mi objetivo.
Muchas gracias por adelantado,
- Thomas
Escribí un ayudante fácil para hacer esto. Simplemente llame a BubblePropertyChanged (x => x.BestFriend) en su modelo de vista padre. nb hay una suposición de que tienes un método llamado NotifyPropertyChagned en tu padre, pero puedes adaptar eso.
/// <summary>
/// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping
/// the naming hierarchy in place.
/// This is useful for nested view models.
/// </summary>
/// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
/// <returns></returns>
public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property)
{
// This step is relatively expensive but only called once during setup.
MemberExpression body = (MemberExpression)property.Body;
var prefix = body.Member.Name + ".";
INotifyPropertyChanged child = property.Compile().Invoke();
PropertyChangedEventHandler handler = (sender, e) =>
{
this.NotifyPropertyChanged(prefix + e.PropertyName);
};
child.PropertyChanged += handler;
return Disposable.Create(() => { child.PropertyChanged -= handler; });
}
He estado buscando en la Web por un día y encontré otra buena solución de Sacha Barber:
http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer
Creó referencias débiles dentro de un observador de propiedades encadenado. Consulte el artículo si desea ver otra forma excelente de implementar esta característica.
Y también quiero mencionar una buena implementación con Reactive Extensions @ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/
Esta Solución solo funciona para un Nivel de Observador, no para una Cadena de Observadores completa.
Interesante solución Thomas.
Encontré otra solución. Se llama patrón de diseño de propagador. Puede encontrar más en la web (por ejemplo, en CodeProject: Propagator in C # - Una alternativa al patrón de diseño del observador ).
Básicamente, es un patrón para actualizar objetos en una red de dependencia. Es muy útil cuando los cambios de estado deben ser empujados a través de una red de objetos. Un cambio de estado está representado por un objeto en sí que viaja a través de la red de Propagadores. Al encapsular el cambio de estado como un objeto, los Propagadores se acoplan libremente.
Un diagrama de clases de las clases de Propagator reutilizables:
Lea más en CodeProject .
Verifique mi solución en CodeProject: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator Hace exactamente lo que necesita: ayuda a propagar (de manera elegante) las propiedades dependientes cuando las dependencias relevantes en este o cualquier otro sitio anidado ver modelos cambiar:
public decimal ExchTotalPrice
{
get
{
RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice));
RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate));
return TotalPrice * ExchangeRate.Rate;
}
}
Creo que lo que estás buscando es algo parecido a WPF binding.
Cómo funciona INotifyPropertyChanged
es que RaisePropertyChanged("BestFriend");
solo debe ser financiado cuando cambia la propiedad BestFriend
. No cuando algo en el objeto mismo cambia.
La forma de implementar esto es mediante un controlador de eventos INotifyPropertyChanged
dos pasos. Su oyente se registraría en el evento cambiado de la Person
. Cuando BestFriend
se configura / cambia, se registra en el evento modificado de la Person
BestFriend
. Luego, comienzas a escuchar eventos cambiados de ese objeto.
Así es exactamente como el enlace de WPF implementa esto. La escucha de cambios de objetos anidados se realiza a través de ese sistema.
La razón por la que esto no va a funcionar cuando lo implemente en Person
es porque los niveles pueden llegar a ser muy profundos y el evento cambiado de BestFriend
ya no significa nada ("¿qué ha cambiado?"). Este problema se hace más grande cuando tienes relaciones circulares donde, por ejemplo, el mejor amigo de tu monstruo es la madre de tu mejor enemigo. Luego, cuando una de las propiedades cambia, obtienes un desbordamiento de la pila.
Entonces, cómo resolverías esto es crear una clase con la que puedas construir oyentes. Por ejemplo, construirías un oyente en BestFriend.FirstName
. Esa clase colocaría un controlador de eventos en el evento modificado de Person
y escucharía los cambios en BestFriend
. Luego, cuando eso cambia, pone un oyente en BestFriend
y escucha los cambios de FirstName
. Luego, cuando eso cambie, envía un evento y puedes escucharlo. Eso es básicamente cómo funciona el enlace de WPF.
Consulte http://msdn.microsoft.com/en-us/library/ms750413.aspx para obtener más información sobre el enlace de WPF.