It’s been some time since my last post. Work’s been busy, etc, etc, etc. In trying to convert a JavaFX application I wrote to the new JavaFX platform, I encountered some issues. One of those is the lack of a proper table widget, but that’s not the focus of this post. This post is about another issue: binding different types.
The semantics and syntax of bind changed a little since the old JavaFX script specification. Bind is now essentially one-way, unless you specify it to be ‘with inverse’, which causes a bidirectional bind to be created.
A situation that is rather common is that the model contains an Integer attribute, while the user-interface provides a textfield where a number can be entered. It turns out that is rather difficult to do so using bind:
Code:
import javafx.ext.swing.*; | |
| |
var model = ConverterExampleModel { | |
value: 1 | |
}; | |
| |
var monitor = bind model.value on replace { | |
java.lang.System.out.println("value is now {model.value}"); | |
}; | |
| |
SwingDialog { | |
content: BorderPanel { | |
center: TextField {columns: 6, text: bind "{model.value}"} | |
} | |
visible: true | |
width: 200 | |
height: 50 | |
} | |
| |
class ConverterExampleModel { | |
attribute value: Integer; | |
} |
The piece of code above works, as long as the user doesn’t change the value in the textfield. Otherwise an AssignToBoundException is thrown. There are workarounds, such as updating the model’s value when the textfield changes (this doesn’t solve the exception yet) but that removes the instant feedback as the change is only propagated when the user leaves the textfield. Another solution is use String values in the model, but in more extensive programs it is easier to lose sight of the exact format of the string (and it just feels wrong).
The following is a more elegant solution. I’m not entirely happy with the model’s value now being a converter-object, plus that the switch required changes to code in multiple places. Alternatives, however, would call for creating variables outside the model, which results in more clutter.
Code:
import javafx.ext.swing.*; | |
import java.text.*; | |
| |
var model = ConverterExampleModel { | |
value: IntegerStringConverter {intValue: 1} | |
}; | |
| |
var monitor = bind model.value.intValue on replace { | |
java.lang.System.out.println("value is now {model.value.intValue}"); | |
}; | |
| |
SwingDialog { | |
content: BorderPanel { | |
center: TextField {columns: 6, text: bind model.value.stringValue with inverse} | |
} | |
visible: true | |
width: 200 | |
height: 50 | |
} | |
| |
class ConverterExampleModel { | |
attribute value: IntegerStringConverter; | |
} | |
| |
class IntegerStringConverter { | |
attribute intValue: Integer on replace { | |
stringValue = "{intValue}"; | |
}; | |
attribute stringValue: String on replace { | |
try { | |
intValue = java.lang.Integer.parseInt(stringValue); | |
} catch (e) { | |
} | |
}; | |
} |
The change basically comes down to creating a Converter class with two (or more for multiple formats!) attributes, one for each format. Because these are attributes, we can take advantage of bind throughout the rest of the program. The attributes receive triggers that update the other values. Note that this class can be reused for all attributes requiring the same conversion. The model should then have a variable of the type of the converter (instead of Integer in the above situation). Finally, all places where the value of the model is used should be postfixed by a reference to the correct attribute (intValue or stringValue).
After these changes, the ‘bind with inverse’ works correctly for the textfield, and the model’s value is update when a valid number is entered. For production use, the converter might need a more proper exception handling mechanism (instead of keeping the last known valid value). This pattern can also be used for other conversions, such as dates.
Personal blog on my interests.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| << < | Current | > >> | ||||
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | ||||