Created
February 13, 2015 03:18
-
-
Save james-d/be5bbd6255a4640a5357 to your computer and use it in GitHub Desktop.
(Fairly) reusable edit cell that commits on loss of focus on the text field. Overriding the commitEdit(...) method is difficult to do without relying on knowing the default implementation, which I had to do here. The test code includes a key handler on the table that initiates editing on a key press.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javafx.event.Event; | |
import javafx.scene.control.ContentDisplay; | |
import javafx.scene.control.TableCell; | |
import javafx.scene.control.TableColumn; | |
import javafx.scene.control.TableColumn.CellEditEvent; | |
import javafx.scene.control.TablePosition; | |
import javafx.scene.control.TableView; | |
import javafx.scene.control.TextField; | |
import javafx.scene.input.KeyCode; | |
import javafx.scene.input.KeyEvent; | |
import javafx.util.StringConverter; | |
public class EditCell<S, T> extends TableCell<S, T> { | |
// Text field for editing | |
// TODO: allow this to be a plugable control. | |
private final TextField textField = new TextField(); | |
// Converter for converting the text in the text field to the user type, and vice-versa: | |
private final StringConverter<T> converter ; | |
public EditCell(StringConverter<T> converter) { | |
this.converter = converter ; | |
itemProperty().addListener((obx, oldItem, newItem) -> { | |
if (newItem == null) { | |
setText(null); | |
} else { | |
setText(converter.toString(newItem)); | |
} | |
}); | |
setGraphic(textField); | |
setContentDisplay(ContentDisplay.TEXT_ONLY); | |
textField.setOnAction(evt -> { | |
commitEdit(this.converter.fromString(textField.getText())); | |
}); | |
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> { | |
if (! isNowFocused) { | |
commitEdit(this.converter.fromString(textField.getText())); | |
} | |
}); | |
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { | |
if (event.getCode() == KeyCode.ESCAPE) { | |
textField.setText(converter.toString(getItem())); | |
cancelEdit(); | |
event.consume(); | |
} else if (event.getCode() == KeyCode.RIGHT) { | |
getTableView().getSelectionModel().selectRightCell(); | |
event.consume(); | |
} else if (event.getCode() == KeyCode.LEFT) { | |
getTableView().getSelectionModel().selectLeftCell(); | |
event.consume(); | |
} else if (event.getCode() == KeyCode.UP) { | |
getTableView().getSelectionModel().selectAboveCell(); | |
event.consume(); | |
} else if (event.getCode() == KeyCode.DOWN) { | |
getTableView().getSelectionModel().selectBelowCell(); | |
event.consume(); | |
} | |
}); | |
} | |
/** | |
* Convenience converter that does nothing (converts Strings to themselves and vice-versa...). | |
*/ | |
public static final StringConverter<String> IDENTITY_CONVERTER = new StringConverter<String>() { | |
@Override | |
public String toString(String object) { | |
return object; | |
} | |
@Override | |
public String fromString(String string) { | |
return string; | |
} | |
}; | |
/** | |
* Convenience method for creating an EditCell for a String value. | |
* @return | |
*/ | |
public static <S> EditCell<S, String> createStringEditCell() { | |
return new EditCell<S, String>(IDENTITY_CONVERTER); | |
} | |
// set the text of the text field and display the graphic | |
@Override | |
public void startEdit() { | |
super.startEdit(); | |
textField.setText(converter.toString(getItem())); | |
setContentDisplay(ContentDisplay.GRAPHIC_ONLY); | |
textField.requestFocus(); | |
} | |
// revert to text display | |
@Override | |
public void cancelEdit() { | |
super.cancelEdit(); | |
setContentDisplay(ContentDisplay.TEXT_ONLY); | |
} | |
// commits the edit. Update property if possible and revert to text display | |
@Override | |
public void commitEdit(T item) { | |
// This block is necessary to support commit on losing focus, because the baked-in mechanism | |
// sets our editing state to false before we can intercept the loss of focus. | |
// The default commitEdit(...) method simply bails if we are not editing... | |
if (! isEditing() && ! item.equals(getItem())) { | |
TableView<S> table = getTableView(); | |
if (table != null) { | |
TableColumn<S, T> column = getTableColumn(); | |
CellEditEvent<S, T> event = new CellEditEvent<>(table, | |
new TablePosition<S,T>(table, getIndex(), column), | |
TableColumn.editCommitEvent(), item); | |
Event.fireEvent(column, event); | |
} | |
} | |
super.commitEdit(item); | |
setContentDisplay(ContentDisplay.TEXT_ONLY); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.function.Function; | |
import javafx.application.Application; | |
import javafx.beans.property.SimpleStringProperty; | |
import javafx.beans.property.StringProperty; | |
import javafx.scene.Scene; | |
import javafx.scene.control.Button; | |
import javafx.scene.control.TableColumn; | |
import javafx.scene.control.TablePosition; | |
import javafx.scene.control.TableView; | |
import javafx.scene.layout.BorderPane; | |
import javafx.stage.Stage; | |
public class TableViewEditOnType extends Application { | |
@Override | |
public void start(Stage primaryStage) { | |
TableView<Person> table = new TableView<>(); | |
table.getSelectionModel().setCellSelectionEnabled(true); | |
table.setEditable(true); | |
table.getColumns().add(createColumn("First Name", Person::firstNameProperty)); | |
table.getColumns().add(createColumn("Last Name", Person::lastNameProperty)); | |
table.getColumns().add(createColumn("Email", Person::emailProperty)); | |
table.getItems().addAll( | |
new Person("Jacob", "Smith", "[email protected]"), | |
new Person("Isabella", "Johnson", "[email protected]"), | |
new Person("Ethan", "Williams", "[email protected]"), | |
new Person("Emma", "Jones", "[email protected]"), | |
new Person("Michael", "Brown", "[email protected]") | |
); | |
table.setOnKeyPressed(event -> { | |
TablePosition<Person, ?> pos = table.getFocusModel().getFocusedCell() ; | |
if (pos != null && event.getCode().isLetterKey()) { | |
table.edit(pos.getRow(), pos.getTableColumn()); | |
} | |
}); | |
Button showDataButton = new Button("Debug data"); | |
showDataButton.setOnAction(event -> table.getItems().stream() | |
.map(p -> String.format("%s %s", p.getFirstName(), p.getLastName())) | |
.forEach(System.out::println)); | |
Scene scene = new Scene(new BorderPane(table, null, null, showDataButton, null), 880, 600); | |
primaryStage.setScene(scene); | |
primaryStage.show(); | |
} | |
private <T> TableColumn<T, String> createColumn(String title, Function<T, StringProperty> property) { | |
TableColumn<T, String> col = new TableColumn<>(title); | |
col.setCellValueFactory(cellData -> property.apply(cellData.getValue())); | |
col.setCellFactory(column -> EditCell.createStringEditCell()); | |
return col ; | |
} | |
public static class Person { | |
private final StringProperty firstName = new SimpleStringProperty(); | |
private final StringProperty lastName = new SimpleStringProperty(); | |
private final StringProperty email = new SimpleStringProperty(); | |
public Person(String firstName, String lastName, String email) { | |
setFirstName(firstName); | |
setLastName(lastName); | |
setEmail(email); | |
} | |
public final StringProperty firstNameProperty() { | |
return this.firstName; | |
} | |
public final java.lang.String getFirstName() { | |
return this.firstNameProperty().get(); | |
} | |
public final void setFirstName(final java.lang.String firstName) { | |
this.firstNameProperty().set(firstName); | |
} | |
public final StringProperty lastNameProperty() { | |
return this.lastName; | |
} | |
public final java.lang.String getLastName() { | |
return this.lastNameProperty().get(); | |
} | |
public final void setLastName(final java.lang.String lastName) { | |
this.lastNameProperty().set(lastName); | |
} | |
public final StringProperty emailProperty() { | |
return this.email; | |
} | |
public final java.lang.String getEmail() { | |
return this.emailProperty().get(); | |
} | |
public final void setEmail(final java.lang.String email) { | |
this.emailProperty().set(email); | |
} | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
To anyone seeing this: to fix the issue where it won't input the first character you type on the first edit made by using the "type to start an edit" functionality, modify the startEdit function to include textField.applyCss()
and textField.selectAll()
at the bottom of it, in that order.
Solution credits: https://stackoverflow.com/questions/68192215/how-do-i-edit-tableview-cell-on-numeric-key-release-and-have-the-key-that-is-pre#comment127182621_68667720
@james-d Would you mind putting a License on this gist?
@james-d Your code works fine. But if i apply TextFormatter for input validation then I cannot edit the TableCell more than once. Has anyone added a TextFormatter in above code and checked if they are able to edit more than once?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@sgyeme I have the same issue but cannot find a clear solution nor cause. Have you been able to fix / workaround it?