2

.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] を実装するクラス (ユーザーとアドレス) がいくつかあります。それは正常に動作します。しかし、私が望むのは、子オブジェクトが(私の例のアドレスで)変更された場合、親オブジェクトが通知されることです(私の場合はユーザー)。このコードを拡張して、子オブジェクトの変更をリッスンする親オブジェクトにリスナーを自動的に作成することは可能でしょうか?

4

2 に答える 2

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

以前は、プロパティにセッターがある場合にのみ機能していました (プライベート セッターのみであっても)。プロパティにゲッターしかない場合、通知はありません。これはまだ単一レベルの通知のみを提供することに注意してください (階層内のオブジェクトの変更は通知されません)。OnChildPropertyChanged の各実装に手動で (null) の OnPropertyChanged をパルスさせることで、このようなことを実現できます。これにより、事実上、子の変更が親の全体的な変更と見なされます。ただし、これにより、バインドされたすべてのプロパティが再評価される可能性があるため、データ バインディングで多くの非効率性が生じる可能性があります。

于 2010-03-14T16:44:10.600 に答える
1

私がこれにアプローチする方法は、 のような別のインターフェースを実装しINotifyOnChildChangesPropertyChangedEventHandler. PropertyChanged次に、イベントをこのハンドラーに 結び付ける別のアスペクトを定義します。

この時点で、両方を実装し、子プロパティの変更が通知されるすべてのINotifyPropertyChangedクラスINotifyOnChildChanges

私はこのアイデアが好きで、自分で実装する必要があるかもしれません。プロパティ セットの外部で起動したい状況がかなりの数あることに注意してくださいPropertyChanged(たとえば、プロパティが実際に計算された値であり、コンポーネントの 1 つを変更した場合)。そのため、実際の呼び出しをPropertyChangedベースにラップします。クラスはおそらく最適です。 タイプセーフを確保するためにラムダベースのソリューションを使用していますが、これはかなり一般的なアイデアのようです。

于 2010-03-12T15:07:11.247 に答える