There’s a lot of confusion about custom editor components in TableCells for JavaFX applications. The problem: Someone (like me) implements a custom TableCell with a DatePicker or a ColorPicker for example. A naive solution maybe looks like this.
That works in so far that the value is correctly displayed in the ColorPicker and the ColorPicker is active and can be changed. But, given that the cell is editable, the property of the cells item is editable, the value of it never changes, regardless what is selected in the ColorPicker.
You’ll find a lot of tips like this or this. They have in common that they are either rather complex and most of the time uses a stanza like this:
TableCell cell; cell.contentDisplayProperty().bind(Bindings.when(editingProperty()) .then(ContentDisplay.GRAPHIC_ONLY) .otherwise(ContentDisplay.TEXT_ONLY)); |
Although pretty cool feature that bindings can be conditionally expressed, but not so useful. What is stated there is: “If the table is not in editing mode, use the text set with setText (in that case a formatted date), otherwise, if editing mode, use the node provided through setGraphic”. So the date picker in that case or my color picker is only available when editing. What might work with a date picker isn’t an option with the color picker. Also, the user need to do 2 clicks.
So i came up with that solution:
public class ColorTableCell<T> extends TableCell<T, Color> { private final ColorPicker colorPicker; public ColorTableCell(TableColumn<T, Color> column) { this.colorPicker = new ColorPicker(); this.colorPicker.editableProperty().bind(column.editableProperty()); this.colorPicker.disableProperty().bind(column.editableProperty().not()); this.colorPicker.setOnShowing(event -> { final TableView<T> tableView = getTableView(); tableView.getSelectionModel().select(getTableRow().getIndex()); tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column); }); this.colorPicker.valueProperty().addListener((observable, oldValue, newValue) -> { if(isEditing()) { commitEdit(newValue); } }); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } @Override protected void updateItem(Color item, boolean empty) { super.updateItem(item, empty); setText(null); if(empty) { setGraphic(null); } else { this.colorPicker.setValue(item); this.setGraphic(this.colorPicker); } } } |
Let me explain:
- this.colorPicker.setOnShowing() adds a handler that is executed just before the color picker popups up. It retrieves the TableView from the cell and also the currently selected row (which only works in single selection mode). It than starts the edit by calling tableView.edit. There’s no need to call startEdit() from TableCell, which actually will lead to a NPE somewhere…
- Than i watch the valueProperty() of the color picker and not the onAction or keypressed or whatsoever event, i want the value. That value is then committed. All further actions on the table are automatically, no need to manually call updateItem and the like.
That solution can be easily transferred to LocalDate cells etc. It’s benefits: It’s trivial and easy to implement, better user experience (no vanishing controls, no 2 clicks). Everything that fiddles with setContentDisplay(text or graphic) is just working around to get the table into edit mode when the user clicks often enough.
The classes shown here are part of my JavaFX demo project BikingFX, which is available on GitHub.
What do you think?
21 comments
Excellent approach. It helped me creating a ChoiceBoxTableCell around the same concept.
—-
Hello Jan,
great feedback, thanks.
I took the freedom to enable syntax highlighting on your code.
Have a great day,
Michael.
Hi,
great post! I’m using the LocalDateTableCell; it works fine if you select the value from the picker, but if you try to modify manually the text and then confirm it pressing enter key, your change is lost and is preserved last value of date.
How you suggest to solve the problem?
Thanks
Hi Daniele,
which LocalDateTableCell do you use? I have thought that it *would* work, didn’t implement it myself…
Hi Michael,
I used this class https://github.com/michael-simons/bikingFX/blob/master/src/main/java/ac/simons/bikingFX/common/LocalDateTableCell.java
It works quite well but if you change the date by hand in the textbox the value is not committed. I think is missing a listener of keyReleased (ENTER key) and a listener on loss of focus that should commit the value of the textbox.
THanks
Thanks Daniele,
thanks for reminding me what I have written… How embarrassing. I’ll have a look tomorrow.
🙂 Thanks very much!
Hello Daniele,
i’ve just updated the component.
Editing starts when the editor component receives focus as well as the button is clicked. See https://github.com/michael-simons/bikingFX/commit/b9129f0ef23bef765ed1deaf68cdcb2fc8e4b2e8#diff-188032c25dbef0578d1639b72a285224.
So if you click inside the text field and you enter a valid date and commit with ENTER, everything is fine.
Regarding commit on focus lost see https://bugs.openjdk.java.net/browse/JDK-8136838, i don’t thing i’m gonna put effort into this.
If you find a solution, I’d be more then happy to add it.
have a great day,
Michael
Hi,
thanks for the update. I had done something slightly different to handle the loss of focus: http://pastie.org/private/i18lxfg8l5hkzy0o2sxx3g
What do you think?
Lines 29 – 34 are obsolete with my update (i have replaced that code with the focus property).
You don’t need lines 35 – 53, this works out of the box (hitting enter ).
Lines 64 – 73: That’s would i would do as well for the focus lost event, but haven’t done it because it would fire twice when using the button.
Thanks very much, all very clear!
Happy to hear! Thanks for reading my blog.
How can you implement with ComboBox, I tried this logivlc to implement ComboBox editor but I am getting CalssCastException that String can’t be cast to my custom object.
This is happening at commitEdit.
Regards,
Farhan
Hi Farhan,
that sounds that you have only Strings in your ComboBox Model (the List that is the data source of your view).
You have two options: Add your custom Objects to the ComboBox’ model. Then you could either implement a fancy toString on the model or, the “cleaner” solution, implement an additional CellFactory for customizing the rendering.
I like the way you wrote your color picker table cell. Very simple and a nice display. Thanks for sharing your code.
Thanks, Mike!
Thanks man, you save my life.
Hi.
This is a really useful post. But, it turns out it’s much harder to implement a custom ComboBox table cell. To be more specific, I tried it and I’m getting this problem:
http://stackoverflow.com/quest.....e-behavior
Can you help me please? I’m really stucked and can’t figure this out. Thank you
Hi, i tried this solution and it has worked.
Instead of passing column instance through constructor just use TableCell class methods getTableView(), getTableRow() and getTableColumn():
this.colorPicker.setOnShowing(event -> {
getTableView().edit(getTableRow().getIndex(), getTableColumn());
});
Hi Micheal, when I check the value of isEditing() in this code snippet , it returns false. Can you confirm why is that so?
this.colorPicker.valueProperty().addListener((observable, oldValue, newValue) -> {
if(isEditing()) {
commitEdit(newValue);
}
});
Hi Akshaj, nope, not at the moment. Not having a JavaFX environment at hand. Sorry.
Post a Comment