Re: Bindings - newbie question
Re: Bindings - newbie question
- Subject: Re: Bindings - newbie question
- From: "Oleg Krupnov" <email@hidden>
- Date: Tue, 2 Sep 2008 13:32:51 +0300
I have read the following thread:
http://www.cocoabuilder.com/archive/message/cocoa/2008/6/27/211362
(the useful part of it) and mostly figured out the matter myself and
coped to make my code work. I will now briefly set forth my
understanding and appreciate if some more experienced colleague proved
me right or wrong.
Conceptually, binding is an informal protocol
(NSKeyValueBindingCreation) that objects can implement to establish
one- or two-way connection between each other. The protocol itself
does not imply whether the connection is one-way or two-way.
There is a basic implementation of NSKeyValueBindingCreation in
NSObject that provides one-way connection between two objects, where
an object specified in the initial bind:toObject: message becomes the
observed object, and the object that receives the bind: message
becomes the observer. This default implementation can be re-used for
implementing (at least) simple bindings (the one-way part of it).
In fact, the simple one-way binding implemented in NSObject can be
(roughly) thought of as a thin-layer addition over the KVO mechanism,
i.e. the bind: message is kind of a way of sending addObserver:
message in reverse direction from the observer to the observed.
Also, bindings have names. This is the most confusing part for
newbies. An object may expose a set of bindings, which are not
equivalent to that object's properties, even though they may (or
rather should not) have identical names. It is why bindings of Cocoa
objects are documented separately from its properties and messages,
here: http://developer.apple.com/documentation/Cocoa/Reference/CocoaBindingsRef/CocoaBindingsRef.html
The binding names can be thought of as string keys to an internally
maintained dictionary of connections. Although it is physically
possible to bind to object property names instead of binding names, it
is a mistake.
(These binding names can also be exposed in IB by sending the
+exposeBinding message in +initialize etc. and implementing an IB
palette. Unless you implement the palette, there is unfortunately no
way to make the bindings appear in IB. Luckily, there is no need to
necessarily expose the bindings "formally", they will work by default.
You just need to establish them manually by sending the bind: message.
In a document-based application, this is done in the
windowControllerDidLoadNib message.)
In order to make use of the NSObject's default implementation of
binding, you need to have a getter and setter messages with the same
name as the binding name, inside the receiver of the bind: message,
otherwise you will get the "class is not KVC/KVO-compliant" error.
Note that these accessor methods should not necessarily be declared in
the interface, because they are called via KVC.
-(id)someBinding
{
return m_someBinding;
}
-(void)setSomeBinding:(id)value
{
m_someBinding = value;
// do what you need
// but don't fire the binding back
}
In order to make a binding bi-directional, the receiver of the bind:
message needs to implement some custom logic that queries the bindings
established on self and if they are available, sends the corresponding
message on it. Like this:
- (void)fireSomeBinding:(id)someValue
{
NSDictionary* bindingInfo = [self infoForBinding:SOME_BINDING_NAME];
if (bindingInfo != nil)
{
id target = [bindingInfo valueForKey:NSObservedObjectKey];
NSString* keyPath = [bindingInfo valueForKey:NSObservedKeyPathKey];
// ignore options
[target setValue:someValue forKeyPath:keyPath];
}
}
Note that this message should be only invoked from a message that
originates from an external source, such as a view, or user
mouse/keyboard event, but not incoming from the same binding's setter
message. Otherwise, you can end up with firing back the binding that
has just reported a change in the observed value.
Finally, many Cocoa widgets and controllers already implement
bi-directional bindings, so you only need to implement the above code
if you want to expose some bindings in a custom view or another kind
of custom object that implements some non-trivial connection to a
view. For example, I needed to use the above technique in a custom
toolbar "controller" (the MyController), because I wanted it to
observe the value of the MyDocument's currMode property and select the
corresponding button on the toolbar.
The answers to my other questions are:
> 1) Should I send the bind message to myDocument to observe
> myController as well? I used to think that bindings are two-way
> inherently, i.e. the object whose "bind" message is invoked, stores
> its observed object and when the observer's property changes, it
> updates the observed object automatically. Am I wrong?
No I should not.
> 2) Am I allowed to send [myController setCurrMode:] in order for the
> binding to fire, or am I obliged to always send [myController
> setValue:... forKey:@"currMode"] for this?
It does not matter. Both work.
On Tue, Sep 2, 2008 at 8:25 AM, Oleg Krupnov <email@hidden> wrote:
> I have two objects: a MyDocument (NSDocument subclass) and a custom
> MyController controller (NSObject subclass). In the MyDocument there
> is "currMode" property. The controller class also has the currMode
> property. I want these two properties to be bound to each other
> two-way, so that when either of the properties changes, the other
> updates respectively.
>
> What I do:
> From within MyDocument's windowControllerDidLoadNib message:
>
> [myController bind: @"currMode" toObject: self withKeyPath:@"currMode"
> options:nil];
>
> I do not override bind, unbind etc. of MyController relying on the
> default implementation of bindings in NSObject.
>
> Now when the myController's currMode properties changes by its view,
> nothing happens in MyDocument. It's setCurrMode is not sent.
>
> What am I doing wrong?
>
> I have two accompanying questions:
> 1) Should I send the bind message to myDocument to observe
> myController as well? I used to think that bindings are two-way
> inherently, i.e. the object whose "bind" message is invoked, stores
> its observed object and when the observer's property changes, it
> updates the observed object automatically. Am I wrong?
>
> 2) Am I allowed to send [myController setCurrMode:] in order for the
> binding to fire, or am I obliged to always send [myController
> setValue:... forKey:@"currMode"] for this?
>
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden