Anyone who’s used any of the JavaFX UI Controls (in javafx.scene.control) has probably noticed that most of the controls don’t have any listeners. Why is this? Without listeners, how can you detect when the user has changed the state of the control?
The first, easy answer is that you can often avoid the need for listeners by binding to the state of the control. Let’s consider a simple CheckBox example. Suppose you wanted to change the visibility of something based on whether the checkbox is selected. In Swing, you’d do something like adding an item listener to the checkbox, and when it got called, you’d look in the event to see which checkbox got changed and whether it was selected or deselected, and then modify the visibility of your component (or whatever) accordingly.
In JavaFX, you’d do something kind of the opposite. Instead of having a listener get called that “pushes” the side effect of the checkbox state change into some target object, you’d instead bind the “visible” state of the target object to the selected state of the checkbox. Here’s how:
var cb:CheckBox;
Stage {
scene: Scene {
content: [
HBox {
spacing: 10
padding: Insets { top: 10 right: 10 bottom: 10 left: 10 }
content: [
cb = CheckBox {
text: "Check Me!"
selected: true
}
Label {
text: "sample label text"
visible: bind cb.selected
}
]
}
]
}
}
The bind-expression makes sure that the Label’s “visible” variable will always track the state of the CheckBox, and so the Label will appear and disappear as you check and uncheck the CheckBox. Simple.
This is all well and good if your situation calls for some state to be updated when the CheckBox changes state, and the update computation is something simple enough to be written using a bind-expression. But what if you have some kind of process that you need to perform, say, one that requires having some function called. Wouldn’t you need a listener? Yes, but it’s fairly simple to construct your own.
Like many languages, JavaFX Script has block structure, where any code block “{ … }” can declare local variables that are scoped to that block (i.e., they are not visible outside that block). It’s also legal to declare local variables within the “{ … }” of an object literal, even though it’s a syntactically different construct from a code block. What’s not very well known is that it’s also possible to override variables of the class you’re instantiating. By itself, overriding a variable doesn’t let you do much except to override the initial value of a variable. Since this is already within an object literal, you’re providing initial values already, so what’s the point? Overriding useful because it provides a point of (re)declaration of the variable where you can supply an “on replace” trigger. This is code that gets called whenever the value is changed … just like a listener!
Let’s try this out. Instead of just toggling the visibility of the label, let’s toggle a timeline that rotates it:
Stage {
scene: Scene {
content: [
HBox {
spacing: 10
padding: Insets { top: 10 right: 10 bottom: 10 left: 10 }
content: [
CheckBox {
text: "Check Me!"
override var selected on replace {
if (selected) tl.play() else tl.pause();
}
}
label = Label {
text: "sample label text"
}
]
}
]
}
}
var tl = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at (2s) {
label.rotate => 360
}
]
}
(See Footnote 1.)
This is a little non-obvious but it works quite well.
Now, there’s a problem with this technique. In order to override a variable, that variable must be public. Let’s say that we wanted to create a ListView and create the equivalent of a listener that gets called whenever the selection changes. We’ll just do this, right?
ListView {
items: [ "One", "Two", "Three" ]
override var selectedItem on replace {
println("selection is now {selectedItem}"); // ERROR
}
}
Oh no, this doesn’t work! The “selectedItem” variable isn’t public, it’s public-read. (Why? See Footnote 2.) This is a bit of a pain to work around. We cannot override public-read variables, but we can still bind to them. This means we can use the foreign trigger idiom to create a new variable that tracks the value of the variable we’re interested in, and then add a trigger to our new variable. Here’s how:
var list = ListView {
items: [ "One", "Two", "Three" ]
var sel = bind list.selectedItem on replace {
println("selection is now {sel}");
}
}
This is fairly cumbersome, since we have to create and name a new local variable and hang our “listener” code there, and we have to make sure the ListView is accessible to it via a named variable. This is probably about as much boilerplate as adding a listener using an anonymous class in Java. Still, we’re used to much more concise syntax in JavaFX Script. It’d probably be nicer to have a function variable such as “onSelectionChanged” whose value is a function that gets called when the ListView’s selection changes. Adding callback APIs like this hasn’t been a very high priority up until now, though, since it’s possible (if inconvenient) to use the controls without them using the bind or override-and-bind-with-trigger techniques. We’re definitely considering adding them, though, since it makes the API much more approachable.
Footnote 1. This is a bit sloppy. Those of you who compile this code will note that the “label.rotate => 360″ line gets a compiler warning to the effect that “label is not a constant but will be treated as such”. This means that, at the time the KeyFrame object of the Timeline is created, a snapshot of the current value of “label” is taken and stored within the KeyFrame. This means that “label” needs to have been initialized to the right value before the Timeline and KeyFrame are created. In fact, I first created the Timeline above the Stage/Scene/HBox hierarchy, and the animation didn’t work! Moving it below makes it work, but the compiler still issues the warning. ↩
Footnote 2. Why are selectedItem and selectedIndex public-read instead of public? If they were both public, the implementation would have to have triggers to handle the cases where the application wrote to them. Each trigger would have to write to the other variable, which in turn would invoke its trigger. This doesn’t cause infinite recursion, though, since the trigger won’t fire if the variable is set to a value equal to its current value. But it’s still a pain. Oh, another problem is, what if the application set selectedItem to an object that’s not a member of the ListView? The trigger would have to set the variable back to a legal value, and add a flag to avoid recursive trigger invocation as noted here. (Also note that Toggle.selected and ToggleGroup.selectedToggle are both public and have this problem. Trust me, dealing with this kind of situation is a real pain.) ↩
Good, clear and concise explanation of the concept. I will sure point to here instead of explaining (as I did many times) how to deal with these events.
I don’t think I like the idea of adding numerous callback function variables is such a good idea, it will increase an already big API and “bastardize” an interesting idiomatic pattern (exposing variables to allow using them).
Perhaps the compiler could allow overriding public-read variables as read-only, only in combination with an on replace/on invalidate scheme? I also wonder why we cannot do var sel = bind this.selectedItem
I have to agree with PhiLho. I think the trigger approach, and allowing triggers on public-read variables is more in the spirit, and “feel” of the JavaFX language than adding a whole bunch of callback functions which will bloat the API. So, if I may ask what are the main issues with relaxing the override rules to allow this to be supported?
Well, callbacks don’t have to be numerous. We’re sensitive to bloat. On the other hand, people keep looking for callbacks/listeners and not finding them, so that’s telling us something too.
Offhand I’m not sure why you can’t override a public-read variable. You certainly shouldn’t be allowed to initialize it in the override declaration. Maybe there’s no way to allow an override but without an initializer, so overrides had to be disallowed entirely — losing the ability to add a trigger. It might be possible to add this. However, tinkering with the language has a lot of subtleties, and adding something apparently simple like this might have unforeseen side effects.
Regarding the use of “this” it is unfortunate that in order to refer to the object being created from within its object literal, you cannot use something convenient like “this” and instead you have to declare a variable and use that variable name. Use of “this” always refers to the object on which a function has been invoked. It would be strange if “this” changed its meaning within in object literal. It’s often quite useful for a function on an object to create a new object and pass a reference to itself by using “this” as a value within an object literal.
The meaning of “this” can generate quite a lot of controversy. Though it’s not directly related, a discussion of “this” came up on the lambda-dev mailing list (one of the Java closures proposals). See the following for example:
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-January/000425.html
If ‘this’ usage is fishy in this context, perhaps JavaFX can use a different keyword, for example ‘self’ used in Python or some other languages.
[...] Marks has blogged about ‘Missing Listeners and Local Overrides‘ in the JavaFX UI [...]
[...] Marks has blogged about ‘Missing Listeners and Local Overrides‘ in the JavaFX UI [...]