The goal of this series of posts is to introduce Key-Value Coding (KVC), Key-Value Observing (KVO) and Cocoa Bindings for MonoMac. As it stands right now there will be four parts planned as follows:

  1. Part 1 – Key-Value Coding
  2. Part 2 – Key-Value Observing
  3. Part 3 – Cocoa-Bindings
  4. Part 4 – Practical Example drawing everything together

Update: January 9th, 2011 – I totally forgot that this will not work unless you are using the MonoMac add-in compiled from source due to a bug fix last week. To compile from source view Exploring Cocoa with MonoMac and C#Chapter 1 Part 2 specifically.

There is a new add-in slotted to be released tomorrow.

The title of the post is a little corny with the “Oh My!” added but for those coming from Windows with no experience with Cocoa will probably feel like they are “not in Kansas any more”. Those of you with Cocoa experience will be right at home with the concepts but will find the implementation within MonoMac and C# a little different. Don’t let that scare you off though as it really is quite simple. 

Hopefully these introductory articles will bring both camps together.

So what is Key-Value Coding?

Very simply put Key-Value Coding (from here on out referred to as KVC) is Cocoa’s way of accessing an NSObject’s properties without accessing the object’s properties directly.

“And so what does that mean then” you ask? Glad you asked and here is a couple of simple examples to explain.

It means that if you have an object, let’s say a Movie, and the Movie object has three properties, Title, Producer and Year you can access the values of those properties without actually invoking the property name itself.

using System;
using System.Collections.Generic;
 
namespace KVC
{
 
	public partial class Movie
	{
		public Movie () {}
 
		public string Title { get; set; }
 
		public string Producer { get; set; }
 
		public int Year { get; set; }
 
	}
}

Normally to access the title value of a Movie object you would directly access the value using the property Title of the object as follows:

Movie movie = new Movie();
movie.Title = "Shrek - Forever After";  // to assign the value
var title = movie.Title;  // to read the property value

 

Using KVC you would pass the string value of the property to the following NSObject’s methods:

  • SetValueForKey (NSObject value, NSString key) to set the property value
  • ValueForKey(NSString key) to read the property value
Movie movie = new Movie();
movie.SetValueForKey((NSString)"Shrek - Forever After",(NSString)"Title");;  // to assign the value
var title = movie.ValueForKey((NSString)"Title");  // to read the property value

 

That is down right ugly right? Well no matter whether it is ugly or not, what matters is the understanding of the concept because it will come into play later with the observing and bindings parts later.

.Net and Reflection

Those familiar with .Net are saying “Hey that is nothing more than reflection”. Well they are absolutely correct. The only difference is that it is built directly into each NSObject.

Using reflection in .Net would be implemented as follows:

Movie movie = new Movie();
 
Type sourceType =movie.GetType();
PropertyInfo info = sourceType.GetProperty("Title");
 
info.SetValue(movie, "Shrek - Forever After" , null);  // to assign the value
var title = info.GetValue(this,null));  // to read the property value

 

It is a little longer coding and the code is just as ugly.

Developers coming from .Net should be pretty comfortable with this so far but here is where it starts getting into the fuzziness of the MonoMac to Cocoa bindings.

Completing the KVC Contract with [Export()]

To make this work for Cocoa the class needs to be Key-Value Coding Compliant,  You do this by decorating the properties that are to participate in KVC for the NSObject with an [Export("xxxxx")] attribute where xxxx is the name of the key that is defined with Cocoa.

Here is the Movie class in all it’s KVC’d glory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;
 
using MonoMac.Foundation;
 
namespace KVC
{
 
	public partial class Movie : NSObject
	{
		public Movie () {}
 
		[Export("title")]
		public string Title { get; set; }
 
		[Export("producer")]
		public string Producer { get; set; }
 
		[Export("year")]
		public int Year { get; set; }
 
	}
}

Notice on line 4 we make reference to the MonoMac.Foundation namespace. This allows us access to Cocoa.

On line 9 we are extending NSObject and this you must do to be able to use KVC in Cocoa.

The lines numbered 13, 16, and 19 are the magic behind the MonoMac to Cocoa bindings. The Export attributes automatically complete the KVC contract with Cocoa behind the scenes for the object.

Practical Example

To follow along with this section download the following program:

Hello Cocoa Start

Once you have it downloaded you can open it by double clicking the HelloCocoaKVC.sln file.  This should bring up MonoDevelop with the solution loaded.

You should see something like the following:

Showing solution

Figure 1.1 Showing solution

Double click on the SayHello.cs file to open it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
 
using MonoMac.Foundation;
 
namespace HelloCocoaKVC
{
	public class SayHello : NSObject
	{
		const string hello = "Hello";
 
		public SayHello ()
		{
			Name = "Cocoa";
		}
 
		public string Name { get; set; }
 
		public string Greeting 
		{
			get 
			{ 
				return String.Format("{0} {1}", hello, Name);
			}
		}
	}
}

 

As previously discussed you will notice that line 3 references the MonoMac.Foundation name space.

Notice on line 7 that we extend NSObject.  This is obligatory if you want the class to participate within Cocoa with KVC.

Build and run the solution.  You will be presented with the following screen:

Showing example screen

Figure 1.2 Showing example screen

Nothing happens yet because we have not added any code behind the button.  So close the program and let’s do that.

Open the MainWindowController.cs file by double clicking it.  Look for the setValueAction method and add the code below:

sayHello.SetValueForKey((NSString)valueField.StringValue, (NSString)"name");

This line says on the sayHello class set the property value “name” to the string value of the TextField valueField.

So that it looks like this:

54
55
56
57
58
		partial void setValueAction (NSButton sender)
		{
			// Example code goes here
			sayHello.SetValueForKey((NSString)valueField.StringValue, (NSString)"name");
		}

Run the program again, type a value in the Value: field and hit the Set Value button.

You should find the following message in the Application Output window in MonoDevelop complaining about the class not being key value coding compliant:

2011-01-08 09:55:03.413 HelloCocoaKVC[3289:613] [<HelloCocoaKVC.SayHello 0x570fa00> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.

Well if you look back at the SayHello.cs class you will find that we did not tell the class that it was.  Remember from the previous section Completing the KVC Contract with [Export()] you need to tell Cocoa that the “name” property should be included in KVC.

So let’s add the Export attribute now.

16
17
		[Export("name")]
		public string Name { get; set; }
  • Note that we used the key value of “name” here but it does not have to coincide with the name of the property.  We could also have used another value like “greetingName” as long as in the SetValueForKey we also pass “greetingName”.

Now run the program again, type a value in the Value: field and hit the Set Value button.  You should find that the class is now Key-Value Compliant for the Name property because there is no message.

Wrapping up

Sure there is no message but did the value really change.  Let’s find out.

Add the following line to the SetValueAction method:

helloLabel.StringValue = sayHello.Greeting;

and the following lines to the getValueAction method:

			var name = sayHello.ValueForKey((NSString)"name");
			resultField.SetValueForKey(name,(NSString)"StringValue");

It should look like the following:

54
55
56
57
58
59
60
61
62
63
64
65
66
67
		partial void setValueAction (NSButton sender)
		{
			// Example code goes here
			sayHello.SetValueForKey((NSString)valueField.StringValue, (NSString)"name");
			helloLabel.StringValue = sayHello.Greeting;
 
		}
 
		partial void getValueAction (NSButton sender)
		{
			// Example code goes here
			var name = sayHello.ValueForKey((NSString)"name");
			resultField.SetValueForKey(name,(NSString)"StringValue");
		}

Line 58 modifies the helloLabel string value to the value of the sayHello objects Greeting property which is “Hello” plus whatever you typed in.

Line 65 creates a variable “name” and assigns its value to the value obtained from the sayHello class using ValueForKey method. It passes “name” as the key.

Line 66 modifies the resultField to the value of the variable name taken from the sayHello object via the ValueForKey method. Notice that we did not use the StringValue property of the resultField but instead used the key “StringValue”  via a call to SetValueForKey . 

Cocoa controls and objects come Key-Value Compliant by default so the same rules apply to them.

Run the program again, type a value in the Value: field, press the Set Value button and then the Get Value button.  You should see the following but of course with the value that you entered.

Showing completed example screen

Figure 1.3 Showing completed example screen

Summary

That should wrap up the first part of this introductory series.

Let’s go over what we learned:

  • What is Key-Value Coding
  • What makes a class Key-Value Coding Compliant
  • How to make a class Key-Value Coding Compliant in MonoMac
  • How to read and write values indirectly to KVC Compliant classes with SetValueForKey and ValueForKey

I keep mentioning that this introductory and it is.  There are more advanced features to these concepts than we are learning but first we have to learn to walk before we run.

Hopefully the time I spent writing this has helped someone out there.

If you want to see more code you can look at the samples provided by the MonoMac team here Samples.

Specifically the following:

  • NSTableViewBinding
  • PopupBindings
  • SkinnableApp – This sample, submitted by Maxi Combina, shows how to call C# code from Javascript where SetValueForKey is used in the communication of the objects via scripting.

To learn how to obtain the samples please check out the series I have started on Exploring Cocoa with MonoMac and C#Chapter 1 Part 2 specifically.

For extra practice

Modify the SayHello.cs class as follows:

  • To be Key-Value Coding Compliant for the Greeting property.
  • Modify the controller source to obtain the the Greeting property indirectly using KVC instead of using the property directly.

This will get you ready for the second part of this series on Key-Value Observing.