我玩了一段时间。底线是TableCell 中的默认commitEdit 方法(在您提交TextFieldTableCell 中的文本字段中的更改时调用)使用新值在表格单元格上调用updateItem(...)。这就是为什么即使您没有在模型中更改单元格中的值也会发生变化的原因。
为了防止这种情况,您需要自己实现表格单元格,这并不难。最简单的实现应该可能只使用文本字段表格单元格并覆盖updateItem(...) 来检查值的有效性。类似的东西
tcAantalDagen.setCellFactory(col -> new TextFieldTableCell<T, Integer>(new IntegerStringConverter()) {
@Override
public void updateItem(Integer item, boolean empty) {
if (empty) {
super.updateItem(item, empty) ;
} else {
// if out of range, revert to previous value:
if (item.intValue() < 1 || item.intValue() > 31) {
item = getItem();
}
super.updateItem(item, empty);
}
}
});
应该可以工作(虽然我还没有测试过)。显然将T 替换为表中项目的任何类型。请注意,我在这里使用Integer 作为列类型,如果您为TextFieldTableCell 提供适当的转换器,您可以这样做。您可以将单元格值工厂修改为
tcAantalDagen.setCellValueFactory(cellData -> cellData.getValue().aantalDagenProperty().asObject());
但是...一旦您遇到了实现表格单元格的所有麻烦,您不妨提供一个根本不允许用户输入无效值的单元格,恕我直言,这是一种更好的用户体验.为此,您可以为使用 TextFormatter 的单元格创建一个文本字段,该字段只是否决无效值。您必须对这些小心一点,因为您希望允许部分编辑的值(所以一般来说,仅允许有效的值是不够的,因为每次文本更改时都会检查它们,而不仅仅是在提交上)。在这种情况下,这意味着您应该在文本字段中允许空字符串(否则用户将无法在编辑时删除当前值,这会很尴尬)。如果用户尝试使用空字符串提交,您可以使用转换器返回当前值。
所以它的实现可能看起来像
public class IntegerEditingCell extends TableCell<Item, Integer> {
private TextField textField ;
private TextFormatter<Integer> textFormatter ;
public IntegerEditingCell(int min, int max) {
textField = new TextField();
UnaryOperator<TextFormatter.Change> filter = c -> {
String newText = c.getControlNewText() ;
// always allow deleting all characters:
if (newText.isEmpty()) {
return c ;
}
// otherwise, must have all digits:
if (! newText.matches("\\d+")) {
return null ;
}
// check range:
int value = Integer.parseInt(newText) ;
if (value < min || value > max) {
return null ;
} else {
return c ;
}
};
StringConverter<Integer> converter = new StringConverter<Integer>() {
@Override
public String toString(Integer value) {
return value == null ? "" : value.toString() ;
}
@Override
public Integer fromString(String string) {
// if it's an int, return the parsed value
if (string.matches("\\d+")) {
return Integer.valueOf(string);
} else {
// otherwise, just return the current value of the cell:
return getItem() ;
}
}
};
textFormatter = new TextFormatter<Integer>(converter, 0, filter) ;
textField.setTextFormatter(textFormatter);
textField.addEventFilter(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
textField.setOnAction(e -> commitEdit(converter.fromString(textField.getText())));
textProperty().bind(Bindings
.when(emptyProperty())
.then((String)null)
.otherwise(itemProperty().asString()));
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
@Override
protected void updateItem(Integer value, boolean empty) {
super.updateItem(value, empty);
if (isEditing()) {
textField.requestFocus();
textField.selectAll();
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
@Override
public void startEdit() {
super.startEdit();
textFormatter.setValue(getItem());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
@Override
public void commitEdit(Integer newValue) {
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
@Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
这看起来像很多代码,但大部分只是设置文本字段和格式化程序,然后有一些非常标准的单元格实现,只是确保文本字段在编辑模式下显示并且纯文本是以非编辑模式显示。
现在您根本无需担心检查输入的有效性,因为用户无法输入无效值。
这是一个简单的用法示例:
import java.util.Random;
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class ValidatingTableColumn extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
TableColumn<Item, String> nameColumn = new TableColumn<>("Item");
nameColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
TableColumn<Item, Integer> valueColumn = new TableColumn<>("Value");
valueColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty().asObject());
table.getColumns().add(nameColumn);
table.getColumns().add(valueColumn);
valueColumn.setCellFactory(col -> new IntegerEditingCell(1, 31));
valueColumn.setOnEditCommit(e -> {
table.getItems().get(e.getTablePosition().getRow()).setValue(e.getNewValue());
});
Random rng = new Random();
for (int i = 1; i <= 20; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(31)+1));
}
Button debug = new Button("Show all values");
debug.setOnAction(e -> table.getItems().forEach(item -> System.out.println(item.getName()+" ("+item.getValue()+")")));
BorderPane.setAlignment(debug, Pos.CENTER);
BorderPane.setMargin(debug, new Insets(5));
BorderPane root = new BorderPane(table, null, null, debug, null);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
public static class Item {
private final IntegerProperty value = new SimpleIntegerProperty() ;
private final StringProperty name = new SimpleStringProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
}
public static void main(String[] args) {
launch(args);
}
}