javafx - 取消对某个TableView cell的修改

标签 javafx tableview cell edit

我想举一个例子来解释我如何取消编辑并重置已编辑但未能通过验证的 TableView 中特定单元格的旧值。有关详细信息,请参阅下面的代码。

tcAantalDagen.setOnEditCommit(cell -> {
        int dagen = Integer.parseInt(cell.getNewValue());
        if (Integer.parseInt(cell.getNewValue()) < 1 || Integer.parseInt(cell.getNewValue()) > 31) {
            // This shows an Alert Dialog
            Main.toonFoutbericht("Het item kan maar tussen 1 en 31 dagen uitgeleend worden");
            // The "return;" is successful in canceling the passing through of the new value of the cell to the backend code, 
            // but in the TableView the new value is still set in the cell, which can confuse the user
            return;
        }
}

单元格的值是这样设置的:

// getAantalDagen() returns an Integer
tcAantalDagen.setCellValueFactory(cell -> {
            return new ReadOnlyObjectWrapper<String>(Integer.toString(cell.getValue().getAantalDagen()));
        });
// Make the cells of the tcAantalDagen column editable
tcAantalDagen.setCellFactory(TextFieldTableCell.forTableColumn());
// The table needs to be set to editable too for the above column to work
tblUitleningen.setEditable(true);

最佳答案

我玩了一会儿。最重要的是,TableCell 中的默认 commitEdit 方法调用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);
    }
}

关于javafx - 取消对某个TableView cell的修改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34698986/

相关文章:

JavaFX TilePane - 绘制子项的顺序

ios - 创建一个具有相同高度的静态单元格的 View 表以填充屏幕

objective-c - 如何修复缓慢滚动的表格 View

WPF datagrid 单元格颜色取决于先前的单元格值

JavaFX 文本字段监听器

java - 使用 java 运行深度链接来打开应用程序

java - 数字格式异常和正则查询

ios - 从网络服务器(ASIHTTPRequest、PHP、mysql)更新 iOS TableView 单元格中的数据

ios - 如何在 xamarin.ios 中绑定(bind)后动态更改表高度

swift - 如何在 UITableView Cell 中为 ImageView 添加尺寸和位置?