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
sourceandtargetvalues must be specified. AnIllegalArgumentExceptionwill be thrown if that's not the case. - both
sourceandtargetinstances must be observable. This does not imply that both must implement Observable per se, as Swing components do not. - either
sourcePropertyortargetPropertycan be omitted but not both. The missing value will be taken from the other property. - the
builderinstance 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>