Editable ListView Cells in JavaFX
This post is part of the 100 Days of JavaFX Series.
Following up on the previous article on ListView and Observable Collections, we want to use it in our JSON Schema Editor project with the ability to edit the name of the schema.
Let’s explore how we can use TextFieldListCell
for this purpose.
Using the built-in TextFieldListCell
JJavaFX provides an out-of-the-box mechanism to have ListView cells with are editable by using a TextFieldListCell
.
public class Demo2 extends Application {
@Override
public void start(Stage stage) throws Exception {
ObservableList<String> items = FXCollections.observableArrayList("a", "b", "c");
StringConverter<String> converter = new DefaultStringConverter();
ListView<String> listView = new ListView<>(items);
listView.setEditable(true);
listView.setCellFactory(param -> new TextFieldListCell<>(converter));
Scene scene = new Scene(listView, 640, 480);
stage.setScene(scene);
stage.show();
}
}
TextFieldListCell
works great for scalar values or simple value objects (such as Currency). However, it will not play well with our previous example where we listed Schema objects composed of a name (that we want to edit) and a JSON Schema as a String.
A TextFieldListCell needs a StringConverter object in order to transform the string the user typed into the object type we desire to use.
public static class Converter extends StringConverter<Schema> {
@Override
public String toString(Schema schema) {
return schema.getName();
}
@Override
public Schema fromString(String string) {
return new Schema(string, "");
}
}
When converting a string to a Schema, we can only fill the name of the Schema and TextFieldListCell
will replace the existing Schema in the list with the one it created, effectively some data along the way.
Extend TextFieldListCell and Rebuild the Converter
The main problem with the earlier approach is: The StringConverter always create a new Schema
instance and only fill its name, losing existing data in the process.
We could have the converter try to reusing the current schema object. However, since we created the converter beforehand it.
The trick is to have the converter re-created whenever a new schema instance is assigned to the cell by overriding the updateItem
method.
package eu.leward.jschema;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.util.StringConverter;
public class SchemaListCell extends TextFieldListCell<Schema> {
public SchemaListCell() {
super();
refreshConverter();
}
private void refreshConverter() {
StringConverter<Schema> converter = new StringConverter<>() {
@Override
public String toString(Schema schema) {
return schema.getName();
}
@Override
public Schema fromString(String string) {
if (isEmpty()) {
return new Schema(string, "{}");
}
Schema schema = getItem();
schema.setName(string);
return schema;
}
};
setConverter(converter);
}
@Override
public void updateItem(Schema item, boolean empty) {
super.updateItem(item, empty);
refreshConverter();
}
}
With this approach we can leverage any class of our choosing with TextFieldListCell
.