If you do Swing application development, especially the enterprise application type you know lots of code that looks like this:
public class Person {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public void setName(String aName){
String oldValue = name;
name = aName;
pcs.firePropertyChange("name", oldValue, aName);
}
public String getName(){
return name;
}
Having properties like this you can bind them to Swing components using JGoodies Binding like this:
MyBean bean = new MyBean();
BeanAdapter adapter = new BeanAdapter(bean, true);
ValueModel stringModel = adapter.getValueModel("name");
JTextField field = BasicComponentFactory.createTextField(stringModel);
If you don't know JGoodies Binding you should read this article about it asap.
If you do form based Swing applications this is the way to go: don't write Listeners, bind components.
Yet I have a huge problem with this code. Actually multiple problems:
- You have to repeat the name of the property in four places and you will end up in hell if you don't do it in the correct way: In the name of the setter, in the name of the getter, in the first argument to firePropertyChange and in the call to the binding framework.
-
A trivial property takes 8 lines of code. Thats EIGHT lines of code. Or depending on your code quality probably about 1/100th to 1/10th of a bug, for a trivial property.
-
Apart from being to much code, it also has a high degree of duplication, because it is the same pattern over and over again. Yet their doesn't seem to be a reasonable way to abstract over this kind of thing
If you are willing to switch to another language you actually can abstract over this quite nicely. This of course in no surprise, since it should be easy to write a language that offers support for this kind of property. But that is not what I'm talking about. I'm of course talking about a little Scala goodness. How about this for defining a class with a property:
class Person extends PropertyOwner {
val name : Property[String] = "smith"
}
Note that there is only one line defining the property (including setting it to an initial value). So how would binding it to a JTextField look like? Like this:
val p = new Person
val nameTextField = new JTextField()
Binder.bind(p.name, nameTextField)
Note that I don't use the String "name" anywhere. Its all just normal, strongly typed values. If I use firstName instead of name by accident, the compiler will complain. The trick is obviously that I actually use objects of type Property, instead of fields, getters and setters. Yet using this thing looks (almost) perfect natural:
val p = new Person
p.name := "Alf"
println(p.name())
The code to achieve this is rather simple and short. The key is that a Property has a apply method returning its value and an := operator as replacement for a setter.
class Property[T](var value : T) {
var listeners = List[T => Unit]()
def apply() = value
def :=(aValue : T) {
if (value != aValue) {
value = aValue
fireEvent
}
}
def registerListener(l : T => Unit) {
listeners = l :: listeners
}
private def fireEvent {
listeners.foreach(_(value))
}
}
object Property {
implicit def apply[T](t : T)(implicit owner : PropertyOwner) : Property[T] =
new Property(t : T)
implicit def toT[T](p : Property[T]) : T = p()
}
trait PropertyOwner {
implicit val THE_OWNER = this
}
object Binder {
def bind(p : Property[String], textField : JTextField) {
var locked = false
initTextField
syncFromTextFieldToProperty
syncFromPropertyToTextField
def initTextField = p() match {
case null => textField.setText("")
case _ => textField.setText(p().toString())
}
def syncFromPropertyToTextField {
p.registerListener { value : String =>
if (textField.getText != value)
textField.setText(value)
}
}
def syncFromTextFieldToProperty = textField.getDocument.addDocumentListener(new DocumentListener() {
def changedUpdate(e : DocumentEvent) = updateProperty
def insertUpdate(e : DocumentEvent) = updateProperty
def removeUpdate(e : DocumentEvent) = updateProperty
})
def updateProperty = {
p := textField.getText
}
}
}
This is currently just a little toy, call it proof of concept if you like. It only works with JTextFields, and the event notification might needs tweaking, since it provides just the new value of the property right now. There are many more ideas how to use this as a basis for building nice clean code with a Swing GUI. Watch this blog for more ideas and toys like this.
Talks
Wan't to meet me in person to tell me how stupid I am? You can find me at the following events: