Skip to content

Instantly share code, notes, and snippets.

@james-d
Created February 13, 2015 03:18
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save james-d/be5bbd6255a4640a5357 to your computer and use it in GitHub Desktop.
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.
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);
}
}
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", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);
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);
}
}
@mihahauke
Copy link

Thank You. It was helpful.

@SohanChy
Copy link

Thanks, I wasted several hours trying to get similar functionality and your code has solved all of my needs.
👍

@abdallhahmed070
Copy link

Thank you very much your code help me
You are the best

@Telvozzzar
Copy link

working smoothly, but not somehow it overrides my functionality to check weither the displayed data should be editable.
Trying to figure this out and then letting you know how it worked.

@Telvozzzar
Copy link

Telvozzzar commented Mar 20, 2018

Callback<TableColumn<Parameter, String>, TableCell<Parameter, String>> cellFactory = new Callback<TableColumn<Parameter, String>, TableCell<Parameter, String>>() {
		@Override
		public TableCell<Parameter, String> call(TableColumn<Parameter, String> paramTableColumn) {
			return new EditCell<Parameter, String>( new DefaultStringConverter()) {
				@Override
				public void updateItem(String s, boolean b) {
					super.updateItem(s, b);

					if (! isEmpty()) {
						Parameter item = getTableView().getItems().get(getIndex());
						// Test for disable condition
						if (item != null && !item.isDummy()) {
							setEditable(false);
							getStylesheets().add("/styles/ParameterTableView.css");
						} else {
							setEditable(true);
							setStyle("");
						}
					}
				}
			};
		}
	};

i was using something like this without your EditCell class but the default TableCell

@decman2
Copy link

decman2 commented Jul 17, 2018

Works fine and helps a lot, thanks! Nonetheless, is there a possibility to directly show the textfield, when a new row is created? I tried things like calling the startEdit() function in the constructor, but the result was not like expected.

@s6ch13
Copy link

s6ch13 commented Dec 18, 2018

Hi,
Works great. thanks. I would like to use this for our project.
Can you please share copyright / license details of your above work ?
thanks

@jaysridhar
Copy link

Does not handle cursor keys properly. The default TextFieldTableCell behaves as expected. So basically this code trades one bug for another.

@trin94
Copy link

trin94 commented Jan 29, 2020

Does not handle cursor keys properly. The default TextFieldTableCell behaves as expected. So basically this code trades one bug for another.

@jaysridhar That is really easy to fix. You gotta change the behaviour in the EditCell constructor at textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> { ... });

@james-d I'd use the existing DefaultStringConverter class instead of the custom IDENTITY_CONVERTER object.

Apart from that, it's great 😃 Thank you!

@sgyeme
Copy link

sgyeme commented Mar 4, 2020

The code worked fine on jdk 8 but on jdk 11 ,javafx 11, windows 10 , I have to type a key twice before it starts editing. But any subsequent typing works fine

@jheut
Copy link

jheut commented Aug 16, 2022

@sgyeme I have the same issue but cannot find a clear solution nor cause. Have you been able to fix / workaround it?

@Warren1001
Copy link

Warren1001 commented Feb 20, 2023

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

@FlorianKroiss
Copy link

@james-d Would you mind putting a License on this gist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment