Search code examples
wpfmvvm-lightreactive-programmingweakeventmanager

IWeakEventListener.ReceiveWeakEvent() called multiple times when PropertyChanged(null) is called on source object


I'm using the PropertyObserver class in my code to avoid doing string comparisons in PropertyChanged event handling and factor out the handling of null or string.Empty as its argument (Which indicates that all properties of an object has changed).

This class uses PropertyChangedEventManager to register callbacks in the target object and implements IWeakEventListener to respond every time PropertyChanged event is invoked on the source object.

But during the creation of a unit test I found out that the IWeakEventListener.ReceiveWeakEvent() is called N number of times, with N as the number of the registered callbacks. This only occurs when null or string.Empty is specified, not when a valid property name is given in the PropertyChanged event.

Does anyone knows why this is happening and how to fix it? My goal is to do a foreach of the registered handlers ONE time when null is given, so I can update my target object by getting all the properties of the source object. But when ReceiveWeakEvent() is called N times then the foreach will be repeated N times!

To illustrate it, the following is a simplified version of the PropertyObserver class and the source class (I'm using MVVM Light's ObservableObject for INotifyPropertyChanged implementation):

public class PropertyObserver : IWeakEventListener {
    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) {
        if (managerType == typeof(PropertyChangedEventManager)) {
            string propertyName = ((PropertyChangedEventArgs)e).PropertyName;

            if (string.IsNullOrEmpty(propertyName)) {
                Console.WriteLine ("Foreach registered handlers and invoke one by one");
            } else {
                Console.WriteLine ("Invoke handler for property {0}", propertyName);
            }
            return true;
        }
        return false;
    }
}

public class ViewModel : ObservableObject {
    private int mProp1;
    private int mProp2;

    public int Prop1 {
        get { return mProp1; }
        set {
            mProp1 = value;
            RaisePropertyChanged("Prop1");
        }
    }

    public int Prop2 {
        get { return mProp2; }
        set {
            mProp2 = value;
            RaisePropertyChanged("Prop2");
        }
    }

    public void RaiseAllPropertyChanged() {
        RaisePropertyChanged(null);
    }
}

And in a console app's Main we can call them like so:

var vm = new ViewModel();
var obs = new PropertyObserver();

// Normally this is done inside the PropertyObserver class.
PropertyChangedEventManager.AddListener(vm, obs, "Prop1");
PropertyChangedEventManager.AddListener(vm, obs, "Prop2");

vm.Prop1 = 1; // Results in a console line "Invoke handler for property Prop1"
vm.Prop2 = 2; // Results in a console line "Invoke handler for property Prop2"

// Results in two console lines: "Foreach registered handlers and invoke one by one", expected is only 1!
vm.RaiseAllPropertyChanged();

Solution

  • Okay, I didn't understand the AddListener() method before. I only need to register a listener once:

    PropertyChangedEventManager.AddListener(vm, obs, string.Empty);
    

    to listen to all PropertyChanged events of a source object. Doing this will produce the correct working of the PropertyObserver class:

    vm.Prop1 = 1;    // "Invoke handler for property Prop1"
    vm.Prop2 = 2;    // "Invoke handler for property Prop2"
    
    // Now results in one console line "Foreach registered handlers and invoke one by one"
    vm.RaisePropertyChanged();
    

    Each registered listener with non-empty third argument (the property name) will respond to only the specified property name and null or string.Empty. So that's why the foreach was invoked twice on the original code.