3

Im new to .NET and WPF so i hope i will ask the question correctly. I am using INotifyPropertyChanged implemented using PostSharp 1.5:

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

}

Then i have a couple of classes (user and adress) that implement [NotifyPropertyChanged]. It works fine. But what i want would be that if the child object changes (in my example address) that the parent object gets notified (in my case user). Would it be possible to expand this code so it automaticly creates listeners on parent objects that listen for changes in its child objets?

no9
  • 125
  • 1
  • 2
  • 7
  • What would you like the child listener to do? – Dan Bryant Mar 14 '10 at 15:57
  • Actualy at this point all i want is that Parent gets notified (on any change on a child - any depth). – no9 Mar 15 '10 at 06:28
  • That's a much more challenging problem. You'd have to use some sort of reflection (if you can't rely on your children to notify you of changes about their children) and it's always a bit dicey deciding how and when to recurse during reflection. What is the motivating problem that's leading you to this solution? There may be design changes that could help simplify your task. – Dan Bryant Mar 15 '10 at 14:37
  • On a side note, it would be good if this post could be tagged PostSharp. The creator of the tool reads these boards and would be able to answer your question about v1.5 better than me. – Dan Bryant Mar 15 '10 at 14:38
  • i did tag this with PostSharp tag and i also posted on PostSharp forum ... now im waiting for an anwser.... getting this to work would be a major thing. Loosing all the NPC coding and keeping the classes clean is my dream come true ! – no9 Mar 17 '10 at 08:08

2 Answers2

3

I'm not sure if this works in v1.5, but this works in 2.0. I've done only basic testing (it fires the method correctly), so use at your own risk.

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

Usage is like this:

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

This will apply to all child properties of the class that implement INotifyPropertyChanged. If you want to be more selective, you could add another simple Attribute (such as [InterestingChild]) and use the presence of that attribute in the MethodPointcut.


I discovered a bug in the above. The SelectProperties method should be changed to:

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

Previously, it would only work when the property had a setter (even if only a private setter). If the property only had a getter, you would not get any notification. Note that this still only provides a single level of notification (it won't notify you of any change of any object in the hierarchy.) You could accomplish something like this by manually having each implementation of OnChildPropertyChanged pulse an OnPropertyChanged with (null) for the property name, effectively letting any change in a child be considered an overall change in the parent. This could create a lot of inefficiency with data binding, however, as it may cause all bound properties to be reevaluated.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • sorry, but this does not work in 1.5. Im missing ImportMember and MethodPointCut attribute :S – no9 Mar 15 '10 at 06:22
  • i have also tried this on PostSharp 2.0 (but still my main goal is to do this on 1.5). Yet i did not have any success even on 2.0 with it. The event on parent object never fires up. – no9 Mar 15 '10 at 07:55
  • I have verified it in 2.0. My IMainViewModel exposed a property WindowTitle and the underlying class implemented INotifyPropertyChanged. I set the value of WindowTitle after my WiringListViewModel has been instantiated and I could see the Debug text print indicating that the OnChildPropertyChanged had been called with MainViewModel. – Dan Bryant Mar 15 '10 at 14:34
  • I cant find the problem. I created a simple test, yet OnChildPropertyChanged never gets called :S ... if you are interested i could send you my test project so you can help me find an error – no9 Mar 17 '10 at 07:30
  • Please do send me your test project at dan.bryant@posincorp.com. I'm using this aspect in one of my projects now, so it would be good to see if there is a failure case I haven't discovered. I agree, using aspects really does help with keeping things clean; now I can have all the magic of WPF binding without the tedious (and error-prone) overhead of manually implementing the Notify pattern. – Dan Bryant Mar 17 '10 at 13:41
1

The way I would approach this would be to implement another interface, something like INotifyOnChildChanges, with a single method on it that matches the PropertyChangedEventHandler. I would then define another Aspect that wires up the PropertyChanged event to this handler.

At this point, any class that implemented both INotifyPropertyChanged and INotifyOnChildChanges would get notified of child property changes.

I like this idea and may have to implement it myself. Note that I have also found a fair number of circumstances where I want to fire PropertyChanged outside of a property set (e.g. if the property is actually a calculated value and you have changed one of the components), so wrapping the actual call to PropertyChanged into a base class is probably optimal. I use a lambda based solution to ensure type safety, which seems to be a pretty common idea.

Ben Von Handorf
  • 2,326
  • 1
  • 15
  • 17
  • Due to my long comment had to post it as an anwser. If you find the time i would be most excited to implement your idea ... ofcors with your assistance :) – no9 Mar 15 '10 at 06:25