2010-03-12 75 views
2

即时通讯.NET和WPF新手,所以我希望我会正确地提出问题。 我使用INotifyPropertyChanged的使用PostSharp 1.5实施:使用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

您想让孩子听众做什么? – 2010-03-14 15:57:28

+0

目前所有我想要的是,家长得到通知(对任何一个孩子的变化 - 任何深度)。 – no9 2010-03-15 06:28:17

+0

这是一个更具挑战性的问题。你必须使用某种反思(如果你不能依靠你的孩子来通知你有关他们的孩子的变化),并且决定在反思过程中如何以及何时进行递归总是有点冒险。导致您采用此解决方案的动机问题是什么?可能有设计更改可能有助于简化您的任务。 – 2010-03-15 14:37:27

回答

1

我会这样做的方法是实现另一个接口,如INotifyOnChildChanges,其中一个方法与PropertyChangedEventHandler相匹配。然后我会定义另一个方面,将PropertyChanged事件连接到此处理程序。

此时,执行INotifyPropertyChangedINotifyOnChildChanges的任何类都会收到有关子属性更改的通知。

我喜欢这个想法,可能必须自己实现它。请注意,我还发现了很多情况下,我想在属性集外触发PropertyChanged(例如,如果属性实际上是一个计算值并且您更改了其中一个组件),那么将实际调用PropertyChanged基类可能是最优的。 I use a lambda based solution to ensure type safety,这似乎是一个非常普遍的想法。

+0

由于我长时间的评论不得不张贴为anwser。如果你发现时间我会最兴奋的实现你的想法... ofcors与您的协助:) – no9 2010-03-15 06:25:44

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方法(哪怕只有一个私人二传手)工作。如果该财产只有一个吸气剂,您将不会收到任何通知。请注意,这仍然只提供一个级别的通知(它不会通知您对层次结构中任何对象的任何更改)。您可以通过手动将OnChildPropertyChanged的每个实现脉冲为OnPropertyChanged(null)为属性名称,实际上让孩子的任何变化都被认为是父母的整体变化。但是,这可能会导致数据绑定效率低下,因为它可能会导致所有绑定属性被重新评估。

+0

对不起,但这不适用于1.5。我缺少ImportMember和MethodPointCut属性:S – no9 2010-03-15 06:22:26

+0

我也在PostSharp 2.0上试过这个(但我的主要目标是在1.5上做这个)。然而即使在2.0版本上,我也没有取得任何成功。父对象上的事件永远不会启动。 – no9 2010-03-15 07:55:25

+0

我已在2.0中对其进行验证。我的IMainViewModel暴露了一个属性WindowTitle和实现INotifyPropertyChanged的基础类。在我的WiringListViewModel被实例化后,我设置了WindowTitle的值,我可以看到Debug文本打印,指示OnChildPropertyChanged已被MainViewModel调用。 – 2010-03-15 14:34:36

相关问题