2010-03-12 72 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

相關問題