Issue
I'm working on an editor for my quiz application.
I have a custom ListCell<Question>
class that looks like this:
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);
}
}
And I set up my ListView in my MainController
like this:
@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());
}
So far so good, the ListView looks like this:
However, when I delete the first (and only the first) item, the GridPane of the second item is seemingly overlaid on the first.
This doesn't happen if I have more elements in the ListView either, as far as I can tell, only if there are but two elements left and I'm deleting the first one. What am I doing wrong?
Solution
Add the line
answersGrid.getChildren().clear() ;
at the beginning of addContent()
(or, if you prefer, in updateItem()
, immediately before calling addContent()
).
The issue is that you only load the FXML once for each cell (which is a good thing); if (when) the cell is subsequently reused, updateItem()
is called again without changing the answersGrid
reference, and you add more children to the grid without removing the original ones. Thus when the cell is reused (e.g. through scrolling, or when an item is deleted and its cell is reused for another item), multiple nodes are added to the same locations in the grid.
Removing the existing labels from the grid, as above, will fix the problem.
Answered By - James_D
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)