java - JavaFx: can data binding (textfield) be used in production mode? -
i use java 8.0.45. have implemented first javafx application (very simple) data binding. however, biding user input-> pojo seems work bugs. i've checked 200 times. entered new values in text fields , after checked model values. same code, same behaviour. works fine (in cases - 80-90%) model value!=textfield value. i've noticed following. data binding text field works,works , @ point of time binding stops working , new values textfield not passed model. nor exceptions. nor warnings. nothing. binding doesn't work.
i have 4 textfiled created via fxml. 2 string model type. 1 integer. 1 bigdecimal. problem happens these fields(sometimes one, several). number fields can have null values, use example propertyobject not integerproperty (people openjfx advised so).
so javafx bug or what? p.s. use felix osgi, weld cdi, , pax - don't know if matters...
my code following:
dto - pojo model
public class task { private string name; private integer order; private bigdecimal weight; private string comment; private final propertychangesupport propertychangesupport; public task() { this.propertychangesupport = new propertychangesupport(this); } public string getname() { return name; } public void setname(string name) { string pv = this.name ; this.name = name; propertychangesupport.firepropertychange("name", pv, name); } public integer getorder() { return order; } public void setorder(integer order) { integer pv = this.order; this.order = order; propertychangesupport.firepropertychange("order", pv, this.order); } public bigdecimal getweight() { return weight; } public void setweight(bigdecimal weight) { bigdecimal pv = this.weight; this.weight = weight; propertychangesupport.firepropertychange("weight", pv, weight); } public string getcomment() { return comment; } public void setcomment(string comment) { string pv = this.comment; this.comment = comment; propertychangesupport.firepropertychange("comment", pv, this.comment); } public void addpropertychangelistener(propertychangelistener listener) { propertychangesupport.addpropertychangelistener(listener); } }
adapter
public class taskadapter { private stringproperty nameproperty; private objectproperty<integer> orderproperty; private objectproperty<bigdecimal> weightproperty; private stringproperty commentproperty; public taskadapter(task task) { try { nameproperty=new javabeanstringpropertybuilder().bean(task).name("name").build(); orderproperty=new javabeanobjectpropertybuilder<integer>().bean(task).name("order").build(); weightproperty=new javabeanobjectpropertybuilder<bigdecimal>().bean(task).name("weight").build(); commentproperty=new javabeanstringpropertybuilder().bean(task).name("comment").build(); } catch (nosuchmethodexception ex) { logger.getlogger(this.getclass().getname()).log(level.severe, null, ex); } } public stringproperty getnameproperty() { return nameproperty; } public objectproperty<integer> getorderproperty() { return orderproperty; } public objectproperty<bigdecimal> getweightproperty() { return weightproperty; } public stringproperty getcommentproperty() { return commentproperty; } }
bigdecimal converter
public class simplebigdecimalstringconverter extends stringconverter<bigdecimal>{ @override public string tostring(bigdecimal i) { if (i == null) { return "" ; } else { return i.tostring(); } } @override public bigdecimal fromstring(string string) { if (string.trim().length() == 0) { return null ; } else { try { return new bigdecimal(string); } catch (numberformatexception nfe) { return null ; } } } }
integerconverter
public class simpleintegerstringconverter extends stringconverter<integer>{ @override public string tostring(integer i) { if (i == null) { return "" ; } else { return i.tostring(); } } @override public integer fromstring(string string) { if (string.trim().length() == 0) { return null ; } else { try { return integer.valueof(string); } catch (numberformatexception nfe) { return null ; } } } }
initializing code
task task=new task(); taskadapter adapter=new taskadapter(task); nametextfield.textproperty().bindbidirectional(adapter.getnameproperty()); ordertextfield.textproperty().bindbidirectional(adapter.getorderproperty(),new simpleintegerstringconverter()); weighttextfield.textproperty().bindbidirectional(adapter.getweightproperty(),new bigdecimalstringconverter()); commenttextfield.textproperty().bindbidirectional(adapter.getcommentproperty());
what happening
javafx bindings use weakchangelisteners behind scenes implement binding. means binding can garbage collected if no other references in scope. in code, adapter
defined local variable, gets prematurely garbage collected @ arbitrary time when gc runs.
demo
here's demo using code shows issue. has same text fields define, plus 2 buttons. 1 button dumps value of task
console, other forces garbage collector run. you'll see binding stops working run gc.
import javafx.application.application; import javafx.geometry.insets; import javafx.geometry.pos; import javafx.scene.scene; import javafx.scene.control.button; import javafx.scene.control.label; import javafx.scene.control.textfield; import javafx.scene.layout.borderpane; import javafx.scene.layout.gridpane; import javafx.scene.layout.hbox; import javafx.stage.stage; import javafx.util.converter.bigdecimalstringconverter; public class pojobindingexample extends application { private textfield nametextfield = new textfield(); private textfield ordertextfield = new textfield(); private textfield weighttextfield = new textfield(); private textfield commenttextfield = new textfield(); @override public void start(stage primarystage) { task task = new task(); taskadapter adapter = new taskadapter(task); nametextfield.textproperty().bindbidirectional(adapter.getnameproperty()); ordertextfield.textproperty().bindbidirectional(adapter.getorderproperty(),new simpleintegerstringconverter()); weighttextfield.textproperty().bindbidirectional(adapter.getweightproperty(),new bigdecimalstringconverter()); commenttextfield.textproperty().bindbidirectional(adapter.getcommentproperty()); gridpane grid = new gridpane(); grid.addrow(0, new label("name:"), nametextfield); grid.addrow(1, new label("order:"), ordertextfield); grid.addrow(2, new label("weight:"), weighttextfield); grid.addrow(3, new label("comment:"), commenttextfield); button showbutton = new button("show task"); showbutton.setonaction(e -> { system.out.println(task.getname()); system.out.println(task.getorder()); system.out.println(task.getweight()); system.out.println(task.getcomment()); system.out.println(); }); button gcbutton = new button("run gc"); gcbutton.setonaction(e -> system.gc()); hbox buttons = new hbox(10, showbutton, gcbutton); borderpane.setalignment(grid, pos.center); borderpane.setalignment(buttons, pos.center); borderpane.setmargin(grid, new insets(10)); borderpane.setmargin(buttons, new insets(10)); borderpane root = new borderpane(grid, null, null, buttons, null); scene scene = new scene(root); primarystage.setscene(scene); primarystage.show(); } public static void main(string[] args) { launch(args); } }
fix
to fix problem, need ensure reference taskadapter
persists long need it. in above code, if move reference taskadapter
instance field, work required:
public class pojobindingexample extends application { private textfield nametextfield = new textfield(); private textfield ordertextfield = new textfield(); private textfield weighttextfield = new textfield(); private textfield commenttextfield = new textfield(); private taskadapter adapter; @override public void start(stage primarystage) { task task = new task(); adapter = new taskadapter(task); // ... etc } }
you might interested in reading tomas mikula's blog, though don't think can use library directly implement binding pojo.
Comments
Post a Comment