-
-
Save eckig/30abf0d7d51b7756c2e7 to your computer and use it in GitHub Desktop.
| import javafx.beans.binding.Bindings; | |
| import javafx.beans.property.ObjectProperty; | |
| import javafx.beans.property.SimpleObjectProperty; | |
| import javafx.scene.control.Cell; | |
| import javafx.scene.control.TableCell; | |
| import javafx.scene.control.TableColumn; | |
| import javafx.scene.control.TextArea; | |
| import javafx.scene.input.KeyCode; | |
| import javafx.util.Callback; | |
| import javafx.util.StringConverter; | |
| import javafx.util.converter.DefaultStringConverter; | |
| public class TextAreaTableCell<S, T> extends TableCell<S, T> { | |
| 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> converter) { | |
| return list -> new TextAreaTableCell<>(converter); | |
| } | |
| private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) { | |
| return converter == null ? cell.getItem() == null ? "" : cell.getItem() | |
| .toString() : converter.toString(cell.getItem()); | |
| } | |
| private static <T> TextArea createTextArea(final Cell<T> cell, final StringConverter<T> converter) { | |
| TextArea textArea = new TextArea(getItemText(cell, converter)); | |
| textArea.setOnKeyReleased(t -> { | |
| if (t.getCode() == KeyCode.ESCAPE) { | |
| cell.cancelEdit(); | |
| t.consume(); | |
| } | |
| else if(t.getCode() == KeyCode.ENTER && t.isShiftDown()) { | |
| if (converter == null) { | |
| throw new IllegalStateException( | |
| "Attempting to convert text input into Object, but provided " | |
| + "StringConverter is null. Be sure to set a StringConverter " | |
| + "in your cell factory."); | |
| } | |
| cell.commitEdit(converter.fromString(textArea.getText())); | |
| t.consume(); | |
| } | |
| }); | |
| textArea.prefRowCountProperty().bind(Bindings.size(textArea.getParagraphs())); | |
| return textArea; | |
| } | |
| private void startEdit(final Cell<T> cell, final StringConverter<T> converter) { | |
| textArea.setText(getItemText(cell, converter)); | |
| cell.setText(null); | |
| cell.setGraphic(textArea); | |
| textArea.selectAll(); | |
| textArea.requestFocus(); | |
| } | |
| private static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter) { | |
| cell.setText(getItemText(cell, converter)); | |
| cell.setGraphic(null); | |
| } | |
| private void updateItem(final Cell<T> cell, final StringConverter<T> converter) { | |
| if (cell.isEmpty()) { | |
| cell.setText(null); | |
| cell.setGraphic(null); | |
| } else { | |
| if (cell.isEditing()) { | |
| if (textArea != null) { | |
| textArea.setText(getItemText(cell, converter)); | |
| } | |
| cell.setText(null); | |
| cell.setGraphic(textArea); | |
| } else { | |
| cell.setText(getItemText(cell, converter)); | |
| cell.setGraphic(null); | |
| } | |
| } | |
| } | |
| private TextArea textArea; | |
| private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>(this, "converter"); | |
| public TextAreaTableCell() { | |
| this(null); | |
| } | |
| public TextAreaTableCell(StringConverter<T> converter) { | |
| this.getStyleClass().add("text-area-table-cell"); | |
| setConverter(converter); | |
| } | |
| public final ObjectProperty<StringConverter<T>> converterProperty() { | |
| return converter; | |
| } | |
| public final void setConverter(StringConverter<T> value) { | |
| converterProperty().set(value); | |
| } | |
| public final StringConverter<T> getConverter() { | |
| return converterProperty().get(); | |
| } | |
| @Override | |
| public void startEdit() { | |
| if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) { | |
| return; | |
| } | |
| super.startEdit(); | |
| if (isEditing()) { | |
| if (textArea == null) { | |
| textArea = createTextArea(this, getConverter()); | |
| } | |
| startEdit(this, getConverter()); | |
| } | |
| } | |
| @Override | |
| public void cancelEdit() { | |
| super.cancelEdit(); | |
| cancelEdit(this, getConverter()); | |
| } | |
| @Override | |
| public void updateItem(T item, boolean empty) { | |
| super.updateItem(item, empty); | |
| updateItem(this, getConverter()); | |
| } | |
| } |
Should not be textArea.appendText("\n"), but rather textArea.insertText(textArea.getCaretPosition(), "\n"). With your code, when the caret is within the text, "Enter" adds a new line at the end. It took me a long time to figure this out.
I found that in the createTextArea method using textArea.setOnKeyReleased(... let an extra newline get in there before commiting. Using textArea.setOnKeyPressed(... instead fixed that for me. (Also, thanks @limitedAtonement and @DominikStyp for the tips.)
I'm new to Gists—if you can edit them, I can't figure out how—but I do have a fork with the above-mentioned improvements.
An improvement to the text area sizing:
private void startEdit(final Cell<T> cell, final StringConverter<T> converter) {
textArea.setText(getItemText(cell, converter));
cell.setText(null);
cell.setGraphic(textArea);
// Make sure the text area stays the right size:
/* The following solution is courtesy of James_D: https://stackoverflow.com/a/22733264/5432315 */
// Perform a lookup for an element with a css class of "text"
// This will give the Node that actually renders the text inside the
// TextArea
Node text = textArea.lookup(".text");
// Bind the preferred height of the text area to the actual height of the text
// This will make the text area the height of the text, plus some padding
// of 20 pixels, as long as that height is between the text area's minHeight
// and maxHeight. The max height will be the height of its parent (usually).
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(() ->
text.getBoundsInLocal().getHeight(), text.boundsInLocalProperty()).add(20)
);
textArea.selectAll();
textArea.requestFocus();
}Thanks for this! I adapted it for a kotlin implementation that also wraps the text in the cell.
https://gist.github.com/maroc81/6c60d5ae11fdf79099e50fe308509a27
Thanks very much for this implementation - shame that Oracle didn't do this...
I think that "chat version" of the key's would be better - so if you click ENTER you confirm the change, and if you click SHIFT + ENTER you go to new line: