JavaFX : TableView inside Dialog has duplicate items

 ̄綄美尐妖づ 提交于 2019-12-31 05:20:10

问题


I have an issue with my TableView and its items. I have created a small Dialog window to display warnings about my app, and inside the Dialog I have a TableView which displays the name of the warning and some information about it upon clicking on a button.

I have created a WarningUtil class (Singleton pattern) just to open / close the Dialog. The relevant code follows.

The constructor of the WarningUtil class (called once only) :

private WarningUtil(RootCtrl rootCtrl) {
    this.rootCtrl = rootCtrl;
    warnings = new HashMap<>();

    setupWarningCallbacks(); // not relevant
    setupTable();
    setupColumns(); // not relevant
    setupDialog();
}

The function managing the construction of the Dialog :

private void setupTable() {
    // create the content pane
    content = new AnchorPane(); // class variable - reference needed for further uses
    content.setPrefSize(480, 240);

    // create the root nodes of the view (table + 2 columns)
    warningTable = new TableView<>(); // class variable - reference needed for further uses
    warnDescriptionCol = new PTableColumn<>(); // class variable - reference needed for further uses
    warnDetailsCol = new PTableColumn<>(); // class variable - reference needed for further uses

    // settings anchors to keep the ration between dialog <-> table
    AnchorPane.setBottomAnchor(warningTable, 15.0);
    AnchorPane.setTopAnchor(warningTable, 15.0);
    AnchorPane.setLeftAnchor(warningTable, 15.0);
    AnchorPane.setRightAnchor(warningTable, 15.0);

    // setting up the columns
    warnDescriptionCol.setText(i18n("label.desc"));
    warnDetailsCol.setText(i18n("label.details"));
    warnDescriptionCol.setPercentageWidth(0.7);
    warnDetailsCol.setPercentageWidth(0.3);
    warnDescriptionCol.setResizable(false);
    warnDetailsCol.setResizable(false);

    // adding nodes to containers
    warningTable.getColumns().addAll(warnDescriptionCol, warnDetailsCol);
    content.getChildren().add(warningTable);
}

The function used to create the Dialog and set the content :

private void setupDialog() {
    // creation and saving of the dialog in a variable reused later
    warningDialog = DialogFactory.getInstance(rootCtrl.getPrimaryStage()).createWarningDialog();
    warningDialog.getDialogPane().setContent(content);
    warningDialog.getDialogPane().getScene().getWindow().sizeToScene();
}

// The DialogFactory function creating the dialog

public Dialog createWarningDialog(){
    CustomDialog dialog = new CustomDialog(rootStage);

    dialog.setTitle(i18n("warning.description"));

    ButtonType cancelBt = new ButtonType(i18n("button.close"), ButtonData.OK_DONE);
    dialog.getDialogPane().getButtonTypes().add(cancelBt);

    return dialog.setupLayout();
}

The Main class is in charge of loading the warnings (stored in a .json file and deserialized upon starting the app). For now, the file only contains one entry.

When I click on my Warning button, the following function is called :

public void showWarnings() {
    warningTable.getItems().clear(); // BP
    warningTable.setItems(FXCollections.observableArrayList(warnings.values()));
    warningDialog.showAndWait();
}

What happens is the following : When I have only one entry in my .json file, the first time I click on the button, only one warning is shown. If I click a second time, a second entry appears (the same) which should not be possible because of the following reasons :

  • Logic constraint : warnings.values() comes from an HashMap where the key is the type of the warning (WarningType class) > Not possible to have two identical keys
  • Debugging : When I set a breakpoint at "//BP", I clearly see that the warningTable has one item, and after clear the number of items is zero
  • Debugging : Still with the same breakpoint, I also check that warnings.values() has only one item, which is the case

After five clicks on the button, the Dialog clearly shows something is bugging.

More surprisingly, when I add a second warning (different from the first one, another type), the problem does not occur : No duplicates, warnings are correctly displayed and no matter how many times I open the window.

My question is : Could that be that the way I am creating this warning dialog leads to uncommon errors ? If so, why isn't it the case with two warnings ?

EDIT Include of the cellFactories / cellValueFactories

private void setupColumns() {
    warnDescriptionCol.setCellFactory(new Callback<TableColumn<CustomWarning, String>, TableCell<CustomWarning, String>>() {
        @Override
        public TableCell<CustomWarning, String> call(TableColumn<CustomWarning, String> param) {
            TableCell<CustomWarning, String> cell = new TableCell<CustomWarning, String>() {
                @Override
                protected void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);

                    if (item != null) {
                        Label label = new Label(item);
                        setGraphic(label);
                    }
                }
            };

            return cell;
        }
    });

    warnDetailsCol.setCellFactory(new Callback<TableColumn<CustomWarning, CustomWarning>, TableCell<CustomWarning, CustomWarning>>() {
        @Override
        public TableCell<CustomWarning, CustomWarning> call(TableColumn<CustomWarning, CustomWarning> param) {
            TableCell<CustomWarning, CustomWarning> cell = new TableCell<CustomWarning, CustomWarning>() {
                @Override
                protected void updateItem(CustomWarning item, boolean empty) {
                    super.updateItem(item, empty);

                    if (item != null) {
                        Button button = new Button(i18n("button.view"));
                        button.getStyleClass().add("save");
                        button.setOnAction(new EventHandler<ActionEvent>() {
                            @Override
                            public void handle(ActionEvent event) {
                                showWarning(item);
                            }
                        });

                        setGraphic(button);
                    }
                }
            };

            return cell;
        }
    });

    warnDescriptionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, String>, ObservableValue<String>>() {
        TableViewObjectWrapper<CustomWarning, String> wrapper = new TableViewObjectWrapper<CustomWarning, String>() {
            @Override
            public String getData() {
                return getModel().getTitle();
            }
        };

        @Override
        public ObservableValue<String> call(TableColumn.CellDataFeatures<CustomWarning, String> param) {
            return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
        }
    });

    warnDetailsCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, CustomWarning>, ObservableValue<CustomWarning>>() {
        TableViewObjectWrapper<CustomWarning, CustomWarning> wrapper = new TableViewObjectWrapper<CustomWarning, CustomWarning>() {
            @Override
            public CustomWarning getData() {
                return getModel();
            }
        };

        @Override
        public ObservableValue<CustomWarning> call(TableColumn.CellDataFeatures<CustomWarning, CustomWarning> param) {
            return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
        }
    });
}

回答1:


You have to clear your cells in the cell factory if the cell is empty, as explained in the documentation:

It is very important that subclasses of Cell override the updateItem method properly, as failure to do so will lead to issues such as blank cells or cells with unexpected content appearing within them. Here is an example of how to properly override the updateItem method:

 protected void updateItem(T item, boolean empty) {
     super.updateItem(item, empty);

     if (empty || item == null) {
         setText(null);
         setGraphic(null);
     } else {
         setText(item.toString());
     }
 }

Note in this code sample two important points:

  1. We call the super.updateItem(T, boolean) method. If this is not done, the item and empty properties are not correctly set, and you are likely to end up with graphical issues.
  2. We test for the empty condition, and if true, we set the text and graphic properties to null. If we do not do this, it is almost guaranteed that end users will see graphical artifacts in cells unexpectedly.

Since the cells are reused, you have to clear the graphic if it has become empty, not just set it if it's not.



来源:https://stackoverflow.com/questions/40218817/javafx-tableview-inside-dialog-has-duplicate-items

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!