Javafx: fail to display content properly in table cell - java

I have the following Trade object class.
public class Trade implements Comparable<Trade>{
// attributes of each trade that go into the tableViewTransaction log
// properties
private StringProperty itemID;
public Trade(int itemID){
this.itemID = new SimpleStringProperty(String.format("%04d",itemID));
}
public String getItemID(){
return this.itemID.get();
}
public StringProperty itemIDProperty(){
return this.itemID;
}
public void setItemID(String itemID){
int id = Integer.parseInt(itemID);
this.itemID.set(String.format("%04d",id));
}
}
Now in my Controller class, I have a tableView TransactionLog and a table column for itemID.
public TableView<Trade> fxTransactionLog;
public TableColumn<Trade, String> fxTransactionLogItemID;
The tableView is editable, so is the table column using the following code.
Here is where the problem is: The tableView is able to display itemID perfectly. For example say when I create a new Trade object with itemID = 1, the table cell will display 0001, then I decide to edit the itemID of a Trade object and type in to a new ID of 13, it will show up as 0013 like below.
0001 -> 0013 // This works fine, if I edit the cell and assign a DIFFERENT value to the cell.
However, if I click to edit the itemID and assign the same value it already has, which is 1 in this case. It displays 1, which is not what I want, as it is missing the leading zeros. I looked through my code and just couldn't figure out why this is happening. This is more of an aesthetic issue.
0001 -> 1 // edit and reassign same value to the cell
How can I make it display 0001 instead of just 1 even if I assign the SAME value to the cell ?
Secondly, what code and where should I write to prevent the user from typing in String for itemID ?
UPDATE: So I followed thislink Example 12-11 . I created a separate class for EditingItemIDCell.
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
public class EditingItemIDCell extends TableCell{
private TextField textField;
public EditingItemIDCell() {
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(String.format("%04d",Integer.parseInt(getString())));
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
And in my Controller class, I made the following changes.
But I am getting an error saying:
The method setCellFactory(Callback<TableColumn<Trade,String>,TableCell<Trade,String>>) in the type TableColumn<Trade,String> is not applicable for the arguments (Callback<TableColumn,TableCell>).

Define cellfactory as;
Callback<TableColumn<Trade, String>, TableCell<Trade, String>> cellFactory
= new Callback<TableColumn<Trade, String>, TableCell<Trade, String>>()
{
public TableCell call( TableColumn<Trade, String> p )
{
return new EditingItemIDCell();
}
};
In EditingItemIDCell when you create textfield do
private void createTextField()
{
NumberFormat nf = NumberFormat.getIntegerInstance();
textField = new TextField();
// add filter to allow for typing only integer
textField.setTextFormatter( new TextFormatter<>( c ->
{
if (c.getControlNewText().isEmpty()) {
return c;
} // for the No.2 issue in the comment
ParsePosition parsePosition = new ParsePosition( 0 );
Object object = nf.parse( c.getControlNewText(), parsePosition );
if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
{
return null;
}
else
{
return c;
}
} ) );
textField.setText( getString() );
textField.setMinWidth( this.getWidth() - this.getGraphicTextGap() * 2 );
// commit on Enter
textField.setOnAction( new EventHandler<ActionEvent>()
{
#Override
public void handle( ActionEvent event )
{
commitEdit( textField.getText() );
}
} );
textField.focusedProperty().addListener( new ChangeListener<Boolean>()
{
#Override
public void changed( ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2 )
{
if ( !arg2 )
{
commitEdit( textField.getText() );
}
}
} );
}
and finally, your actual problem, since you decided to format the input value in Trade class setters, when the user commits the same value, tableview determines the original value is not changed and do not update the rendered value in a cell, so when user commits "1" again, the underlying value of Trade instance is "0001", but the rendered value remains with "1". The dirty workaround may be to change the value to some arbitrary intermediate one:
public void setItemID( String itemID )
{
this.itemID.set( "0" );
int id = Integer.parseInt( itemID );
this.itemID.set( String.format( "%04d", id ) );
}
Edit
1) I still want the existing textfield 0001 in the tableCell to be
highlighted, when I double click on the cell to edit ?
Change the startEdit to this:
#Override
public void startEdit()
{
if ( !isEmpty() )
{
super.startEdit();
createTextField();
setText( null );
setGraphic( textField );
textField.requestFocus();
// textField.selectAll(); commenting out this because
// JavaFX confuses the caret position described in the comment
// as OP has observed. Seems to be a bug.
}
}
2) Plus, with your existing code, i can never delete the integer on
the first index when editing.
In the textfield filter, check for emptiness with:
if (c.getControlNewText().isEmpty()) {
return c;
}
I also edited the filter code above.

Related

JavaFX: what to do if you need to use a PropertyValueFactory

In my table I have one cell that does not update without interaction with the table.
I found the reason already here Java: setCellValuefactory; Lambda vs. PropertyValueFactory; advantages/disadvantages
My problem is, the default value of the cells item is LocalDate.MIN and I want my cell to contain "---" as long as the item has this default value. When I update the item, I want the cell to contain the current date string.
Item Class:
public class ItemEv {
private final ObjectProperty<LocalDate> openedAt;
#XmlJavaTypeAdapter(LocalDateAdapter.class)
public final LocalDate getOpenedAt() {
return openedAt.get();
}
public final ObjectProperty<LocalDate> openedAtProperty() {
return this.openedAt;
}
public final void setOpenedAt(LocalDate openedAt) {
this.openedAt.set(openedAt);
}
}
in another CellFactory I set the new value: i.setOpenedAt(LocalDate.now());
this is working but not wanted:
openedAtColumnEv.setCellValueFactory(cellData -> cellData.getValue().openedAtProperty().asString());
and this is what I tried so far:
openedAtColumnEv.setCellValueFactory(new Callback<CellDataFeatures<ItemEv, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<ItemEv, String> i) {
if (i.getValue().getOpenedAt().equals(LocalDate.MIN)) {
return new SimpleStringProperty("---");
}
return i.getValue().openedAtProperty().asString();
}
});
and this:
openedAtColumnEv.setCellValueFactory(cellData -> {
if(cellData.getValue().openedAtProperty().getValue().equals(LocalDate.MIN)) {
return new SimpleStringProperty("---");
}
return cellData.getValue().openedAtProperty().asString();
});
Both of my tests return either SimpleStringProperty or StringBinding which should be fine.
In my tests I made a mistake where the first return in the IF statement does never return true, then the cell values show the standard string for LocalDate.MIN and get updated immediately when the item property changes.
Im a bit lost on this. Please forgive my bad english, Im not a native speaker.
If the property in the model class is an ObjectProperty<LocalDate>, then the column should be a TableColumn<ItemEv, LocalDate>, not a TableColumn<ItemEv, String>.
Implementing the cellValueFactory directly (typically with a lambda expression) is always preferable to using the legacy PropertyValueFactory class. You never "need to use" a PropertyValueFactory (and never should).
The cellValueFactory is only used to determine what data to display. It is not used to determine how to display the data. For the latter, you should use a cellFactory.
So:
private TableColumn<ItemEv, LocalDate> opendAtColumnEv ;
// ...
openedAtColumnEv.setCellValueFactory(cellData -> cellData.getValue().openedAtProperty());
openedAtColumnEv.setCellFactory(column -> new TableCell<ItemEv, LocalDate>() {
#Override
protected void updateItem(LocalDate openedAt, boolean empty) {
super.updateItem(openedAt, empty);
if (openedAt == null || empty) {
setText("");
} else {
if (openedAt.equals(LocalDate.MIN)) {
setText("---");
} else {
// Note you can use a different DateTimeFormatter as needed
setText(openedAt.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
}
});

JavaFX Display Enum with String field in Combobox as String (in TableView)

My goal is to display every field of an instance of a Class in a tableView. The Class has a field of type enum which has a field of type String.
The enum shall be displayed in a ComboBox as it's String field name.
Of course it also has to be editable.
Now what isn't working:
The String field of enum class is only displayed if the ComboBox is clicked on, otherwise it is the name of the enum constant. Also, if another enum in the combobox is selected, it can't be commited for edit. Clicking return doesn't deselect the Combobox neither is the method commitEdit invoked. If an other column is selected for edit, the attempted edit is cancelled.
I put some effort into trying to figure this out, so I thought maybe one could help me here.
As the original task is about much bigger classes in enterprise software, I abstracted it to ask this question.
I know I could make the column holding the enum of type String and make it work with MyEnum.values() and MyEnum.valueOf() but that could not go into production due to bad performance as the original classes are too big.
Here is my code as an example, if you don't understand the problems just try to use the combobox once and you'll see.
Class which is type of TableView:
public class MyClass {
private MyEnum myEnum;
private String string;
public MyClass(MyEnum myEnum, String string) {
this.myEnum = myEnum;
this.string = string;
}
public MyEnum getMyEnum() {
return myEnum;
}
public void setMyEnum(MyEnum myEnum) {
this.myEnum = myEnum;
}
public String getString() {
return string;
}
}
It's enum field:
public enum MyEnum {
EnumOne("First Enum"),
EnumTwo("Second Enum");
private String name;
public String getName() {
return name;
}
private MyEnum(String name) {
this.name = name;
}
}
FX App:
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<MyClass> items = FXCollections.observableArrayList();
items.add(new MyClass(MyEnum.EnumOne, "String"));
TableView<MyClass> table = new TableView(items);
table.setEditable(true);
TableColumn<MyClass, MyEnum> myEnumColumn = new TableColumn();
TableColumn<MyClass, String> stringColumn = new TableColumn();
stringColumn.setCellFactory(TextFieldTableCell.forTableColumn());
stringColumn.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getString()));
myEnumColumn.setCellFactory((param) -> new MyEnumComboBoxCell());
myEnumColumn.setCellValueFactory(data -> new ReadOnlyObjectWrapper(data.getValue().getMyEnum()));
myEnumColumn.setOnEditCommit(
event -> {
event.getRowValue().setMyEnum(event.getNewValue());
System.out.println(event.getRowValue().getMyEnum());
});
table.getColumns().addAll(myEnumColumn, stringColumn);
StackPane root = new StackPane();
root.getChildren().add(table);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
class MyEnumComboBoxCell extends ComboBoxTableCell<MyClass, MyEnum> {
private ComboBox<MyEnum> box;
public MyEnumComboBoxCell() {
box = new ComboBox<>(FXCollections.observableArrayList(MyEnum.values()));
box.setCellFactory(new Callback<ListView<MyEnum>, ListCell<MyEnum>>() {
#Override
public ListCell<MyEnum> call(ListView<MyEnum> param) {
return new ListCell<MyEnum>() {
#Override
protected void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( item != null ) setText(item.getName());
}
};
}
});
}
#Override
public void startEdit() {
super.startEdit();
setGraphic(box);
}
#Override
public void commitEdit(MyEnum newValue) {
super.commitEdit(newValue);
if ( newValue != null ) {
setText(newValue.getName());
getTableView().getSelectionModel().getSelectedItem().setMyEnum(newValue);
box.setValue(newValue);
}
}
#Override
public void updateItem(MyEnum item, boolean empty) {
super.updateItem(item, empty);
if ( empty ) {
setGraphic(null);
} else {
setGraphic(null);
setText(item.getName());
}
}
}
Instead of setting the name in updateItem use a StringConverter like:
public class MyEnumConverter extends StringConverter<MyEnum>{
#Override public String toString(MyEnum enumConstant) {
return enumConstant.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}
Then in the cell's constructor:
this.setConverter(new MyEnumConverter());
Edit: You may not #Override all of the ComboBoxTableCell's methods, since all of them are working like you want. On the other hand you should not specify an own ComboBox for the table cell, since it has one. You just have to add a StringConverter and set the items.
You may use like this:
myEnumColumn.setCellFactory((param) -> new ComboBoxTableCell<>(new StringConverter<MyEnum>() {
#Override public String toString(MyEnum object) {
return object.getName();
}
#Override public MyEnum fromString(String string) {
return MyEnum.valueOf(string);
}
}, MyEnum.values()));
If you prefer you can create a separate class for the StringConverter like I mentioned earlier, then just simply:
myEnumColumn.setCellFactory(factory -> new ComboBoxTableCell<>(new MyEnumConverter(), MyEnum.values()));
You can also get rid of the myEnumColumn.setOnEditCommit.
Thanks a lot! Actually I've spent a day on this, partially with another person, so this is really appreciated! :)
But:
I have to implement setOnEditCommit or otherwise the myEnum field backing the tableColumn does not change! With this everything works. Without, only what is displayed is changed.
myEnumColumn.setOnEditCommit(
event ->
{
event.getRowValue().setMyEnum(event.getNewValue());
});

JavaFx: Formatting Table Column Data

This is my first time working on JavaFx and I'm following this tutorial just as a template: http://code.makery.ch/library/javafx-8-tutorial/part3/.
For my application, I'm working with 2 columns on the left side, telephone number and the call start date/time. I'm wanting to change the formatting of the data in the table as it's currently coming through as yyyy-MM-ddThh:mm.
I can't seem to figure out where to place the formatting piece at. I have a date formatter function that you can find at the link above, but it's returning a string and giving me errors. Thanks for any help you can give. Here are some code snippets of what I'm working with.
Controller:
#FXML
private void initialize() {
// Initialize the person table with the two columns.
billingNumberColumn.setCellValueFactory(cellData -> cellData.getValue().billingNumberProperty());
callStartColumn.setCellValueFactory(cellData -> cellData.getValue().callStartProperty());
}
Model:
public LocalDateTime getCallStart() {
return callStart.get();
}
public void setCallStart(LocalDateTime callStart) {
this.callStart.set(callStart);
}
public ObjectProperty<LocalDateTime> callStartProperty() {
return callStart;
}
Date Format:
public static String format(ObjectProperty<LocalDateTime> callStart) {
if (callStart == null) {
return null;
}
return DATE_FORMATTER.format((TemporalAccessor) callStart);
}
Use a cellFactory. TextFieldTableCell provides a method to create a cell factory given a converter. As converter a LocalDateTimeStringConverter can be used:
callStartColumn.setCellValueFactory(cellData -> cellData.getValue().callStartProperty());
callStartColumn.setCellFactory(TextFieldTableCell.forTableColumn(new LocalDateTimeStringConverter(DATE_FORMATTER, DATE_FORMATTER)));
Specify column
TableColumn<Person, LocalDateTime> column = new TableColumn<>("Birth");
Code for this is quite complicated and not really good lookin.
Make sure yo utake care of empty case or / null handled when no data is in the cell
column.setCellFactory(
new Callback<TableColumn<Person, LocalDateTime>, TableCell<Person, LocalDateTime>>() {
#Override
public TableCell<Person, LocalDateTime> call(TableColumn<Person, LocalDateTime> param
) {
return new TableCell<Person, LocalDateTime>() {
#Override
protected void updateItem(LocalDateTime item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
} else {
//FORMAT HERE AND CALL setText() with formatted date string
}
}
};
}
}
);

Committing a value in TableView at focus change [duplicate]

Is there any simple approach to let the TreeTableView (or TableView) try to commit values on focus lost?
Unfortunatly I didn't succed with any default-implementations of javafx TableCellFactories, which is why I tried my own TreeTableCell implementations and also some different tableCell implementations like the one from Graham Smith, which seemed the most straight forward, since it already implemented a hook for focus lost, but nevertheless the value is never committed and the userchanges are resetted to the original value.
My guess is, whenever focus is lost, the editingProperty of the affected Cell is always already false which causes the Cell never to commit a value on focusLost. Here the relevant part from the original (oracle-)TreeTableCell Implementation (8u20ea), which causes my approaches to fail:
#Override public void commitEdit(T newValue) {
if (! isEditing()) return; // <-- here my approaches are blocked, because on focus lost its not editing anymore.
final TreeTableView<S> table = getTreeTableView();
if (table != null) {
#SuppressWarnings("unchecked")
TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell();
// Inform the TableView of the edit being ready to be committed.
CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>(
table,
editingCell,
TreeTableColumn.<S,T>editCommitEvent(),
newValue
);
Event.fireEvent(getTableColumn(), editEvent);
}
// inform parent classes of the commit, so that they can switch us
// out of the editing state.
// This MUST come before the updateItem call below, otherwise it will
// call cancelEdit(), resulting in both commit and cancel events being
// fired (as identified in RT-29650)
super.commitEdit(newValue);
// update the item within this cell, so that it represents the new value
updateItem(newValue, false);
if (table != null) {
// reset the editing cell on the TableView
table.edit(-1, null);
// request focus back onto the table, only if the current focus
// owner has the table as a parent (otherwise the user might have
// clicked out of the table entirely and given focus to something else.
// It would be rude of us to request it back again.
ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
}
}
I succeded with overriding this method and commiting the value "by hand" before the original commitEdit() method is called, but this causes the commit on keys like enter to commit the value twice (on key + on focus lost). Moreover I dont really like my approach at all, so I wonder, if anyone else has solved this in a "nicer" way?
After some digging, turned out that the culprit (aka: the collaborator that cancels the edit before the textField looses focus) is the TableCellBehaviour/Base in its processing of a mousePressed:
mousePressed calls simpleSelect(..)
on detecting a single click it calls edit(-1, null)
which calls the same method on TableView
which sets its editingCell property to null
a tableCell listens to that property and reacts by canceling its own edit
Unfortunately, a hackaround requires 3 collaborators
a TableView with additional api to terminate an edit
a TableCellBehaviour with overridden simpleSelect(...) that calls the additional api (instead of edit(-1..)) before calling super
a TableCell that is configured with the extended behaviour and is aware of table's extended properties
Some code snippets (full code) :
// on XTableView:
public void terminateEdit() {
if (!isEditing()) return;
// terminatingCell is a property that supporting TableCells can listen to
setTerminatingCell(getEditingCell());
if (isEditing()) throw new IllegalStateException(
"expected editing to be terminated but was " + getEditingCell());
setTerminatingCell(null);
}
// on XTableCellBehaviour: override simpleSelect
#Override
protected void simpleSelect(MouseEvent e) {
TableCell<S, T> cell = getControl();
TableView<S> table = cell.getTableColumn().getTableView();
if (table instanceof XTableView) {
((XTableView<S>) table).terminateEdit();
}
super.simpleSelect(e);
}
// on XTextFieldTableCell - this method is called from listener
// to table's terminatingCell property
protected void terminateEdit(TablePosition<S, ?> newPosition) {
if (!isEditing() || !match(newPosition)) return;
commitEdit();
}
protected void commitEdit() {
T edited = getConverter().fromString(myTextField.getText());
commitEdit(edited);
}
/**
* Implemented to create XTableCellSkin which supports terminating edits.
*/
#Override
protected Skin<?> createDefaultSkin() {
return new XTableCellSkin<S, T>(this);
}
Note: the implementation of TableCellBehaviour changed massively between jdk8u5 and jdk8u20 (joys of hacking - not fit for production use ;-) - the method to override in the latter is handleClicks(..)
BTW: massive votingfor JDK-8089514 (was RT-18492 in old jira) might speed up a core fix. Unfortunately, at least the author role is needed to vote/comment bugs in the new tracker.
I also needed this functionality and did some study. I faced some stability issues with XTableView hacking mentioned above.
As problem seems to be commitEdit() won't take effect when focus is lost, why you don't just call your own commit callback from TableCell as follows:
public class SimpleEditingTextTableCell extends TableCell {
private TextArea textArea;
Callback commitChange;
public SimpleEditingTextTableCell(Callback commitChange) {
this.commitChange = commitChange;
}
#Override
public void startEdit() {
...
getTextArea().focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
if (!arg2) {
//commitEdit is replaced with own callback
//commitEdit(getTextArea().getText());
//Update item now since otherwise, it won't get refreshed
setItem(getTextArea().getText());
//Example, provide TableRow and index to get Object of TableView in callback implementation
commitChange.call(new TableCellChangeInfo(getTableRow(), getTableRow().getIndex(), getTextArea().getText()));
}
}
});
...
}
...
}
In cell factory, you just store committed value to the object or do whatever necessary to make it permanent:
col.setCellFactory(new Callback<TableColumn<Object, String>, TableCell<Object, String>>() {
#Override
public TableCell<Object, String> call(TableColumn<Object, String> p) {
return new SimpleEditingTextTableCell(cellChange -> {
TableCellChangeInfo changeInfo = (TableCellChangeInfo)cellChange;
Object obj = myTableView.getItems().get(changeInfo.getRowIndex());
//Save committed value to the object in tableview (and maybe to DB)
obj.field = changeInfo.getChangedObj().toString();
return true;
});
}
});
So far, I have not been able to find any problems with this workaround. On the other hand, I haven't been yet done extensive testing on this either.
EDIT: Well, after some testing noticed, the workaround was working well with big data in tableview but with empty tableview cell was not getting updated after focus lost, only when double clicking it again. There would be ways to refresh table view but that too much hacking for me...
EDIT2: Added setItem(getTextArea().getText()); before calling callback -> works with empty tableview as well.
With reservation of this being a dumb suggestion. Seems too easy. But why don't you just override TableCell#cancelEdit() and save the values manually when it is invoked? When the cell loses focus, cancelEdit() is always invoked to cancel the edit.
class EditableCell extends TableCell<ObservableList<StringProperty>, String> {
private TextField textfield = new TextField();
private int colIndex;
private String originalValue = null;
public EditableCell(int colIndex) {
this.colIndex = colIndex;
textfield.prefHeightProperty().bind(heightProperty().subtract(2.0d));
this.setPadding(new Insets(0));
this.setAlignment(Pos.CENTER);
textfield.setOnAction(e -> {
cancelEdit();
});
textfield.setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.ESCAPE)) {
textfield.setText(originalValue);
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (isEmpty()) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
textfield.setText(item);
setGraphic(textfield);
setText(null);
} else {
setText(item);
setGraphic(null);
}
}
}
#Override
public void startEdit() {
super.startEdit();
originalValue = getItem();
textfield.setText(getItem());
setGraphic(textfield);
setText(null);
}
#Override
public void cancelEdit() {
super.cancelEdit();
setGraphic(null);
setText(textfield.getText());
ObservableList<StringProperty> row = getTableView().getItems().get(getIndex());
row.get(colIndex).set(getText());
}
}
I don't know. Maybe I'm missing something. But it seems to work for me.
Update: Added cancel edit functionality. You can now cancel the edit by pressing escape while focusing the text field. Also added so that you can save the edit by pressing enter while focusing the textfield.
I prefer building as much as possible on the existing code, and since this behaviour is still not fixed w/ Java 10, here's a more general approach based on J. Duke's solution from bug: JDK-8089311.
public class TextFieldTableCellAutoCmt<S, T> extends TextFieldTableCell<S, T> {
protected TextField txtFldRef;
protected boolean isEdit;
public TextFieldTableCellAutoCmt() {
this(null);
}
public TextFieldTableCellAutoCmt(final StringConverter<T> conv) {
super(conv);
}
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn() {
return forTableColumn(new DefaultStringConverter());
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(final StringConverter<T> conv) {
return list -> new TextFieldTableCellAutoCmt<S, T>(conv);
}
#Override
public void startEdit() {
super.startEdit();
isEdit = true;
if (updTxtFldRef()) {
txtFldRef.focusedProperty().addListener(this::onFocusChg);
txtFldRef.setOnKeyPressed(this::onKeyPrs);
}
}
/**
* #return whether {#link #txtFldRef} has been changed
*/
protected boolean updTxtFldRef() {
final Node g = getGraphic();
final boolean isUpd = g != null && txtFldRef != g;
if (isUpd) {
txtFldRef = g instanceof TextField ? (TextField) g : null;
}
return isUpd;
}
#Override
public void commitEdit(final T valNew) {
if (isEditing()) {
super.commitEdit(valNew);
} else {
final TableView<S> tbl = getTableView();
if (tbl != null) {
final TablePosition<S, T> pos = new TablePosition<>(tbl, getTableRow().getIndex(), getTableColumn()); // instead of tbl.getEditingCell()
final CellEditEvent<S, T> ev = new CellEditEvent<>(tbl, pos, TableColumn.editCommitEvent(), valNew);
Event.fireEvent(getTableColumn(), ev);
}
updateItem(valNew, false);
if (tbl != null) {
tbl.edit(-1, null);
}
// TODO ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tbl);
}
}
public void onFocusChg(final ObservableValue<? extends Boolean> obs, final boolean v0, final boolean v1) {
if (isEdit && !v1) {
commitEdit(getConverter().fromString(txtFldRef.getText()));
}
}
protected void onKeyPrs(final KeyEvent e) {
switch (e.getCode()) {
case ESCAPE:
isEdit = false;
cancelEdit(); // see CellUtils#createTextField(...)
e.consume();
break;
case TAB:
if (e.isShiftDown()) {
getTableView().getSelectionModel().selectPrevious();
} else {
getTableView().getSelectionModel().selectNext();
}
e.consume();
break;
case UP:
getTableView().getSelectionModel().selectAboveCell();
e.consume();
break;
case DOWN:
getTableView().getSelectionModel().selectBelowCell();
e.consume();
break;
default:
break;
}
}
}
I found a simple solution to this, one just needs to provide the commit function to column specific to data type:
TableColumn msgstr = new TableColumn("msgstr");
msgstr.setMinWidth(100);
msgstr.prefWidthProperty().bind(widthProperty().divide(3));
msgstr.setCellValueFactory(
new PropertyValueFactory<>("msgstr")
);
msgstr.setOnEditCommit(new EventHandler<CellEditEvent<PoEntry, String>>() {
#Override
public void handle(CellEditEvent<PoEntry, String> t) {
((PoEntry)t.getTableView().getItems().get(t.getTablePosition().getRow())).setMsgstr(t.getNewValue());
}
});
Since TextFieldTableCell is missing functionality (as reckoned in the JDK bug tracker), an alternative solution may work. Forget about TextFieldTableCell and use a custom TableCell class with a TextField in it. The custom TableCell:
public class CommentCell extends TableCell<ListItem, String> {
private final TextField comment = new TextField();
public CommentCell() {
this.comment.setMaxWidth( Integer.MAX_VALUE );
this.comment.setDisable( true );
this.comment.focusedProperty().addListener( new ChangeListener<Boolean>() {
#Override
public void changed( ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue,
Boolean newPropertyValue ) {
if ( !newPropertyValue ) {
// Binding the TextField text to the model
MainController.getInstance().setComment( getTableRow().getIndex(), comment.getText() );
}
}
} );
this.setGraphic( this.comment );
}
#Override
protected void updateItem( String s, boolean empty ) {
// Checking if the TextField should be editable (based on model condition)
if ( MainController.getInstance().isDependency( getTableRow().getIndex() ) ) {
this.comment.setDisable( false );
this.comment.setEditable( true );
}
// Setting the model value as the text for the TextField
if ( s != null && !s.isEmpty() ) {
this.comment.setText( s );
}
}
}
The UI display might differ from a TextFieldTableCell but at least, it allows for better usability:
UI Display
You'll need:
a CellEditor;
a TableCell or TreeCell subclass; and
a cell factory method.
For more details on the classes shown below, see:
CellEditor
AltTableCell
AltTreeCell
CellEditor
The CellEditor is responsible for handling the Esc and Enter, as well as focus loss:
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.input.KeyEvent;
import java.util.function.Consumer;
import static javafx.application.Platform.runLater;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import static javafx.scene.input.KeyEvent.KEY_RELEASED;
public class CellEditor {
private FocusListener mFocusListener;
private final Property<String> mInputText = new SimpleStringProperty();
private final Consumer<String> mConsumer;
/**
* Responsible for accepting the text when users press the Enter or Tab key.
*/
private class KeyHandler implements EventHandler<KeyEvent> {
#Override
public void handle( final KeyEvent event ) {
if( event.getCode() == ENTER || event.getCode() == TAB ) {
commitEdit();
event.consume();
}
}
}
/**
* Responsible for committing edits when focus is lost. This will also
* deselect the input field when focus is gained so that typing text won't
* overwrite the entire existing text.
*/
private class FocusListener implements ChangeListener<Boolean> {
private final TextField mInput;
private FocusListener( final TextField input ) {
mInput = input;
}
#Override
public void changed(
final ObservableValue<? extends Boolean> c,
final Boolean endedFocus, final Boolean beganFocus ) {
if( beganFocus ) {
runLater( mInput::deselect );
}
else if( endedFocus ) {
commitEdit();
}
}
}
/**
* Generalized cell editor suitable for use with {#link TableCell} or
* {#link TreeCell} instances.
*
* #param consumer Converts the field input text to the required
* data type.
* #param graphicProperty Defines the graphical user input field.
*/
public CellEditor(
final Consumer<String> consumer,
final ObjectProperty<Node> graphicProperty ) {
assert consumer != null;
mConsumer = consumer;
init( graphicProperty );
}
private void init( final ObjectProperty<Node> graphicProperty ) {
final var keyHandler = new KeyHandler();
// When the text field is added as the graphics context, we hook into
// the changed value to get a handle on the text field. From there it is
// possible to add change the keyboard and focus behaviours.
graphicProperty.addListener( ( c, o, n ) -> {
if( o instanceof TextField ) {
o.removeEventHandler( KEY_RELEASED, keyHandler );
o.focusedProperty().removeListener( mFocusListener );
}
if( n instanceof final TextField input ) {
n.addEventFilter( KEY_RELEASED, keyHandler );
mInputText.bind( input.textProperty() );
mFocusListener = new FocusListener( input );
n.focusedProperty().addListener( mFocusListener );
}
} );
}
private void commitEdit() {
mConsumer.accept( mInputText.getValue() );
}
}
AltTableCell
The only difference between an AltTableCell and an AltTreeCell is the inheritance hierarchy. They are otherwise identical:
public class AltTableCell<S, T> extends TextFieldTableCell<S, T> {
public AltTableCell( final StringConverter<T> converter ) {
super( converter );
assert converter != null;
new CellEditor(
input -> commitEdit( getConverter().fromString( input ) ),
graphicProperty()
);
}
}
To be concrete, the AltTreeCell would begin:
public class AltTreeCell<T> extends TextFieldTreeCell<T>
Cell factory method
Assign the alternate table cell to the table column's cell factory:
final var column = new TableColumn<Entry<K, V>, T>( label );
column.setEditable( true );
column.setCellFactory(
tableColumn -> new AltTableCell<>(
new StringConverter<>() {
#Override
public String toString( final T object ) {
return object.toString();
}
#Override
public T fromString( final String string ) {
return (T) string;
}
}
)
);
For a tree cell, it's fairly similar:
final var view = new TreeView<>(); // ...
view.setEditable( true );
view.setCellFactory( treeView -> new AltTreeCell<>( converter ) );

How to enable commit on focusLost for TableView/TreeTableView?

Is there any simple approach to let the TreeTableView (or TableView) try to commit values on focus lost?
Unfortunatly I didn't succed with any default-implementations of javafx TableCellFactories, which is why I tried my own TreeTableCell implementations and also some different tableCell implementations like the one from Graham Smith, which seemed the most straight forward, since it already implemented a hook for focus lost, but nevertheless the value is never committed and the userchanges are resetted to the original value.
My guess is, whenever focus is lost, the editingProperty of the affected Cell is always already false which causes the Cell never to commit a value on focusLost. Here the relevant part from the original (oracle-)TreeTableCell Implementation (8u20ea), which causes my approaches to fail:
#Override public void commitEdit(T newValue) {
if (! isEditing()) return; // <-- here my approaches are blocked, because on focus lost its not editing anymore.
final TreeTableView<S> table = getTreeTableView();
if (table != null) {
#SuppressWarnings("unchecked")
TreeTablePosition<S,T> editingCell = (TreeTablePosition<S,T>) table.getEditingCell();
// Inform the TableView of the edit being ready to be committed.
CellEditEvent<S,T> editEvent = new CellEditEvent<S,T>(
table,
editingCell,
TreeTableColumn.<S,T>editCommitEvent(),
newValue
);
Event.fireEvent(getTableColumn(), editEvent);
}
// inform parent classes of the commit, so that they can switch us
// out of the editing state.
// This MUST come before the updateItem call below, otherwise it will
// call cancelEdit(), resulting in both commit and cancel events being
// fired (as identified in RT-29650)
super.commitEdit(newValue);
// update the item within this cell, so that it represents the new value
updateItem(newValue, false);
if (table != null) {
// reset the editing cell on the TableView
table.edit(-1, null);
// request focus back onto the table, only if the current focus
// owner has the table as a parent (otherwise the user might have
// clicked out of the table entirely and given focus to something else.
// It would be rude of us to request it back again.
ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
}
}
I succeded with overriding this method and commiting the value "by hand" before the original commitEdit() method is called, but this causes the commit on keys like enter to commit the value twice (on key + on focus lost). Moreover I dont really like my approach at all, so I wonder, if anyone else has solved this in a "nicer" way?
After some digging, turned out that the culprit (aka: the collaborator that cancels the edit before the textField looses focus) is the TableCellBehaviour/Base in its processing of a mousePressed:
mousePressed calls simpleSelect(..)
on detecting a single click it calls edit(-1, null)
which calls the same method on TableView
which sets its editingCell property to null
a tableCell listens to that property and reacts by canceling its own edit
Unfortunately, a hackaround requires 3 collaborators
a TableView with additional api to terminate an edit
a TableCellBehaviour with overridden simpleSelect(...) that calls the additional api (instead of edit(-1..)) before calling super
a TableCell that is configured with the extended behaviour and is aware of table's extended properties
Some code snippets (full code) :
// on XTableView:
public void terminateEdit() {
if (!isEditing()) return;
// terminatingCell is a property that supporting TableCells can listen to
setTerminatingCell(getEditingCell());
if (isEditing()) throw new IllegalStateException(
"expected editing to be terminated but was " + getEditingCell());
setTerminatingCell(null);
}
// on XTableCellBehaviour: override simpleSelect
#Override
protected void simpleSelect(MouseEvent e) {
TableCell<S, T> cell = getControl();
TableView<S> table = cell.getTableColumn().getTableView();
if (table instanceof XTableView) {
((XTableView<S>) table).terminateEdit();
}
super.simpleSelect(e);
}
// on XTextFieldTableCell - this method is called from listener
// to table's terminatingCell property
protected void terminateEdit(TablePosition<S, ?> newPosition) {
if (!isEditing() || !match(newPosition)) return;
commitEdit();
}
protected void commitEdit() {
T edited = getConverter().fromString(myTextField.getText());
commitEdit(edited);
}
/**
* Implemented to create XTableCellSkin which supports terminating edits.
*/
#Override
protected Skin<?> createDefaultSkin() {
return new XTableCellSkin<S, T>(this);
}
Note: the implementation of TableCellBehaviour changed massively between jdk8u5 and jdk8u20 (joys of hacking - not fit for production use ;-) - the method to override in the latter is handleClicks(..)
BTW: massive votingfor JDK-8089514 (was RT-18492 in old jira) might speed up a core fix. Unfortunately, at least the author role is needed to vote/comment bugs in the new tracker.
I also needed this functionality and did some study. I faced some stability issues with XTableView hacking mentioned above.
As problem seems to be commitEdit() won't take effect when focus is lost, why you don't just call your own commit callback from TableCell as follows:
public class SimpleEditingTextTableCell extends TableCell {
private TextArea textArea;
Callback commitChange;
public SimpleEditingTextTableCell(Callback commitChange) {
this.commitChange = commitChange;
}
#Override
public void startEdit() {
...
getTextArea().focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) {
if (!arg2) {
//commitEdit is replaced with own callback
//commitEdit(getTextArea().getText());
//Update item now since otherwise, it won't get refreshed
setItem(getTextArea().getText());
//Example, provide TableRow and index to get Object of TableView in callback implementation
commitChange.call(new TableCellChangeInfo(getTableRow(), getTableRow().getIndex(), getTextArea().getText()));
}
}
});
...
}
...
}
In cell factory, you just store committed value to the object or do whatever necessary to make it permanent:
col.setCellFactory(new Callback<TableColumn<Object, String>, TableCell<Object, String>>() {
#Override
public TableCell<Object, String> call(TableColumn<Object, String> p) {
return new SimpleEditingTextTableCell(cellChange -> {
TableCellChangeInfo changeInfo = (TableCellChangeInfo)cellChange;
Object obj = myTableView.getItems().get(changeInfo.getRowIndex());
//Save committed value to the object in tableview (and maybe to DB)
obj.field = changeInfo.getChangedObj().toString();
return true;
});
}
});
So far, I have not been able to find any problems with this workaround. On the other hand, I haven't been yet done extensive testing on this either.
EDIT: Well, after some testing noticed, the workaround was working well with big data in tableview but with empty tableview cell was not getting updated after focus lost, only when double clicking it again. There would be ways to refresh table view but that too much hacking for me...
EDIT2: Added setItem(getTextArea().getText()); before calling callback -> works with empty tableview as well.
With reservation of this being a dumb suggestion. Seems too easy. But why don't you just override TableCell#cancelEdit() and save the values manually when it is invoked? When the cell loses focus, cancelEdit() is always invoked to cancel the edit.
class EditableCell extends TableCell<ObservableList<StringProperty>, String> {
private TextField textfield = new TextField();
private int colIndex;
private String originalValue = null;
public EditableCell(int colIndex) {
this.colIndex = colIndex;
textfield.prefHeightProperty().bind(heightProperty().subtract(2.0d));
this.setPadding(new Insets(0));
this.setAlignment(Pos.CENTER);
textfield.setOnAction(e -> {
cancelEdit();
});
textfield.setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.ESCAPE)) {
textfield.setText(originalValue);
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (isEmpty()) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
textfield.setText(item);
setGraphic(textfield);
setText(null);
} else {
setText(item);
setGraphic(null);
}
}
}
#Override
public void startEdit() {
super.startEdit();
originalValue = getItem();
textfield.setText(getItem());
setGraphic(textfield);
setText(null);
}
#Override
public void cancelEdit() {
super.cancelEdit();
setGraphic(null);
setText(textfield.getText());
ObservableList<StringProperty> row = getTableView().getItems().get(getIndex());
row.get(colIndex).set(getText());
}
}
I don't know. Maybe I'm missing something. But it seems to work for me.
Update: Added cancel edit functionality. You can now cancel the edit by pressing escape while focusing the text field. Also added so that you can save the edit by pressing enter while focusing the textfield.
I prefer building as much as possible on the existing code, and since this behaviour is still not fixed w/ Java 10, here's a more general approach based on J. Duke's solution from bug: JDK-8089311.
public class TextFieldTableCellAutoCmt<S, T> extends TextFieldTableCell<S, T> {
protected TextField txtFldRef;
protected boolean isEdit;
public TextFieldTableCellAutoCmt() {
this(null);
}
public TextFieldTableCellAutoCmt(final StringConverter<T> conv) {
super(conv);
}
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> forTableColumn() {
return forTableColumn(new DefaultStringConverter());
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> forTableColumn(final StringConverter<T> conv) {
return list -> new TextFieldTableCellAutoCmt<S, T>(conv);
}
#Override
public void startEdit() {
super.startEdit();
isEdit = true;
if (updTxtFldRef()) {
txtFldRef.focusedProperty().addListener(this::onFocusChg);
txtFldRef.setOnKeyPressed(this::onKeyPrs);
}
}
/**
* #return whether {#link #txtFldRef} has been changed
*/
protected boolean updTxtFldRef() {
final Node g = getGraphic();
final boolean isUpd = g != null && txtFldRef != g;
if (isUpd) {
txtFldRef = g instanceof TextField ? (TextField) g : null;
}
return isUpd;
}
#Override
public void commitEdit(final T valNew) {
if (isEditing()) {
super.commitEdit(valNew);
} else {
final TableView<S> tbl = getTableView();
if (tbl != null) {
final TablePosition<S, T> pos = new TablePosition<>(tbl, getTableRow().getIndex(), getTableColumn()); // instead of tbl.getEditingCell()
final CellEditEvent<S, T> ev = new CellEditEvent<>(tbl, pos, TableColumn.editCommitEvent(), valNew);
Event.fireEvent(getTableColumn(), ev);
}
updateItem(valNew, false);
if (tbl != null) {
tbl.edit(-1, null);
}
// TODO ControlUtils.requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(tbl);
}
}
public void onFocusChg(final ObservableValue<? extends Boolean> obs, final boolean v0, final boolean v1) {
if (isEdit && !v1) {
commitEdit(getConverter().fromString(txtFldRef.getText()));
}
}
protected void onKeyPrs(final KeyEvent e) {
switch (e.getCode()) {
case ESCAPE:
isEdit = false;
cancelEdit(); // see CellUtils#createTextField(...)
e.consume();
break;
case TAB:
if (e.isShiftDown()) {
getTableView().getSelectionModel().selectPrevious();
} else {
getTableView().getSelectionModel().selectNext();
}
e.consume();
break;
case UP:
getTableView().getSelectionModel().selectAboveCell();
e.consume();
break;
case DOWN:
getTableView().getSelectionModel().selectBelowCell();
e.consume();
break;
default:
break;
}
}
}
I found a simple solution to this, one just needs to provide the commit function to column specific to data type:
TableColumn msgstr = new TableColumn("msgstr");
msgstr.setMinWidth(100);
msgstr.prefWidthProperty().bind(widthProperty().divide(3));
msgstr.setCellValueFactory(
new PropertyValueFactory<>("msgstr")
);
msgstr.setOnEditCommit(new EventHandler<CellEditEvent<PoEntry, String>>() {
#Override
public void handle(CellEditEvent<PoEntry, String> t) {
((PoEntry)t.getTableView().getItems().get(t.getTablePosition().getRow())).setMsgstr(t.getNewValue());
}
});
Since TextFieldTableCell is missing functionality (as reckoned in the JDK bug tracker), an alternative solution may work. Forget about TextFieldTableCell and use a custom TableCell class with a TextField in it. The custom TableCell:
public class CommentCell extends TableCell<ListItem, String> {
private final TextField comment = new TextField();
public CommentCell() {
this.comment.setMaxWidth( Integer.MAX_VALUE );
this.comment.setDisable( true );
this.comment.focusedProperty().addListener( new ChangeListener<Boolean>() {
#Override
public void changed( ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue,
Boolean newPropertyValue ) {
if ( !newPropertyValue ) {
// Binding the TextField text to the model
MainController.getInstance().setComment( getTableRow().getIndex(), comment.getText() );
}
}
} );
this.setGraphic( this.comment );
}
#Override
protected void updateItem( String s, boolean empty ) {
// Checking if the TextField should be editable (based on model condition)
if ( MainController.getInstance().isDependency( getTableRow().getIndex() ) ) {
this.comment.setDisable( false );
this.comment.setEditable( true );
}
// Setting the model value as the text for the TextField
if ( s != null && !s.isEmpty() ) {
this.comment.setText( s );
}
}
}
The UI display might differ from a TextFieldTableCell but at least, it allows for better usability:
UI Display
You'll need:
a CellEditor;
a TableCell or TreeCell subclass; and
a cell factory method.
For more details on the classes shown below, see:
CellEditor
AltTableCell
AltTreeCell
CellEditor
The CellEditor is responsible for handling the Esc and Enter, as well as focus loss:
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.input.KeyEvent;
import java.util.function.Consumer;
import static javafx.application.Platform.runLater;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import static javafx.scene.input.KeyEvent.KEY_RELEASED;
public class CellEditor {
private FocusListener mFocusListener;
private final Property<String> mInputText = new SimpleStringProperty();
private final Consumer<String> mConsumer;
/**
* Responsible for accepting the text when users press the Enter or Tab key.
*/
private class KeyHandler implements EventHandler<KeyEvent> {
#Override
public void handle( final KeyEvent event ) {
if( event.getCode() == ENTER || event.getCode() == TAB ) {
commitEdit();
event.consume();
}
}
}
/**
* Responsible for committing edits when focus is lost. This will also
* deselect the input field when focus is gained so that typing text won't
* overwrite the entire existing text.
*/
private class FocusListener implements ChangeListener<Boolean> {
private final TextField mInput;
private FocusListener( final TextField input ) {
mInput = input;
}
#Override
public void changed(
final ObservableValue<? extends Boolean> c,
final Boolean endedFocus, final Boolean beganFocus ) {
if( beganFocus ) {
runLater( mInput::deselect );
}
else if( endedFocus ) {
commitEdit();
}
}
}
/**
* Generalized cell editor suitable for use with {#link TableCell} or
* {#link TreeCell} instances.
*
* #param consumer Converts the field input text to the required
* data type.
* #param graphicProperty Defines the graphical user input field.
*/
public CellEditor(
final Consumer<String> consumer,
final ObjectProperty<Node> graphicProperty ) {
assert consumer != null;
mConsumer = consumer;
init( graphicProperty );
}
private void init( final ObjectProperty<Node> graphicProperty ) {
final var keyHandler = new KeyHandler();
// When the text field is added as the graphics context, we hook into
// the changed value to get a handle on the text field. From there it is
// possible to add change the keyboard and focus behaviours.
graphicProperty.addListener( ( c, o, n ) -> {
if( o instanceof TextField ) {
o.removeEventHandler( KEY_RELEASED, keyHandler );
o.focusedProperty().removeListener( mFocusListener );
}
if( n instanceof final TextField input ) {
n.addEventFilter( KEY_RELEASED, keyHandler );
mInputText.bind( input.textProperty() );
mFocusListener = new FocusListener( input );
n.focusedProperty().addListener( mFocusListener );
}
} );
}
private void commitEdit() {
mConsumer.accept( mInputText.getValue() );
}
}
AltTableCell
The only difference between an AltTableCell and an AltTreeCell is the inheritance hierarchy. They are otherwise identical:
public class AltTableCell<S, T> extends TextFieldTableCell<S, T> {
public AltTableCell( final StringConverter<T> converter ) {
super( converter );
assert converter != null;
new CellEditor(
input -> commitEdit( getConverter().fromString( input ) ),
graphicProperty()
);
}
}
To be concrete, the AltTreeCell would begin:
public class AltTreeCell<T> extends TextFieldTreeCell<T>
Cell factory method
Assign the alternate table cell to the table column's cell factory:
final var column = new TableColumn<Entry<K, V>, T>( label );
column.setEditable( true );
column.setCellFactory(
tableColumn -> new AltTableCell<>(
new StringConverter<>() {
#Override
public String toString( final T object ) {
return object.toString();
}
#Override
public T fromString( final String string ) {
return (T) string;
}
}
)
);
For a tree cell, it's fairly similar:
final var view = new TreeView<>(); // ...
view.setEditable( true );
view.setCellFactory( treeView -> new AltTreeCell<>( converter ) );

Categories

Resources