如何防止 TextFieldTableCell 在按下 ENTER 键时进入编辑模式?
How do you prevent a TextFieldTableCell from going into Edit mode when pressing the ENTER key?
默认情况下,在 TextFieldTableCell
上按 ENTER 会将单元格置于编辑模式并在单元格中绘制文本字段。我该如何防止这种情况?如果按下数字键,我只希望单元格进入编辑模式。如果按下 ENTER,那么它应该只是 select 下面的单元格。
我尝试过的:
tableView.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
TablePosition tp;
if(event.getCode().isDigitKey()) {
lastKey = event.getText();
tp = tableView.getFocusModel().getFocusedCell();
tableView.edit(tp.getRow() , tp.getTableColumn());
}
if(tableView.getEditingCell() == null && event.getCode() == KeyCode.ENTER) {
event.consume();
tableView.getSelectionModel().selectBelowCell();
}
}
});
单元格仍进入编辑模式。
也许您可以使用 Event Filter
来防止在按下回车键时进入编辑模式(以及 select 下面的单元格):
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if(keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell();
keyEvent.consume();
}
});
按下数字键开始编辑模式:
tableView.setOnKeyPressed(event -> {
TablePosition<Person, ?> pos = tableView.getFocusModel().getFocusedCell() ;
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
编辑:包含示例
package org.example;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class TableViewTestApp extends Application {
@Override
public void start(Stage stage) {
stage.setScene(new Scene(createTableView()));
stage.show();
}
private TableView<Item> createTableView() {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, String> nameCol = new TableColumn<>("name");
tableView.getColumns().add(nameCol);
nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameCol.setCellFactory(c -> new CustomTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
return tableView;
}
private class CustomTableCell extends TableCell<Item, String> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
commitEdit(textField.getText());
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(String 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, String> column = getTableColumn();
TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item);
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
}
}
}
private class Item {
private final StringProperty name;
public Item(String name) {
this.name = new SimpleStringProperty(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}
public static void main(String[] args) {
launch();
}
}
编辑 2:为整数添加了自定义 table 单元格(带微调器)
package org.example;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.WritableValue;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
public class TableViewTestApp extends Application {
@Override
public void start(Stage stage) {
TableView<Item> tableView = createTableView();
Button btn = new Button("print items to console");
btn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
stage.setScene(new Scene(new VBox(tableView, btn)));
stage.show();
}
private TableView<Item> createTableView() {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, String> nameCol = new TableColumn<>("name");
tableView.getColumns().add(nameCol);
nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameCol.setCellFactory(c -> new StringTextFieldTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
quantityCol.setCellFactory(c -> new IntegerSpinnerTableCell<>());
quantityCol.setMinWidth(150);
tableView.getColumns().add(quantityCol);
return tableView;
}
private class StringTextFieldTableCell extends TableCell<Item, String> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
commitEdit(textField.getText());
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(String 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, String> column = getTableColumn();
TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item);
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty)
setText(null);
else
setText(item);
}
}
public class IntegerSpinnerTableCell<T> extends TableCell<T, Integer> {
private final Spinner<Integer> spinner;
private final SpinnerValueFactory.IntegerSpinnerValueFactory valueFactory;
private boolean ignoreUpdate;
public IntegerSpinnerTableCell() {
spinner = new Spinner<>();
valueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1, 1);
spinner.setValueFactory(valueFactory);
spinner.setEditable(true);
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
}
}
return c;
};
TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
spinner.getEditor().setTextFormatter(intFormatter);
spinner.valueProperty().addListener((o, oldValue, newValue) -> {
if (!ignoreUpdate) {
ignoreUpdate = true;
WritableValue<Number> property = (WritableValue<Number>) getTableColumn()
.getCellObservableValue(getTableRow().getItem());
property.setValue(newValue);
ignoreUpdate = false;
}
});
spinner.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
ignoreUpdate = true;
setText(null);
valueFactory.setValue(item);
setGraphic(spinner);
ignoreUpdate = false;
} else {
ignoreUpdate = true;
setText(String.valueOf(item));
setGraphic(null);
ignoreUpdate = false;
}
}
}
@Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
ignoreUpdate = true;
setText(null);
setGraphic(spinner);
valueFactory.setValue(getItem());
requestEditorAndSelectText(Integer.MAX_VALUE);
ignoreUpdate = false;
}
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setGraphic(null);
}
@Override
public void commitEdit(Integer newValue) {
if (!isEditing()) {
TableView<T> table = getTableView();
if (table != null) {
TableColumn<T, Integer> column = getTableColumn();
TableColumn.CellEditEvent<T, Integer> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), spinner.getValue());
Event.fireEvent(column, event);
}
}
super.commitEdit(newValue);
setText(String.valueOf(newValue.intValue()));
setGraphic(null);
}
private void requestEditorAndSelectText(int maxTries) {
if (maxTries > 0) {
Platform.runLater(() -> {
if (!spinner.getEditor().isFocused()) {
spinner.getEditor().requestFocus();
spinner.getEditor().selectAll();
requestEditorAndSelectText(maxTries - 1);
}
}
);
}
}
}
private class Item {
private final StringProperty name;
private final IntegerProperty quantity;
public Item(String name) {
this.name = new SimpleStringProperty(name);
this.quantity = new SimpleIntegerProperty(1);
}
@Override
public String toString() {
return getName() + " | " + getQuantity();
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public int getQuantity() {
return quantity.get();
}
public IntegerProperty quantityProperty() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity.set(quantity);
}
}
public static void main(String[] args) {
launch();
}
}
编辑 3:为 IntegerTextFieldTableCell 添加示例(没有 Spinner,仅 TextField):
package org.example;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
public class TableWithPlainTextFieldTableCellForIntTestApp extends Application {
@Override
public void start(Stage stage) {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
tableView.getColumns().add(quantityCol);
quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
quantityCol.setCellFactory(c -> new IntegerTextFieldTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell();
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item()));
quantityCol.setMinWidth(150);
Button printBtn = new Button("print items");
printBtn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
stage.setScene(new Scene(new VBox(tableView, printBtn)));
stage.show();
}
class IntegerTextFieldTableCell extends TableCell<Item, Integer> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem().toString());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
try {
commitEdit(Integer.parseInt(textField.getText()));
} catch (NumberFormatException ignored) {
commitEdit(1);
}
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
}
}
return c;
};
TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
textField.setTextFormatter(intFormatter);
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().toString());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(Integer 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, Integer> column = getTableColumn();
TableColumn.CellEditEvent<Item, Integer> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item.toString());
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty)
setText(null);
else
setText(item.toString());
}
}
class Item {
private final IntegerProperty quantity = new SimpleIntegerProperty(1);
@Override
public String toString() {
return quantity.get() + "";
}
public int getQuantity() {
return quantity.get();
}
public IntegerProperty quantityProperty() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity.set(quantity);
}
}
public static void main(String[] args) {
launch();
}
}
在 table 上手动安装 filters/handlers 的替代方法是调整 table 的 InputMap。当心:那是私有的 api,需要访问内部包(输入映射和行为)和从皮肤反射抓取行为。希望这些软件包很快就会进入 public 范围。
基本方法是
- 从皮肤中获取行为及其 inputMap
- 删除要替换的键映射
- 根据需要添加新的键映射
在代码中:
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
// use your favorite utility method to reflectively access the private field
TableViewBehavior<?> listBehavior = (TableViewBehavior<?>) FXUtils.invokeGetFieldValue(
TableViewSkin.class, skin, "behavior");
InputMap<?> map = listBehavior.getInputMap();
// remove old mapping for enter
Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(ENTER));
map.getMappings().remove(mapping.get());
// add new mapping for enter
map.getMappings().add(new KeyMapping(KeyCode.ENTER, e -> table.getSelectionModel().selectBelowCell()));
// add mappings for digits
EventHandler<KeyEvent> handler = e -> {
TablePosition<T, ?> pos = table.getFocusModel().getFocusedCell() ;
if (pos != null ) {
table.edit(pos.getRow(), pos.getTableColumn());
}
};
List<KeyMapping> digitMappings = Arrays.stream(KeyCode.values())
.filter(c -> c.isDigitKey())
.map(c -> new KeyMapping(c, handler))
.collect(Collectors.toList());
map.getMappings().addAll(digitMappings);
显然,这需要 table 的皮肤可用并且它是 TableViewSkin 类型。要使用,可以在 table 的皮肤 属性:
的侦听器中调用它
table.skinProperty().addListener(c -> {
tweakInputMap(table);
});
应该在 fx 版本 >= fx9 中工作(我的上下文是 fx17/18 dev)并且需要在编译时添加导出 inputmap/behavior 包并在运行时添加打开这些包。
默认情况下,在 TextFieldTableCell
上按 ENTER 会将单元格置于编辑模式并在单元格中绘制文本字段。我该如何防止这种情况?如果按下数字键,我只希望单元格进入编辑模式。如果按下 ENTER,那么它应该只是 select 下面的单元格。
我尝试过的:
tableView.setOnKeyReleased(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
TablePosition tp;
if(event.getCode().isDigitKey()) {
lastKey = event.getText();
tp = tableView.getFocusModel().getFocusedCell();
tableView.edit(tp.getRow() , tp.getTableColumn());
}
if(tableView.getEditingCell() == null && event.getCode() == KeyCode.ENTER) {
event.consume();
tableView.getSelectionModel().selectBelowCell();
}
}
});
单元格仍进入编辑模式。
也许您可以使用 Event Filter
来防止在按下回车键时进入编辑模式(以及 select 下面的单元格):
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if(keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell();
keyEvent.consume();
}
});
按下数字键开始编辑模式:
tableView.setOnKeyPressed(event -> {
TablePosition<Person, ?> pos = tableView.getFocusModel().getFocusedCell() ;
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
编辑:包含示例
package org.example;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class TableViewTestApp extends Application {
@Override
public void start(Stage stage) {
stage.setScene(new Scene(createTableView()));
stage.show();
}
private TableView<Item> createTableView() {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, String> nameCol = new TableColumn<>("name");
tableView.getColumns().add(nameCol);
nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameCol.setCellFactory(c -> new CustomTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
return tableView;
}
private class CustomTableCell extends TableCell<Item, String> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
commitEdit(textField.getText());
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(String 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, String> column = getTableColumn();
TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item);
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
}
}
}
private class Item {
private final StringProperty name;
public Item(String name) {
this.name = new SimpleStringProperty(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}
public static void main(String[] args) {
launch();
}
}
编辑 2:为整数添加了自定义 table 单元格(带微调器)
package org.example;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.WritableValue;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
public class TableViewTestApp extends Application {
@Override
public void start(Stage stage) {
TableView<Item> tableView = createTableView();
Button btn = new Button("print items to console");
btn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
stage.setScene(new Scene(new VBox(tableView, btn)));
stage.show();
}
private TableView<Item> createTableView() {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, String> nameCol = new TableColumn<>("name");
tableView.getColumns().add(nameCol);
nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
nameCol.setCellFactory(c -> new StringTextFieldTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
quantityCol.setCellFactory(c -> new IntegerSpinnerTableCell<>());
quantityCol.setMinWidth(150);
tableView.getColumns().add(quantityCol);
return tableView;
}
private class StringTextFieldTableCell extends TableCell<Item, String> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
commitEdit(textField.getText());
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(String 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, String> column = getTableColumn();
TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item);
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty)
setText(null);
else
setText(item);
}
}
public class IntegerSpinnerTableCell<T> extends TableCell<T, Integer> {
private final Spinner<Integer> spinner;
private final SpinnerValueFactory.IntegerSpinnerValueFactory valueFactory;
private boolean ignoreUpdate;
public IntegerSpinnerTableCell() {
spinner = new Spinner<>();
valueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1, 1);
spinner.setValueFactory(valueFactory);
spinner.setEditable(true);
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
}
}
return c;
};
TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
spinner.getEditor().setTextFormatter(intFormatter);
spinner.valueProperty().addListener((o, oldValue, newValue) -> {
if (!ignoreUpdate) {
ignoreUpdate = true;
WritableValue<Number> property = (WritableValue<Number>) getTableColumn()
.getCellObservableValue(getTableRow().getItem());
property.setValue(newValue);
ignoreUpdate = false;
}
});
spinner.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
}
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
ignoreUpdate = true;
setText(null);
valueFactory.setValue(item);
setGraphic(spinner);
ignoreUpdate = false;
} else {
ignoreUpdate = true;
setText(String.valueOf(item));
setGraphic(null);
ignoreUpdate = false;
}
}
}
@Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
ignoreUpdate = true;
setText(null);
setGraphic(spinner);
valueFactory.setValue(getItem());
requestEditorAndSelectText(Integer.MAX_VALUE);
ignoreUpdate = false;
}
}
@Override
public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setGraphic(null);
}
@Override
public void commitEdit(Integer newValue) {
if (!isEditing()) {
TableView<T> table = getTableView();
if (table != null) {
TableColumn<T, Integer> column = getTableColumn();
TableColumn.CellEditEvent<T, Integer> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), spinner.getValue());
Event.fireEvent(column, event);
}
}
super.commitEdit(newValue);
setText(String.valueOf(newValue.intValue()));
setGraphic(null);
}
private void requestEditorAndSelectText(int maxTries) {
if (maxTries > 0) {
Platform.runLater(() -> {
if (!spinner.getEditor().isFocused()) {
spinner.getEditor().requestFocus();
spinner.getEditor().selectAll();
requestEditorAndSelectText(maxTries - 1);
}
}
);
}
}
}
private class Item {
private final StringProperty name;
private final IntegerProperty quantity;
public Item(String name) {
this.name = new SimpleStringProperty(name);
this.quantity = new SimpleIntegerProperty(1);
}
@Override
public String toString() {
return getName() + " | " + getQuantity();
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public int getQuantity() {
return quantity.get();
}
public IntegerProperty quantityProperty() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity.set(quantity);
}
}
public static void main(String[] args) {
launch();
}
}
编辑 3:为 IntegerTextFieldTableCell 添加示例(没有 Spinner,仅 TextField):
package org.example;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
public class TableWithPlainTextFieldTableCellForIntTestApp extends Application {
@Override
public void start(Stage stage) {
TableView<Item> tableView = new TableView<>();
tableView.setEditable(true);
tableView.getSelectionModel().setCellSelectionEnabled(true);
TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
tableView.getColumns().add(quantityCol);
quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
quantityCol.setCellFactory(c -> new IntegerTextFieldTableCell());
// Enter edit mode on digit pressed only
tableView.setOnKeyPressed(event -> {
TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
if (pos != null && event.getCode().isDigitKey()) {
tableView.edit(pos.getRow(), pos.getTableColumn());
}
});
tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
tableView.getSelectionModel().selectBelowCell();
keyEvent.consume();
}
});
IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item()));
quantityCol.setMinWidth(150);
Button printBtn = new Button("print items");
printBtn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
stage.setScene(new Scene(new VBox(tableView, printBtn)));
stage.show();
}
class IntegerTextFieldTableCell extends TableCell<Item, Integer> {
private TextField textField;
private void createTextField() {
textField = new TextField(getItem().toString());
// Commit value on focus lost:
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (wasFocused) {
try {
commitEdit(Integer.parseInt(textField.getText()));
} catch (NumberFormatException ignored) {
commitEdit(1);
}
}
});
// This is needed to commit last cell of the column on enter released:
// (***Alternative: Move to first cell of next column)
textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(getItem());
keyEvent.consume();
}
});
NumberFormat format = NumberFormat.getIntegerInstance();
UnaryOperator<TextFormatter.Change> filter = c -> {
if (c.isContentChange()) {
ParsePosition parsePosition = new ParsePosition(0);
format.parse(c.getControlNewText(), parsePosition);
if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
return null;
}
}
return c;
};
TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
textField.setTextFormatter(intFormatter);
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
setText(null);
createTextField();
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem().toString());
setGraphic(null);
}
// See: https://gist.github.com/james-d/be5bbd6255a4640a5357
@Override
public void commitEdit(Integer 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<Item> table = getTableView();
if (table != null) {
TableColumn<Item, Integer> column = getTableColumn();
TableColumn.CellEditEvent<Item, Integer> event = new TableColumn.CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setText(item.toString());
setGraphic(null);
textField = null;
}
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty)
setText(null);
else
setText(item.toString());
}
}
class Item {
private final IntegerProperty quantity = new SimpleIntegerProperty(1);
@Override
public String toString() {
return quantity.get() + "";
}
public int getQuantity() {
return quantity.get();
}
public IntegerProperty quantityProperty() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity.set(quantity);
}
}
public static void main(String[] args) {
launch();
}
}
在 table 上手动安装 filters/handlers 的替代方法是调整 table 的 InputMap。当心:那是私有的 api,需要访问内部包(输入映射和行为)和从皮肤反射抓取行为。希望这些软件包很快就会进入 public 范围。
基本方法是
- 从皮肤中获取行为及其 inputMap
- 删除要替换的键映射
- 根据需要添加新的键映射
在代码中:
TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
// use your favorite utility method to reflectively access the private field
TableViewBehavior<?> listBehavior = (TableViewBehavior<?>) FXUtils.invokeGetFieldValue(
TableViewSkin.class, skin, "behavior");
InputMap<?> map = listBehavior.getInputMap();
// remove old mapping for enter
Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(ENTER));
map.getMappings().remove(mapping.get());
// add new mapping for enter
map.getMappings().add(new KeyMapping(KeyCode.ENTER, e -> table.getSelectionModel().selectBelowCell()));
// add mappings for digits
EventHandler<KeyEvent> handler = e -> {
TablePosition<T, ?> pos = table.getFocusModel().getFocusedCell() ;
if (pos != null ) {
table.edit(pos.getRow(), pos.getTableColumn());
}
};
List<KeyMapping> digitMappings = Arrays.stream(KeyCode.values())
.filter(c -> c.isDigitKey())
.map(c -> new KeyMapping(c, handler))
.collect(Collectors.toList());
map.getMappings().addAll(digitMappings);
显然,这需要 table 的皮肤可用并且它是 TableViewSkin 类型。要使用,可以在 table 的皮肤 属性:
的侦听器中调用它table.skinProperty().addListener(c -> {
tweakInputMap(table);
});
应该在 fx 版本 >= fx9 中工作(我的上下文是 fx17/18 dev)并且需要在编译时添加导出 inputmap/behavior 包并在运行时添加打开这些包。