Last active
April 8, 2023 08:07
-
-
Save eckig/30abf0d7d51b7756c2e7 to your computer and use it in GitHub Desktop.
TextAreaTableCell, see Stack Overflow question: http://stackoverflow.com/questions/27666382/editable-multiline-cell-in-tableview
This file contains 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.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()); | |
} | |
} |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Should not be
textArea.appendText("\n")
, but rathertextArea.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.