我是.NET和WPF的新手,所以我希望我会正确地提出这个问题。 我正在使用PostSharp 1.5实现的INotifyPropertyChanged:
[Serializable, DebuggerNonUserCode, AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false),
MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false, Inheritance = MulticastInheritance.None, AllowExternalAssemblies = true)]
public sealed class NotifyPropertyChangedAttribute : CompoundAspect
{
public int AspectPriority { get; set; }
public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
{
Type targetType = (Type)element;
collection.AddAspect(targetType, new PropertyChangedAspect { AspectPriority = AspectPriority });
foreach (var info in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(pi => pi.GetSetMethod() != null))
{
collection.AddAspect(info.GetSetMethod(), new NotifyPropertyChangedAspect(info.Name) { AspectPriority = AspectPriority });
}
}
}
[Serializable]
internal sealed class PropertyChangedAspect : CompositionAspect
{
public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
{
return new PropertyChangedImpl(eventArgs.Instance);
}
public override Type GetPublicInterface(Type containerType)
{
return typeof(INotifyPropertyChanged);
}
public override CompositionAspectOptions GetOptions()
{
return CompositionAspectOptions.GenerateImplementationAccessor;
}
}
[Serializable]
internal sealed class NotifyPropertyChangedAspect : OnMethodBoundaryAspect
{
private readonly string _propertyName;
public NotifyPropertyChangedAspect(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
_propertyName = propertyName;
}
public override void OnEntry(MethodExecutionEventArgs eventArgs)
{
var targetType = eventArgs.Instance.GetType();
var setSetMethod = targetType.GetProperty(_propertyName);
if (setSetMethod == null) throw new AccessViolationException();
var oldValue = setSetMethod.GetValue(eventArgs.Instance, null);
var newValue = eventArgs.GetReadOnlyArgumentArray()[0];
if (oldValue == newValue) eventArgs.FlowBehavior = FlowBehavior.Return;
}
public override void OnSuccess(MethodExecutionEventArgs eventArgs)
{
var instance = eventArgs.Instance as IComposed<INotifyPropertyChanged>;
var imp = instance.GetImplementation(eventArgs.InstanceCredentials) as PropertyChangedImpl;
imp.OnPropertyChanged(_propertyName);
}
}
[Serializable]
internal sealed class PropertyChangedImpl : INotifyPropertyChanged
{
private readonly object _instance;
public PropertyChangedImpl(object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
_instance = instance;
}
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName");
var handler = PropertyChanged as PropertyChangedEventHandler;
if (handler != null) handler(_instance, new PropertyChangedEventArgs(propertyName));
}
}
}
然后我有几个实现[NotifyPropertyChanged]的类(用户和地址)。 它工作正常。但我想要的是,如果子对象更改(在我的示例地址中)父对象得到通知(在我的情况下是用户)。是否可以扩展此代码,以便在父对象上自动创建侦听器,以侦听其子对象中的更改?
答案 0 :(得分:3)
我不确定这是否适用于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;
}
以前,它只会在属性有一个setter时工作(即使只是一个私有的setter)。如果酒店只有吸气剂,您将不会收到任何通知。请注意,这仍然只提供单级别的通知(它不会通知您层次结构中任何对象的任何更改。)您可以通过手动将OnChildPropertyChanged脉冲的每个实现与OnPropertyChanged一起使用(null)来完成类似的操作。属性名称,有效地让孩子的任何变化都被视为父母的整体变化。但是,这可能会导致数据绑定效率低下,因为它可能会导致重新评估所有绑定属性。
答案 1 :(得分:1)
我接近这个的方法是实现另一个接口,比如INotifyOnChildChanges
,其上有一个与PropertyChangedEventHandler
匹配的方法。然后我将定义另一个Aspect,它将PropertyChanged
事件连接到此处理程序。
此时,任何同时实现INotifyPropertyChanged
和INotifyOnChildChanges
的类都会收到有关子属性更改的通知。
我喜欢这个想法,可能必须自己实施。请注意,我还发现了很多情况,我想在属性集之外触发PropertyChanged
(例如,如果属性实际上是一个计算值并且您已经更改了其中一个组件),那么包装实际调用PropertyChanged
到基类可能是最佳的。 I use a lambda based solution to ensure type safety,这似乎是一个非常普遍的想法。