1

I am trying to implement KVO bindings in a Xamarin Mac desktop app. I have followed the docs, and it is working, but the bindings appear to trigger 2 change events each time!

If I create a KVO model with a binding like this...

    private int _MyVal;
    [Export("MyVal")]
    public int MyVal 
    { 
        get { return _MyVal; }
        set 
        { 
            WillChangeValue("MyVal"); 
            this._MyVal = value; 
            DidChangeValue("MyVal"); 
        }
    }

And bind a control to it in Xcode under the bindings section with the path self.SettingsModel.MyValue

It all appears to work fine, the control shows the model value, changing the model value programmatically updates the control and changing the control updates the model value.

However, it runs the change event twice.

I am listening to the change so I can then hit an API with the value.

SettingsModel.AddObserver(this, (NSString)key, NSKeyValueObservingOptions.New, this.Handle);

Then later...

 public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
    {
        switch (keyPath)
        {
            case "MyValue":

            // CODE HERE THAT UPDATES AN API WITH THE VALUE                 
            // But this handler fires twice.


            break;
        }
    }

Im not sure if its Xamarin or XCode that is causing the double trigger.

Interestingly, if you don't specify the Xcode WillChangeValue and DidChangeValue methods, then it doesn't trigger twice - as though Xamarin has automatically triggered the change once. However, it no longer triggers a change when programmatically updating the model value...

[Export("MyVal")]
public int MyVal { get; set }

The above will work for the Xcode controls, they will update the model and trigger a change event.

But programmatically updating it

this.SettingsModel.MyVal = 1;

Does not trigger the change event.

It's very confusing, any idea on how to stop 2 change events firing, as I don't want to hit the API twice every time!

When it fires twice, the stack trace (abridged) for the first has...

MainViewController.ObserveValue
ObjCRuntime.Messaging.void_objc_msgSendSuper_IntPtr()
Foundation.NSObject.DidChangeValue(string forKey)
CameraSettingsModel.set_MyValue(int value)
AppKit.NSApplication.NSApplicationMain() 
AppKit.NSApplication.Main(string[] args) 
MainClass.Main(string[] args) 

Which looks fine, but the second...

MainViewController.ObserveValue
AppKit.NSApplication.NSApplicationMain()
AppKit.NSApplication.Main(string[] args) 
MainClass.Main(string[] args)

Has no mention of the Setting Model triggering the event

Matt Bryson
  • 2,286
  • 2
  • 22
  • 42

1 Answers1

0

You are hitting this - Receiving 2 KVO notifications for a single KVC change

and need to override AutomaticallyNotifiesObserversForKey it appears.

Cocoa is "doing you a favor" by sending the notifications for you, which is great except you have the managed version also sending notifications.

It looks something like this:

    [Export ("automaticallyNotifiesObserversForKey:")]
    public static new bool AutomaticallyNotifiesObserversForKey (string key) => false;

    bool _checkValue;

    [Export("CheckValue")]
    public bool CheckValue
    {
        get { return _checkValue; }
        set
        {
            WillChangeValue("CheckValue");
            _checkValue = value;
            DidChangeValue("CheckValue");
        }
    }

    public override void ViewDidLoad ()
    {
        base.ViewDidLoad();

        this.AddObserver("CheckValue", NSKeyValueObservingOptions.New, o =>
        {
            Console.WriteLine($"Observer triggered for {o}");
        });

        CheckValue = false;

}

Chris Hamons
  • 1,510
  • 11
  • 22