Introduction
If you’ve ever used .NET bindings to connect UI widgets to properties of objects in a WinForms project, this article will demonstrate how to design Delphi Prism classes that can use Cocoa bindings to achieve the same ease of form design when writing an application for OS X.
One of the nice features available to developers using the Cocoa frameworks, is the concept of Key-Value coding. The basic premise of this feature is to implement the Observer design pattern, allowing one object to respond to changes to the properties of another.
However, once implemented, another benefit becomes available; Cocoa bindings use the Key-Value pattern to support, amongst other things, the connection of UI controls to properties of business objects, allowing the UI control to be updated automatically when the property of the object changes and, also, the property of the object to be set when a new value is entered into the UI control.
Whereas, with simple Key-Value coding, a Controller can listen to changes on an object and then interpret those changes into messages to a UI control, bindings completely remove the need for any "glue code" in between the object being observed and the UI controls that are displaying the values of an object's properties.
This example uses the Monobjc bridge libraries, so you will need to ensure that these are installed before proceeding any further
Creating the test project
This example uses the Monobjc bridge libraries, so you will need to ensure that these are installed before proceeding any further.
Start by ensuring that your Windows VM is available, opening Delphi Prism and creating a new Monobjc application called KVOExample.
If you haven't installed Monobjc in a location the project can find on its own you may have to re-establish the References section of the project to point to your installed location for Monobjc.
A value-key coding compliant class
The first thing we need to do is to define the class for the object whose properties we wish to display on a form. So, add a new class to the project, called Customer.
namespace KVOExample.Interface.nib;
interface
uses
Monobjc.*;
type
[ObjectiveCClass("Customer")]
Customer = public class(NSObject)
private
fName: NSString;
protected
public
[ObjectiveCMessage("name")]
method getName: NSString;
[ObjectiveCMessage("setName:")]
method setName(value: NSString);
property Name: NSString
read getName
write setName;
end;
As you will see, it is necessary to do slightly more coding than for a standard Delphi Prism class if we want this class to be usable by the ObjectiveC runtime.
- The class has to derive from NSObject (at least).
- The class has to be marked with the ObjectiveCClass Attribute.
- Any property that is to be involved in Key-Value coding has to be of a type that ObjectiveC can recognise.
- Properties must be represented by two public methods: a getter and a setter.
- Property getter and setter methods can be named as desired but they must also be labelled with an ObjectiveCMessage attribute which follows the naming convention that ObjectiveC expects: "<propertyName>" and "set<propertyName>".
Of course, as far as ObjectiveC is concerned, you need not have a property declaration at all; that is included for ease of coding from other Delphi Prism classes.
method Customer.getName: NSString;
begin
result := fName;
end;
The getter method is just a simple return of the private field's value.
method Customer.setName(value: NSString);
begin
WillChangeValueForKey("name");
fName := value;
DidChangeValueForKey("name");
end;
However, the setter method has to contain calls to the two methods of NSObject that will, in turn, notify those objects that are registered as having an interest in changes to instances of this class.
Creating the editing form
Now we have created a business class, we can focus attention on creating a form on which we can edit an instance of that class.
On the OS X side of things, open Interface Builder, navigate to the Interface NIB file within the project directory and open it. You should see something like this :

Fig. 1: The new Nib file
If it is not already showing, double-clicking on the Window icon will open the main form, which should look like this :

Fig. 2: The new form
We shall not be using the toolbar, so that can be removed and one Button, one Text Field and two Labels can be added, to give us a form that looks like this :

Fig. 3: The completed form ControllerInspector
Now, switch to the ApplicationController icon in the NIB window and add the Actions and Outlets you see in this next image :

Fig. 4: Created Actions and Outlets
In order for these Actions and Outlets to appear in the Delphi Prism class declaration, you will need to connect the button to the setCutomerName action, the nameEdit outlet to the Text Field and the nameLabel outlet to the right hand of the two Labels on the form.
Once these changes are made, we need to save the NIB and that should complete our work in Interface Builder.
Completing the controller class
Changing back to Visual Studio, we now need to make our Delphi Prism project aware of the changes that have been made to the NIB which we have just edited.
In the Solution Explorer, expand the Interface.nib node until you can see the designable.nib node. Right-clicking on this node should display a menu which includes the entry "Run Custom Tool"; clicking on this item will update the designable.pas source file to include the changes we have just made to the NIB in Interface Builder.
In designable.pas, you should now see a class declaration that looks like this :
[ObjectiveCClass]
ApplicationController = public partial class(NSObject)
public
var [ObjectiveCField] nameEdit: NSTextField;
var [ObjectiveCField] nameLabel: NSTextField;
[ObjectiveCMessage('setCustomerName:')]
method setCustomerName(sender: NSObject);
partial; empty;
end;
Having updated the ApplicationController class to this stage, we can now switch to the ApplicationController.pas file, in the solution, and complete any code required to link up the form to an instance of our Customer class.
type
ApplicationController =
public partial class(Monobjc.Cocoa.NSObject)
private
fCustomer: Customer;
method AfterConstruction; partial;
protected
public
method AwakeFromNib; partial;
method setCustomerName(sender: NSObject); partial;
end;
As you can see, the setCustomerName method is now implemented in the separate ApplicationController.pas unit but, since we need the controller to hold an instance of our Customer class, we also need to add a private field and to implement the awakeFromNib method to hook up the controls to the Name property of our Customer object.
method ApplicationController.AfterConstruction;
begin
fCustomer := new Customer();
end;
We could use Cocoa Bindings to connect both the Text Field and the Label to the Customer but, since the Label only needs to observe changes to the Customer and not make them, we will only use KVO to update the Label.
method ApplicationController.AwakeFromNib;
begin
First, we will use Cocoa Binding to connect the Text Field :
nameEdit.BindToObjectWithKeyPathOptions(
"value", fCustomer, "name", nil);
But, for the Label, we need to make this ApplicationController the observer for the Name property of the Customer object :
fCustomer.AddObserverForKeyPathOptionsContext(
self, "name",
NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, nil);
end;
Implementing a KVO handler method
Having told the Customer to tell us when its Name property changes, we need to provide a method that KVO can call when the property changes. In ObjectiveC, the method signature would be observeValueForKeyPath:ofObject:change:context: which can be defined in Delphi Prism like this :
method ApplicationController.
observeValueForKeyPathOfObjectChangeContext(
keyPath: NSString; object: NSObject;
change: NSDictionary; context: NSObject);
begin
The "change" NSDictionary parameter contains one or more key-value pairs, depending on the type of change that has occurred in the object that is being observed; in this example, all we are interested in is whether the value of any property of the observed object has changed.
Cocoa defines a set of string constants as part of the NSKeyValueObserving framework
Cocoa defines a set of string constants as part of the NSKeyValueObserving framework; these are replicated, in Mono, as the NSKeyValueObserving static class which contains static fields for the constants. In this example, we are interested in retrieving the NSKeyValueObserving.NSKeyValueChangeKindKey entry in the change dictionary, in order to ascertain the kind of change that has occurred to the observed object.
The value held in the NSKeyValueObserving.NSKeyValueChangeKindKey entry in the dictionary is of the NSKeyValueChange enum type so, because direct casting of ObjectiveC types is not possible in Delphi Prism, we have to carefully convert the Id type of the value returned from the dictionary entry to the NSValueChange type.
var changeKindAsID: Id :=
change[NSKeyValueObserving.NSKeyValueChangeKindKey];
The Id type is, essentially, an untyped pointer so, first, we have to convert the value to an NSNumber using the CastTo<T> generic method that is a part of the Id class in Monobjc.
var changeKindAsNSNumber: NSNumber :=
changeKindAsID.CastTo<NSNumber>();
Next, because we want to compare this value to a member of an enum, we need to further convert this NSNumber value to the appropriate member of the NSKeyValueChange enum.
var changeKind: NSKeyValueChange :=
NSKeyValueChange(changeKindAsNSNumber.UnsignedIntValue);
There is no direct cast from an NSNumber to an enum so, instead, we have to cast the unsigned integer value of the NSNumber instance, obtained by using the UnsignedIntValue method of the NSNumber class. Finally, we can compare the value, obtained from the dictionary, with the NSKeyValueChange.NSKeyValueChangeSetting enum value that will tell us if this notification is the result of a change to the value of a property to which we want to react.
if changeKind = NSKeyValueChange.NSKeyValueChangeSetting then
begin
Having ascertained that this notification is for a value change, we can now determine which property has changed, simply by checking the value of the keyPath parameter.
if keyPath = "name" then
Now that we know that there was a value change to the "name" property of the object we are observing, we can obtain the new value by reading it from the NSKeyValueObserving.NSKeyValueChangeNewKey entry in the dictionary.
label.ObjectValue :=
change[NSKeyValueObserving.NSKeyValueChangeNewKey];
end;
end;
One last thing
Finally, we can respond to the button press by setting the Customer.Name to a fixed value:
method ApplicationController.setCustomerName(
sender:NSObject);
begin
fCustomer.Name := "CodeGear";
end;
Putting it to the test
Having created all the necessary code for this example, all that is left is to build the application in Visual Studio, find the KVOExample.app file produced and run it.
Clicking on the button will set the Customer Name to "CodeGear".
Editing the value in the Text Field should set the Name property on the Customer, thus triggering an update of the right-hand Label on the form.
Conclusion
Cocoa UI controls can be connected to properties of objects by, either, Cocoa Bindings or Key-Value Observing. This article demonstrated how to create an example project which used Cocoa Bindings to connect a Text Field, and KVO to connect a Label, to the same property of an object, prompting multiple updates to different controls from the same property of an object.