Question

I've written the following code.

import java.util.ArrayList;
import java.util.List;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Callback;

public class App extends Application {

    private ListView<String> listView;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        List<String> friendList = new ArrayList<String>();
        friendList.add("Alice");
        friendList.add("Bob");

        listView = new ListView<>(FXCollections.observableArrayList(friendList));
        listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
            @Override
            public ListCell<String> call(ListView<String> p) {
                ListCell<String> cell = new ListCell<String>() {
                    @Override
                    protected void updateItem(String t, boolean empty) {
                        super.updateItem(t, empty);
                        if (t != null) {
                            Label usernameLabel = new Label(t);
                            usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));

                            Button callButton = new Button("Call");
                            callButton.setOnAction(e -> System.out.println("action")); // not working
                            callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
                            callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked")); // not working

                            HBox usernameBox = new HBox(5);
                            usernameBox.setAlignment(Pos.CENTER_LEFT);
                            usernameBox.getChildren().addAll(usernameLabel);

                            BorderPane borderPane = new BorderPane();
                            borderPane.setLeft(usernameBox);
                            borderPane.setRight(callButton);

                            VBox vbox = new VBox(3);
                            vbox.getChildren().addAll(borderPane);
                            setGraphic(vbox);
                        }
                    }
                };
                return cell;
            }
        });
        stage.setScene(new Scene(listView));
        stage.show();
    }
}

If you look at the callButton, you see that it gets three different handlers. However, only the MOUSE_ENTERED event handler is really triggered. The other ones are completely ignored. What can be the problem?

EDIT: Added and removed some code, in order to make it runnable.

Was it helpful?

Solution

This is a known bug in JavaFX 8, which is fixed in the latest ea release (1.8.0_20).

As a workaround, create the controls once and register handlers with them, then just update their state in the updateItem(...) method:

    listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
        @Override
        public ListCell<String> call(ListView<String> p) {
            Label usernameLabel = new Label();
            usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
            Button callButton = new Button("Call");

            HBox usernameBox = new HBox(5);
            usernameBox.setAlignment(Pos.CENTER_LEFT);
            usernameBox.getChildren().addAll(usernameLabel);

            BorderPane borderPane = new BorderPane();
            borderPane.setLeft(usernameBox);
            borderPane.setRight(callButton);

            VBox vbox = new VBox(3);
            vbox.getChildren().addAll(borderPane);

            ListCell<String> cell = new ListCell<String>() {

                @Override
                protected void updateItem(String t, boolean empty) {
                    super.updateItem(t, empty);
                    if (t != null) {
                        usernameLabel.setText(t);
                        setGraphic(vbox);
                    } else {
                        setGraphic(null); // you will have weird bugs without this: don't omit it
                    }
                }
            };

            callButton.setOnAction(e -> System.out.println("action: "+cell.getItem())); 
            callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered "+ cell.getItem()));
            callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked "+ cell.getItem())); 

            return cell;
        }
    });

Note that this "workaround" is really the preferred approach anyway, and the one that was intended by the designers of the "virtualized" controls like ListView, TableView, etc. The point is that updateItem(...) is potentially called very frequently by the application, whereas cells are created very rarely. By creating new controls in the updateItem(...) method you potentially introduce performance issues. Create them once for the cell, and then just configure them in updateItem(...). Note also how I just registered the event handlers once, and had the handlers refer to cell.getItem() to see which item is currently represented by the cell.

One last thing: you have a bug in your code (which I fixed). Since cells can be reused, including for the case where a cell displaying an item is reused for an empty cell, it's important that you always handle the case where the item is null (typically by setting text and/or graphic to null).

OTHER TIPS

Could you add the code of getIconAndResizeTo16( String s ). I guess the node you return there consumes mouse clicks.

Here is a runnable example that demonstrates the issue. It is just a guess though.

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class App extends Application {
    public static void main(String[] args) { launch(args); }

    @Override
    public void start(Stage stage) throws Exception {

        Button callButton = new Button("", getIconAndResizeTo16("Phone"));
        callButton.setOnAction(e -> System.out.println("clicked1")); // not working
        callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
        callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked"));  // not working

        Button chatButton = new Button("", getIconAndResizeTo16("Chat") );
        chatButton.setOnAction(e -> System.out.println("clicked2")); // not working

        HBox callIconBox = new HBox(3);
        callIconBox.setAlignment(Pos.CENTER_RIGHT);
        callIconBox.getChildren().addAll(callButton, chatButton);

        stage.setScene(new Scene(callIconBox));
        stage.show();
    }

    private Node getIconAndResizeTo16(String s) {
        Label l = new Label("Consumes " + s + " Events");
        l.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> { e.consume(); });
        l.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { e.consume(); });
        return l;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top