Using Key-Value Coding with MonoMac

Key-Value Coding is a set of practices that allow applications to access object properties using strings. This is similar to Binding Expressions in Silverlight. In both cases the purpose is to allow tooling that does not directly have access to your native code to access properties from your program.

You would typically use this from a tool like Interface Builder, where you can use the Binding Inspector (⌘4 from Interface Builder) to explore the possible values of the component that you can data bind. In Interface Builder you would use string to describe the name of the property that you want to access. For example, consider the options available for the NSPopUpButton component show on the right. All of those properties of the NSPopUpButton can be pulled directly from your code from Interface Builder without having to manually hook things up.

To use this with MonoMac, all you have to do is make sure that any property that you want to access from Interface Builder is flagged with the [Export] attribute. If you apply the Export attribute without any parameters, it will expose the C# name of your property for Key-Value access. You can change the name that is exposed by passing a parameter, for example: [Export ("myIndex")].

The QTRecorder sample shows how various elements in the UI are connected to the code: [caption id="attachment_38" align="alignnone" width="583" caption="MonoMac port of QTRecorder sample"][/caption]

In that sample, the class QTRecorder, a class derived from NSDocument exposes properties that are hooked up directly to the Enabled properties in various buttons in the UI and properties to populate and control the contents of the various popups in the application. All of these bindings are configured by setting the target of the binding to be the "File's Owner", in this case the QTRecorder class, as it happens to load the QTRecorder.nib file. You can see the complete list of bindings in the screenshot on the left.

Let us explore what happens in the application. The Video Devices popup is controlled through three bindings: the Content, the Content Values and the Selected Object. This is what the binding are configured to in Interface Builder for all three properties:

The Content is bound to the VideoDevices property in the source code, this is a property that merely returns the strongly typed QTCaptureDevice array of the available devices. The Content Values is bound to the VideoDevices.localizedDisplayName. The first part of the key matches our VideDevices array, and the second part of the key happens to be the Objective-C name of a property exposed by the QTCaptureDevice that provides the name of the device to display. The actual item selected on the screen is controlled by the Selected Object binding. In this case, a new property, the SelectedVideoDevice property. At this point, the NSPopUpButton will populate its contents using these bindings. Additionally, when the user selects a different item, the property bound to the Selected Object will be invoked with the new value. Our code responds by configuring the QTCaptureSession accordingly.

Another interesting binding takes place with the recording button. The recording button hasits Enabled property bound to the HasRecordingDevice C# property and has its Value bound to the C# Recording property. When we change the video source in the popup, the button responds by either getting grayed out or becoming active. Although this could have been done programmatically in response to the new value set in the SelectedVideoDevice property, the code takes advantage of the built-in notification system and instead reacts to changes to the SelectedVideoDevice automatically.

This is done by using the dependency keys feature a feature of Key-Value Coding. It requires that the code exports a specially-named method which is prefixed with the string "keyPathsForValuesAffecting". These methods are are meant to return an NSSet containing the names of the properties that depend on that particular property. In our case this is:

[Export]
public static NSSet keyPathsForValuesAffectingHasRecordingDevice ()
{
    return new NSSet ("SelectedVideoDevice", "SelectedAudioDevice");
}

Once that is done, the runtime knows that when either one of the SelectedVideoDevice or SelectedAudioDevice change, it has to query the value for HasRecordingDevice again.

Posted on 07 Dec 2010 by Miguel de Icaza
This is a personal web page. Things said here do not represent the position of my employer.