2010-03-12 2 views
2

Im new to .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] 구현하는 클래스 (사용자 및 ADRESS)의 몇 가지있다. 잘 작동합니다. 하지만 내가 원하는 것은 자식 개체가 (내 예제 ​​주소에서) 부모 개체가 알림을 받았다면 (내 경우 사용자)입니다. 이 코드를 확장하여 자식 객체의 변경 사항을 수신하는 부모 객체의 리스너를 자동으로 만들 수 있습니까?

+0

아동 청취자는 무엇을하고 싶습니까? –

+0

사실이 시점에서 내가 원하는 것은 학부모가 통지를 받는다는 것입니다 (아동의 변화에 ​​따라 - 깊이가 있음). – no9

+0

그것은 훨씬 더 어려운 문제입니다.일종의 반성을해야 할 것입니다. 자녀에게 자녀의 변화를 알리기 위해 의존 할 수 없다면, 반성하는 동안 언제, 어떻게 언제 재귀 할 것인지를 결정할 때가 있습니다. 이 솔루션에 당신을 이끄는 동기 부여 문제는 무엇입니까? 작업을 단순화하는 데 도움이 될 수있는 디자인 변경이있을 수 있습니다. –

답변

1

이 방법은 INotifyOnChildChanges 같은 다른 인터페이스를 구현하는 것이고 PropertyChangedEventHandler과 일치하는 메서드가 하나 있습니다. 그 다음이 핸들러에 PropertyChanged 이벤트를 연결하는 또 다른 Aspect를 정의 할 것입니다.

이 시점에서 INotifyPropertyChanged과를 모두 구현 한 클래스는 하위 속성 변경 사항을 알게됩니다.

저는이 아이디어가 마음에 들며 직접 구현해야 할 수도 있습니다. 속성 번호가 아닌 PropertyChanged (예 : 속성이 실제로 계산 된 값이고 구성 요소 중 하나를 변경 한 경우)의 외부에서 PropertyChanged으로 실제 전화를 래핑하려는 상황의 상당수를 발견했습니다. 아마도 기본 클래스가 최적 일 것입니다. I use a lambda based solution to ensure type safety 꽤 일반적인 아이디어 인 것 같습니다.

+0

내 긴 코멘트로 인해 그것을 anwser로 게시해야했습니다. 시간을 내면 아이디어를 구현하는 데 가장 흥분됩니다. :) – no9

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; 
     } 
    } 
} 

사용법은 다음과 같이이다 : 이것은에서 INotifyPropertyChanged를 구현하는 클래스의 모든 자식 속성에 적용됩니다

[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!"); 
     } 
    } 
} 

. 좀 더 선택적으로하고 싶다면 ([InterestingChild]와 같은) 다른 간단한 Attribute를 추가하고 MethodPointcut에서 그 속성의 존재를 사용할 수 있습니다.


위의 버그가 발견되었습니다. 속성이 세터 (경우에도 단지 개인 세터)를했을 때 이전

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; 
    } 

, 그것은 단지 일하는 것이 다음 SelectProperties 방법에 변경해야합니다. 속성에 getter 만있는 경우 알림이 표시되지 않습니다. 이것은 여전히 ​​하나의 수준의 알림 만 제공한다는 점에 유의하십시오 (계층 구조의 개체 변경에 대해서는 사용자에게 알리지 않습니다). OnChildPropertyChanged 펄스의 각 구현을 수동으로 OnPropertyChanged (null)로 설정하면 다음과 같은 작업을 수행 할 수 있습니다. 속성 이름은 효과적으로 자식의 변경을 부모의 전반적인 변경으로 간주하도록합니다. 그러나 바인딩 된 모든 속성이 다시 평가 될 수 있으므로 데이터 바인딩을 사용하면 많은 비효율을 초래할 수 있습니다.

+0

죄송하지만, 1.5에서는 작동하지 않습니다. 임 누락 된 ImportMember 및 MethodPointCut 특성 : S – no9

+0

나는 또한 이것을 PostCharp 2.0에서 시도했다. (그러나 아직도 나의 주요 목표는 1.5에서 이것을하는 것이다.) 그러나 2.0에서는 성공하지 못했습니다. 부모 개체의 이벤트가 실행되지 않습니다. – no9

+0

2.0에서 확인했습니다. 내 IMainViewModel은 WindowTitle 속성을 노출하고 기본 클래스는 INotifyPropertyChanged를 구현했습니다. WiringListViewModel이 인스턴스화 된 후 WindowTitle의 값을 설정하고 OnChildPropertyChanged가 MainViewModel과 함께 호출되었음을 나타내는 디버그 텍스트 인쇄를 볼 수 있습니다. –