Question

I've been doing a refactor of an existing application, and I am attempting to use an Attribute on a property to trigger the NotifyPropertyChanged event using Unity interception. I got to the point where the event was firing, but the controls were not updating.

I wasn't sure if it was invoking the event correctly, so on my ViewModelBase I created a DispatchPropertyChanged method that invokes the property changed event. This method works to launch property changed when called directly from a view model, but when called from a ViewModel retrieved through reflection inside the intercept handler, it doesn't work.

I've inserted a link to the https://www.dropbox.com/s/9qg2n0gd2n62elc/WPFUnityTest.zip. If you open this solution and run the application, then click the "Reset" button, you'll see that the "Normal" text box updates, but the "Unity" text box does not update.

If you place a breakpoint at line 65 of the MainWindowViewModel and line 53 of the NotifyPropertyChangedHandler you'll see that the handler is working, the dispatch method is being called, and the event is being invoked. However, only the "Normal" one updates.

Any help on why the "Unity" text box isn't updating would be wonderful, thanks!

Amanda

EDIT:

Sorry for not including this originally, I really had no idea where the problem was. This was the original code for the interception behavior that was correct below:

public class NotifyPropertyChangedHandler :  IInterceptionBehavior
{
    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
    {
        var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
        if (npcAttribute != null)
        {
            if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
                shouldRaiseEvent(input))
            {
                raiseEvent(input);
            }
        }
        return getNext()(input, getNext);
    }

    public IEnumerable<Type> GetRequiredInterfaces()
    {
        return new[] { typeof(INotifyPropertyChanged) };
    }

    public bool WillExecute { get { return true; } }

    private object getNotifyPropertyChangedAttributeForSetter(IMethodInvocation input)
    {
        if (!input.MethodBase.Name.StartsWith("set_"))
        {
            return null;
        }
        return input.MethodBase.ReflectedType.GetProperty(input.MethodBase.Name.Substring(4))
                    .GetCustomAttributes(true).SingleOrDefault(attr => attr.GetType() == typeof (NotifyPropertyChangedAttribute));
    }


    private void raiseEvent(IMethodInvocation input)
    {
        raiseEvent(input, new PropertyChangedEventArgs(input.MethodBase.Name.Substring(4)));
    }

    private void raiseEvent(IMethodInvocation input, PropertyChangedEventArgs eventArgs)
    {
        var viewModel = input.Target as ViewModelBase;
        if (viewModel != null)
        {
            viewModel.DispatchPropertyChangedEvent(eventArgs.PropertyName);
        }
    }


    private static bool shouldRaiseEvent(IMethodInvocation input)
    {
        var methodBase = input.MethodBase;
        if (!methodBase.IsSpecialName || !methodBase.Name.StartsWith("set_"))
        {
            return false;
        }

        var propertyName = methodBase.Name.Substring(4);
        var property = methodBase.ReflectedType.GetProperty(propertyName);
        var getMethod = property.GetGetMethod();
        if (getMethod == null)
        {
            return false;
        }
        var oldValue = getMethod.Invoke(input.Target, null);
        var value = input.Arguments[0];

        return (value == null) ? oldValue !=null : 
               !value.Equals(oldValue);
    }
}
Was it helpful?

Solution

There is a problem in your NotifyPropertyChangedHandler.Invoke method code:

Your Code

I have added some comments to describe what the problem is with this code.

public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
    var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
    if (npcAttribute != null)
    {
        if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
            shouldRaiseEvent(input))
        {
            // You are raising property changed event here, 
            // however the value of the property is not changed until getNext()(input, getNext) called
            // So, WPF will re-read the same "old" value and you don't see anything updated on the screen
            raiseEvent(input);
        }
    }

    // Property value is updated here!!!
    return getNext()(input, getNext);
}

Change that code to something like:

public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
    var temp = false;
    var npcAttribute = getNotifyPropertyChangedAttributeForSetter(input) as NotifyPropertyChangedAttribute;
    if (npcAttribute != null)
    {
        if (npcAttribute.TimingOption == PropertyChangedTiming.Always||
            shouldRaiseEvent(input))
        {
            temp = true;
        }
    }
    var returnValue = getNext()(input, getNext); // Changing the value here
    if (temp) raiseEvent(input); // Raising property changed event, if necessary
    return returnValue;
}

I have tested this code and it works. Hope this helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top