【问题标题】:Cancel the modification of a TableView cell取消对TableView单元格的修改
【发布时间】:2016-04-14 10:20:36
【问题描述】:

我想要一个示例来说明如何取消编辑并重置 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);

【问题讨论】:

  • 您的单元格的实现是什么?
  • 嘿,詹姆斯,我的 JavaFX 救星!我将编辑我的主要帖子,为您提供更多信息。
  • 我的编辑是你要求的吗?
  • 是的。 (我不确定它是否有助于我得到问题的答案......)。您可能需要实现自己的TableCell 来执行此操作(这样您就可以在提交编辑之前验证这些值)。当数据看起来是 Integer 时,为什么您的列是 TableColumn&lt;?, String&gt;
  • 实现我自己的 TableCell?我不知道该怎么做。至于您的下一个问题,我必须将单元格的类型修改为字符串,这样才能正常工作:tcAantalDagen.setCellFactory(TextFieldTableCell.forTableColumn());

标签: javafx tableview cell edit


【解决方案1】:

我玩了一段时间。底线是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);
    }
}

【讨论】:

  • TextFormatter 是一个不错的解决方案,但有时它并不适用——例如,用于验证日期或组合框单元格。在我的项目中,我发现创建一个 ValidatingTableCell 很有用,它有一个 BiPredicate 用于验证(接受行项目和要提交的值),以及一个显示为用户错误消息的字符串。然后我将其子类化为文本、日期、组合框等编辑选项。
  • 肯定使用组合框或日期选择器很容易通过控件强制执行有效选择?
  • 当然,我只是想要一个通用的解决方案,所以我不必多次编写用户交互部分(目前我只是显示一个警报,但稍后我可能想更改它)
  • 这是一个有效且非常令人满意的解决方案!我有一个关于它的问题。当我在表格单元格中输入一个空值(因此删除旧值)时,NullPointerException 发生在这一行if (item.intValue() &lt; 1 || item.intValue() &gt; 31) {。有没有办法绕过这个,以便重新设置旧值?
  • 自定义 TableCell 对于未来的实现也很有趣。感谢那!目前允许保持简单,因为它只是我们 JAVA 类的一个小型团队项目。
【解决方案2】:

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

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

【讨论】:

  • hacking around bugs 时,一切都被允许:) 只是为了强调和澄清为什么这是有效的:如果自定义编辑提交处理程序不接受数据,则数据不会改变 - 只有单元格不接受'不显示旧值 - 因此,当您在数据列表中设置旧(每个属性中未更改)项时,这会触发列表更改,进而更新该行的所有单元格。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-06
  • 1970-01-01
  • 1970-01-01
  • 2020-10-20
  • 1970-01-01
  • 2017-05-02
相关资源
最近更新 更多