Search code examples
javaevent-handlingjavafxhandlerhandle

JavaFX custom list cell with button: handler not called


I have a ListView to show some information, and I created a custom CellFactory: each cell has a label, an image and a small button. I want the user to be able to remove a list row by clicking on that small button.

The list is displayed correctly, but the handler associated to the button is never called...

public class ListViewCell extends ListCell<SessionExercise> {
    @Override
    public void updateItem(SessionExercise exercise, boolean empty) {

    ...

    FXMLLoader listItemLoader = new FXMLLoader();
    listItemLoader.setLocation(getClass().
        getResource("/view/SimpleExerciseListItem.fxml"));

    try {
        listItemLoader.load();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    SimpleExerciseListItemController listItemController
            = (SimpleExerciseListItemController) listItemLoader.getController();

    ...

    this.setGraphic(listItemController.getAnchorPane());
}

And in my SimpleExerciseListItemController class:

public class SimpleExerciseListItemController implements Initializable {
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        btnRemove.
            setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("You clicked the remove button!");

                ...
            }
        });
    }
}

I've also tried setOnMouseClicked(new EventHandler()) instead of setOnAction(new EventHandler()), but nothing is printed to the console...


Solution

  • This is a known bug which is fixed in the latest version.

    The way you are implementing your ListCell is not a recommended approach; using the recommended approach also provides a workaround for the bug.

    The issue is one of performance. For a given ListView, relatively few cells are created, but the updateItem(...) method may be called many times. (Arguably, in some cases, more times then is necessary, but the basic, intentional, design is that updateItem(...) can be called very frequently.)

    So you should load your fxml file in the constructor only; manipulate it and set the graphic in the updateItem(...) method:

    public class ListViewCell extends ListCell<SessionExercise> {
    
        private final SimpleExerciseListItemController listItemController ;
    
        public ListViewCell() {
            FXMLLoader listItemLoader = new FXMLLoader();
            listItemLoader.setLocation(getClass().
                getResource("/view/SimpleExerciseListItem.fxml"));
    
            try {
                listItemLoader.load();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    
            SimpleExerciseListItemController listItemController
                    = (SimpleExerciseListItemController) listItemLoader.getController();
    
            }
            @Override
            public void updateItem(SessionExercise exercise, boolean empty) {
                // don't omit this!!!
                super.updateItem(exercise, empty);
    
                if (empty) {
                    setGraphic(null);
                } else {
                    // update controller and ui as necessary
    
                    this.setGraphic(listItemController.getAnchorPane());
                }
            }
    }