April 13, 2018
This article presents a snippet of code I used to to String-ify a List using JavaFX Binding. Given a List of domain objects, I wanted to form a custom String for display in a JavaFX Alert dialog. There are a few ways to do this, but I opted for Binding because of the streamlined syntax and because the rest of the class used Binding to link UI controls to data sources.
This sample program initializes a three-element List and displays the names as a comma-separated String in a Label. To demonstrate the dynamic nature of Binding, a Button is added that will add items to the List. When an item is added, the String expression in the Label is automatically updated.
The class begins with a domain object, Person. Person has a single field "name".
class Person {
private final String name;
public Person(String name) { this.name = name; }
public String getName() { return name; }
}
The class begins by initializing an FXCollections ObservableList. This is filled with an initial set of three domain objects. The UI controls are added -- VBox, Label, Button -- to the Scene. I apply the binding expression and then show the Stage.
public class StringifyApp extends Application {
private int counter = 1;
@Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add( new Person("Carl") );
persons.add( new Person("Edvin") );
persons.add( new Person("Thomas") );
VBox vbox = new VBox();
Label label = new Label("Empty");
Button b = new Button("Add One");
b.setOnAction( evt -> persons.add( new Person("Matt " + counter++)));
vbox.setSpacing( 10.0d );
vbox.setPadding( new Insets( 40.0d ));
vbox.getChildren().addAll( label, b );
Scene scene = new Scene( vbox, 480, 320 );
primaryStage.setScene( scene );
label.textProperty()
.bind(
Bindings.createStringBinding(
() ->
persons.stream().map( Person::getName ).collect(Collectors.joining(", ")),
persons)
);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
The interesting part of the code is the createStringBindingCall(). This links a Callback with an Observable item. The Callback is a Java Streams expression that extracts the name field of a Person objects, gathers the name fields in a List, and then joins them as a String using the comma as a delimiter. The Observable item is the ObservableArrayList "persons".
If you looked at the program into which I integrated this code, you'd see a lot of JavaFX Binding. Labels are bound to StringProperty fields. TableViews are bound to ObservableList. The style looks nice when I can keep everything consistent rather than adding InvalidationListeners or ChangeListeners. Moreover, this StringBinding can be used in other places by storing the result in a variable.
Edvin from the TornadoFX project wrote this Kotlin code which is a cooler version of my Streams expression.
val join = Callable {
people.joinToString("\n") { "${it.name};${it.age}" }
}
val b = Bindings.createStringBinding(join, people)
The ${} syntax is a better formatted string expression than the getter method reference in the JavaFX version or a MessageFormatter object.
By Carl Walker
President and Principal Consultant of Bekwam, Inc