取消修改TableView单元格 [英] Cancel the modification of a TableView cell

查看:148
本文介绍了取消修改TableView单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'd like to have an example which explains me how I can cancel an edit and reset the old value of a specific cell in a TableView that was edited but failed to pass through the validation. See the code below for more info.

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 方法,当您在文本字段中提交更改时会调用该方法在 TextFieldTableCell 中,使用新值在表格单元格上调用 updateItem(...)。这就是为什么即使你没有在模型中改变它,单元格中的值也在变化。

I played with this a while. The bottom line is that the default commitEdit method in TableCell, which is invoked when you commit the change in the text field in the TextFieldTableCell, calls updateItem(...) on the table cell with the new value. That's why the value in the cell is changing even though you don't change it in the model.

为了防止这种情况,你需要自己实现表格单元,这不是太难。最简单的实现可能只是使用文本字段表单元格并覆盖 updateItem(...)来检查值的有效性。类似

To prevent this, you need to implement the table cell yourself, which isn't too hard. The easiest implementation should would probably just use a text field table cell and override updateItem(...) to check the validity of the value. Something like

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 提供适当的转换器,则可以执行此操作。 。您可以将单元格值工厂修改为

should work (though I haven't tested it). Obviously replace T with whatever the type of the items in the table are. Note that I used Integer as the column type here, which you can do if you provide an appropriate converter to the TextFieldTableCell. You would modify the cell value factory as

tcAantalDagen.setCellValueFactory(cellData -> cellData.getValue().aantalDagenProperty().asObject());

然而......一旦你遇到实施表格单元格的所有麻烦,你可能会同时提供一个不允许用户输入无效值的用户,这是一个更好的用户体验imho。您可以通过为使用 TextFormatter 。你必须要小心这些,因为你想要允许部分编辑的值(所以一般来说,仅仅允许有效的值是不够的,因为每次文本更改时都会检查它们,而不仅仅是在提交)。在这种情况下,唯一的意思是你应该在文本字段中允许空字符串(否则用户将无法在编辑时删除当前值,这将是不方便的)。如果用户尝试使用空字符串提交,则可以使用转换器返回当前值。

However... once you're going to all the trouble of implementing a table cell, you may as well provide one that simply doesn't allow the user to enter an invalid value, which is a much nicer user experience imho. You can do this by creating a text field for the cell that uses a TextFormatter that just vetoes invalid values. You have to be a little careful with these, as you want to allow values that would be partially-edited (so in general, it's not enough to allow only values that are valid, as they are checked every time the text changes, not just on commits). In this case, the only thing this means is that you should allow empty strings in the text field (else the user won't be able to delete the current value while editing, which would be awkward). You can use the converter to return the current value if the user tries to commit with an empty string.

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

So an implementation of this might look like

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

}

这看起来像很多代码,但大多数只是设置文本字段和格式化程序,然后有一些非常标准的单元格实现,只是确保文本字段以编辑模式显示,纯文本以非编辑模式显示。

That looks like a lot of code, but most of it is just setting up the text field and the formatter, then there's some pretty standard cell implementation that just makes sure the text field is displayed in editing mode and the plain text is displayed in non-editing mode.

现在您根本无需担心检查输入的有效性,因为用户无法输入无效值。

Now you simply don't need to worry about checking the validity of the input, because the user cannot enter an invalid value.

以下是一个简单的用法示例:

Here's a simple usage example:

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

这篇关于取消修改TableView单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆