Я не уверен, что это работает в v1.5, но это работает в 2.0. Я сделал только базовое тестирование (он запускает метод правильно), так что используйте на свой страх и риск.
/// <summary>
/// Aspect that, when applied to a class, registers to receive notifications when any
/// child properties fire NotifyPropertyChanged. This requires that the class
/// implements a method OnChildPropertyChanged(Object sender, PropertyChangedEventArgs e).
/// </summary>
[Serializable]
[MulticastAttributeUsage(MulticastTargets.Class,
Inheritance = MulticastInheritance.Strict)]
public class OnChildPropertyChangedAttribute : InstanceLevelAspect
{
[ImportMember("OnChildPropertyChanged", IsRequired = true)]
public PropertyChangedEventHandler OnChildPropertyChangedMethod;
private IEnumerable<PropertyInfo> SelectProperties(Type type)
{
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public;
return from property in type.GetProperties(bindingFlags)
where property.CanWrite && typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
select property;
}
/// <summary>
/// Method intercepting any call to a property setter.
/// </summary>
/// <param name="args">Aspect arguments.</param>
[OnLocationSetValueAdvice, MethodPointcut("SelectProperties")]
public void OnPropertySet(LocationInterceptionArgs args)
{
if (args.Value == args.GetCurrentValue()) return;
var current = args.GetCurrentValue() as INotifyPropertyChanged;
if (current != null)
{
current.PropertyChanged -= OnChildPropertyChangedMethod;
}
args.ProceedSetValue();
var newValue = args.Value as INotifyPropertyChanged;
if (newValue != null)
{
newValue.PropertyChanged += OnChildPropertyChangedMethod;
}
}
}
Использование выглядит так:
[NotifyPropertyChanged]
[OnChildPropertyChanged]
class WiringListViewModel
{
public IMainViewModel MainViewModel { get; private set; }
public WiringListViewModel(IMainViewModel mainViewModel)
{
MainViewModel = mainViewModel;
}
private void OnChildPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if (sender == MainViewModel)
{
Debug.Print("Child is changing!");
}
}
}
Это будет применяться ко всем дочерним свойствам класса, которые реализуют INotifyPropertyChanged. Если вы хотите быть более избирательным, вы можете добавить еще один простой атрибут (например, [InterestingChild]) и использовать наличие этого атрибута в MethodPointcut.
Я обнаружил ошибку в приведенном выше. Метод SelectProperties должен быть изменен на:
private IEnumerable<PropertyInfo> SelectProperties(Type type)
{
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public;
return from property in type.GetProperties(bindingFlags)
where typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType)
select property;
}
Раньше это работало только тогда, когда в свойстве был установщик (даже если только частный установщик). Если бы у собственности был только получатель, вы не получили бы никакого уведомления. Обратите внимание, что это по-прежнему обеспечивает только один уровень уведомлений (он не будет уведомлять вас о каких-либо изменениях какого-либо объекта в иерархии.) Вы можете сделать что-то подобное, вручную установив для каждой реализации OnChildPropertyChanged pulse значение OnPropertyChanged со значением (null) для имя свойства, фактически позволяющее считать любое изменение в дочернем элементе общим изменением в родительском. Однако это может создать большую неэффективность при связывании данных, поскольку может привести к переоценке всех связанных свойств.