【问题标题】:JavaFX ListView doesn't refresh properly when removing custom ListCell删除自定义 ListCell 时 JavaFX ListView 无法正确刷新
【发布时间】:2022-03-07 14:34:02
【问题描述】:

我正在为我的测验应用程序开发一个编辑器。

我有一个自定义的ListCell<Question> 类,如下所示:

public class QuestionListCell extends ListCell<Question> {

private static final Logger LOGGER = Logger.getLogger(QuizLoader.class.getName());

@FXML HBox cellBox;
@FXML Label title;
@FXML GridPane answersGrid;

private FXMLLoader loader;

@Override
protected void updateItem(Question item, boolean empty) {
    super.updateItem(item, empty);
    if (empty || item == null) {
        setText(null);
        setGraphic(null);
    }
    else {
        if (loader == null) {
            loader = new FXMLLoader(getClass().getResource(QUESTION_LIST_CELL_FXML));
            loader.setController(this);
            try {
                loader.load();
            }
            catch (IOException e) {
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
            }
        }
        addContent(item);
    }
}

private void addContent(Question question) {
    title.textProperty().bind(question.getQuestionTextProperty());

    int answersSize = question.getAnswers().size();
    for (int i = 0; i < answersSize; i++) {
        int rowIndex = i / 2;
        int colIndex = i % 2 == 0 ? 0 : 1;

        Label answerLabel = new Label();
        answerLabel.textProperty().bind(question.getAnswers().get(i).getAnswerTextProperty());
        answerLabel.prefWidthProperty().bind(Bindings.divide(answersGrid.widthProperty(), 2));
        answersGrid.add(answerLabel, colIndex, rowIndex);
    }

    final NumberBinding cellBoxWidth = Bindings.subtract(getListView().widthProperty(), 20);
    cellBox.prefWidthProperty().bind(cellBoxWidth);
    answersGrid.prefWidthProperty().bind(cellBoxWidth);
    setGraphic(cellBox);
}

}

我在MainController 中设置了我的 ListView,如下所示:

@FXML
private void onAddButtonClicked() {
    LOGGER.log(Level.INFO, "addButton clicked.");
    // quiz.getQuestions().add(quiz.getQuestions().get(0));
}

@FXML
private void onDeleteButtonClicked() {
    LOGGER.log(Level.INFO, "deleteButton clicked.");

    int index = listView.getSelectionModel().getSelectedIndex();
    if (index >= 0) {
        quiz.getQuestions().remove(index);
    }
}

private void setUpQuestionsListView() {
    listView.setCellFactory(listView -> new QuestionListCell());
    listView.itemsProperty().bindBidirectional(quiz.getQuestionsProperty());
}

到目前为止一切顺利,ListView 看起来像这样:

但是,当我删除第一个(并且只有第一个)项目时,第二个项目的 GridPane 似乎覆盖在第一个项目上。

据我所知,如果我在 ListView 中有更多元素,也不会发生这种情况,只有当只剩下两个元素并且我正在删除第一个元素时。我做错了什么?

【问题讨论】:

    标签: listview javafx


    【解决方案1】:

    添加行

    answersGrid.getChildren().clear() ;
    

    addContent() 的开头(或者,如果您愿意,在updateItem() 中,紧接在调用addContent() 之前)。

    问题是您只为每个单元加载一次 FXML(这是一件好事);如果(何时)该单元格随后被重用,则再次调用 updateItem() 而不更改 answersGrid 引用,并且您将更多子节点添加到网格而不删除原始子节点。因此,当单元格被重用时(例如,通过滚动,或者当一个项目被删除并且它的单元格被重用于另一个项目时),多个节点被添加到网格中的相同位置。

    如上所述,从网格中删除现有标签将解决问题。

    【讨论】:

    • 谢谢,这行得通!我之前做过类似的事情,只是我尝试了 answersGrid.getChildren.removeAll(),我错误地认为它会删除所有孩子。但现在我看到该方法是用于删除特定的孩子。 LPT:总是阅读 Javadoc。
    【解决方案2】:

    如果可以接受一些轻微的低效率和轻微的性能缺陷,您还可以在每次调用时加载 FXML。如果列表很小并且 ListCell 包含的内容不多,就像您的情况一样,那么根本不应该有问题。 清理孩子们效果很好,我过去也这样做过。我只观察到其他人(尤其是没有经验的开发人员)很难理解它,因此在未来的某个地方不太可能被破坏。

    这是我的一个例子:

    public class ProductItemListCell extends javafx.scene.control.ListCell<ProductModel> {
    
    private Consumer<ProductModel> onDeleteProductCallBack;
    
    ProductItemListCell(Consumer<ProductModel> callback) {
        this.onDeleteProductCallBack = callback;
    }
    
    @Override
    public void updateItem(ProductModel product, boolean empty)
    {
        super.updateItem(product, empty);
        if (empty || product == null) {
            setText(null);
            setGraphic(null);
            return;
        }
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("productitem.fxml"));
        try
        {
            fxmlLoader.load();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    
        var controller = (ProductItemController)fxmlLoader.getController();
        controller.setProduct(product);
        controller.addListenerForDeleteProduct(this.onDeleteProductCallBack);
        setGraphic(controller.getProductItemBox());
    }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-15
      • 1970-01-01
      • 1970-01-01
      • 2023-03-27
      • 1970-01-01
      • 2017-04-18
      • 2018-04-12
      • 1970-01-01
      相关资源
      最近更新 更多