15.4 Creating Bindings in Java - Reference Documentation
Authors: Andres Almiray
Version: 1.2.0
15.4 Creating Bindings in Java
Bindings are an effective way to keep two properties in sync. Unfortunately Java does not provide a mechanism nor an API to make bindings, but Griffon does.As shown in the Binding section, Griffon relies onPropertyChangeEvent
and PropertyChangeListener
to keep track of property changes and notify observers. Swing components are already observable by default. You can build your own observable classes by following a convention, or implement the Observable interface (there's a handy partial implementation in AbstractObservable that you can subclass).Bindings can be created by using BindUtils.binding(), like the following example showspackage sample;import java.util.Map; import groovy.util.FactoryBuilderSupport; import griffon.swing.BindUtils; import org.codehaus.griffon.runtime.core.AbstractGriffonView;public class SampleView extends AbstractGriffonView { private SampleController controller; private SampleModel model; public void setController(SampleController controller) { this.controller = controller; } public void setModel(SampleModel model) { this.model = model; } public void mvcGroupInit(Map<String, Object> args) { buildViewFromXml(args); FactoryBuilderSupport builder = getBuilder(); /* * Equivalent Groovy code * bind(source: input, sourceProperty: 'text', * target: model, targetProperty: 'value') */ BindUtils.binding() .withSource(builder.getVariable("input")) .withSourceProperty("text") .withTarget(model)) .withTargetProperty("value") .make(builder); /* * Equivalent Groovy code * bind(source: model, sourceProperty: 'value', * target: input, targetProperty: 'text') */ BindUtils.binding() .withSource(model) .withSourceProperty("value") .withTarget(builder.getVariable("output")) .withTargetProperty("text") .make(builder); } }
- both
source
andtarget
values must be specified. AnIllegalArgumentException
will be thrown if that's not the case. - both
source
andtarget
instances must be observable. This does not imply that both must implement Observable per se, as Swing components do not. - either
sourceProperty
ortargetProperty
can be omitted but not both. The missing value will be taken from the other property. - the
builder
instance must be able to resolve thebind()
node. This is typically the case for the default builder supplied to Views (because Swingbuilder is included).
mutual
, converter
and validator
. The next snippet improves on the previous example by setting a converter and a validator, only numeric values will be accepted.package sample;import java.util.Map; import groovy.util.FactoryBuilderSupport; import griffon.swing.BindUtils; import griffon.util.CallableWithArgs; import org.codehaus.griffon.runtime.core.AbstractGriffonView;public class SampleView extends AbstractGriffonView { private SampleController controller; private SampleModel model; public void setController(SampleController controller) { this.controller = controller; } public void setModel(SampleModel model) { this.model = model; } public void mvcGroupInit(Map<String, Object> args) { buildViewFromXml(args); FactoryBuilderSupport builder = getBuilder(); /* * Equivalent Groovy code * bind(source: input, sourceProperty: 'text', * target: model, targetProperty: 'value', * converter: {v -> v? "FOO $v" : 'BAR'}, * validator: {v -> * if(v == null) true * try { Integer.parseInt(String.valueOf(v)); true } * catch(NumberFormatException e) { false } * }) */ BindUtils.binding() .withSource(builder.getVariable("input")) .withSourceProperty("text") .withTarget(model) .withTargetProperty("value") .withConverter(new CallableWithArgs<String>() { public String call(Object[] args) { return args.length > 0 ? "FOO "+ args[0] : "BAR"; } }) .withValidator(new CallableWithArgs<Boolean>() { public Boolean call(Object[] args) { if(args.length == 0) return Boolean.TRUE; try { Integer.parseInt(String.valueOf(args[0])); return Boolean.TRUE; } catch(NumberFormatException e) { return Boolean.FALSE; } } }) .make(builder); /* * Equivalent Groovy code * bind(source: model, sourceProperty: 'value', * target: input, targetProperty: 'text') */ BindUtils.binding() .withSource(model) .withSourceProperty("value") .withTarget(builder.getVariable("output")) .withTargetProperty("text") .make(builder); } }
<application title="app.config.application.title" pack="true"> <actions> <action id="'clickAction'" name="'Click'" closure="{controller.click(it)}"/> </actions> <gridLayout cols="1" rows="3"/> <textField id="'input'" columns="20"/> <textField id="'output'" columns="20" editable="false"/> <button action="clickAction" </application>