取消修改TableView单元格

时间:2016-01-09 21:10:47

标签: 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);

2 个答案:

答案 0 :(得分:3)

我玩了一会儿。底线是commitEdit中的默认TableCell方法,当您在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());

但是......一旦你遇到了实现表格单元格的所有麻烦,你也可以提供一个根本不允许用户输入无效值的单词,这是一个很大的问题。更好的用户体验imho。您可以通过为使用仅TextFormatter的单元格创建文本字段来执行此操作,该{{3}}只会否决无效值。你必须要小心这些,因为你想允许部分编辑的值(所以一般来说,它不足以只允许有效的值,因为每次文本都会检查它们变化,而不仅仅是提交)。在这种情况下,唯一的意思是你应该在文本字段中允许空字符串(否则用户在编辑时不能删除当前值,这将是不方便的)。如果用户尝试使用空字符串提交,则可以使用转换器返回当前值。

所以这个实现可能看起来像

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);
    }
}

答案 1 :(得分:0)

我遇到了与您相同的问题,并且找到了解决方案。如果新值无效,则获取当前编辑行的索引,并将数据设置为旧对象。

      int row = event.getTablePosition().getRow();
            ProductInOrder productInOrder = event.getTableView().getItems().get(row);
            if (newCount <0){
                Messenger.erro(bundle.getString("txt_number_must_large_or_equal_0"));
                tbProductInOrder.getItems().set(row, productInOrder);
                return;
            }
            productInOrder.setCount(newCount);