We often need to keep synchronized properties of several objects. One place where it is repeatedly encountered is GUI programming: we may need to keep synchronized a check box and a menu item, a control and UserPreferences
object, model and view in ModelViewController
etc. It is also encountered in DiscreteModelling?
, electronics, etc.
The problem can be formally expressed as a set of EqualityConstraint?
Attempts to solve this problem naively very
often result in infinite loops and other errors which sometimes are hard to debug.
The proper way to solve this problem is not the one described here. It's to have an Observable object of the Model (e.g. a UserPreferences
object) and several Observer View objects linked to it (e.g. a CheckBox
and a "toggle" MenuItem?
These View objects should be designed to alter the value of the Model object _when the user acts on them_ (not _whenever_ their internal value changes). The trick is you should not be acting on your GUI objects as if you were the user, but rather on your Model's objects.
A clean way for your View objects to be able to update the value in the Model is to accept a "method" (FunctionObject
| Closure | FunctionPointer
or wathever name pleases your heart the most) for doing it. This way you just plug in them a setter for your Model's object, and they remain decoupled from the Model.
(Well, if you cannot register event handlers dinamically for you library GUI objects, the best approach is not
to write down a static event handler for every single GUI object. You should write a wrapper class for each of your GUI-classes. This class would have a pointer/reference to a function object, and a handler for the GUI-class event which simply delegates on the function object. Those wrapper classes can bring you a lot of benefits: see InterfacePattern?
All this remembers me of VB. VB's widgets used to have the "bad habbit"? (I never tried VB.NET, so I don't know anymore) of raising "onchange" events both when the user changed their status and when _you_ did it from the code. And you had no way to distinguish both cases. So relying on "onchange" to revert unwanted changes (for example you might want a textbox to contain only positive numbers, and disallow the user to input 0 or letters on it) was double-expensive at runtime: You handled onchange; checked for correctness; corrected the value and BANG! onchange raised again, so you handled it, checked for correctness again, returned from nested handler and finally returned from original handler.
The solution was obviously not to depend on "onchange", which would trigger _whenever_ the GUI object was altered, but rather on "onclick", "onkeypress", etc, which would raise _when the user acted on it_.
Objects having some properties (otherwise called DefinedAttribute
s) which are read by LoadAccessor
s and set by the StoreAccessor
s. (Note that not necessarily all properties need to have both LoadAccessor
, some may be read-only or write-only.)
Some mechanism for notification of properties' value changes is needed (see also ObserverPattern
), this is often called value change notification signal or notification event
Binding Two Properties
Bindings can be classified as one-way and two-way. One-way bindings should be applied when one of the properties is read-only (or read-write but is never set in this particular application.) Otherwise two-way bindings must be applied.
Two-way binding is simply expressed in terms of one-way binding:
// In this pseudo-code are not taken into the account initial values assignments
Binding is accomplished by connecting the property change notification in the following event handler:
dst_prop := src_prop;
: Say clearly whether block_signal() block incoming or outgoing signals for dst_prop. Both approaches work, but blocking incoming signals is more efficient. This also relates with simple and double bindings (see [http://ex-code.com/articles/binding-properties.html
the article]). Needs more (simple) research.
To eliminate infinite loops (due circular property bindings such as two-way bindings) the signal must be blocked. Alternatively, infinite loops can be eliminated by comparing the assigned value with the value of the property before assignment and eliminating unnecessary assignments. (But the second approach of eliminating infinite loops may not work in some cases with TypeConversion?
/ transforming values.)
Additionally before connecting the signal the dst_prop should be assigned src_prop to ensure that they are equal:
dst_prop := src_prop; // initial assignment
This should be implemented as library routines.
the article] for a little more detailed/efficient solution.
Binding Properties of Different Types
Binding properties of different types is accomplished the same way as binding properties of the same type but using TypeConversion?
: Say about simple and double bindings.
Binding Properties with a Transformation
Binding properties with a transformation by a functor(s) (e.g. meters<->kilometers by *1000, /1000) is reduced to the problem of binding properties of different types. The functor(s) should be (imaginarily) considered as TypeConversion?
A common case of binding properties with a transformation is binding boolean properties with mutual negation.
Binding Multiple Properties
. For now see [http://ex-code.com/articles/binding-properties.html
: Need to establish the common terminology. I confess that my current terminology is somehow flaky. See both the article and the docs of the library below. Please comment on terminology.
: say here about double bindings or just refer to [http://ex-code.com/articles/binding-properties.html
Properties are kept synchronized completely automatically. Between library calls they always have the values expressed by the EqualityConstraint?
Watching over property changes requires some resources. These resources may be sometimes unnecessarily wasted on properties changes of which no one observes for.
# Formal proof of correctness.
# Write more clearly.
# Say about ModelViewController
and properties used for UserPreferences
, 15 Sep 2004