Search code examples
javafxhoverjavafx-css

After adding a drop shadow to a list cell hovering and selection events are bugged


So I added the below fx-css to a list-cell.

.list-cell:selected:filled:hover {
    -fx-view-order: -1;
    -fx-effect: dropshadow(gaussian, #67676D, 12, 0.05, 0.0, 2);
}

Note when a list-cell is :selected its background is grey.

Now it seems the node boundaries are extended to the end of the drop shadow over the surrounding cells. This behavior is unwanted as the cell with the shadow captures events that should really be handled by the below or above cell.

enter image description here

Is there a fix for this?

Note: the same fx-css can be applied to a table-view and this unwanted functionally does not occur.

public class ListViewTest {
    @Test
    public void testFx() throws InterruptedException {
        new JFXPanel();
        CountDownLatch latch = new CountDownLatch(1);
        Platform.runLater(() -> {
            ListView<String> lv =  new ListView<>();

            lv.setCellFactory(param -> new ListCell<>() {

                {
                    setPrefHeight(38);
                    hoverProperty().addListener((observable, oldValue, newValue) -> {
                            if (newValue) {
                                setStyle("""
                                    -fx-view-order: -1;
                                    -fx-effect: dropshadow(gaussian, #67676D, 12, 0.05, 0.0, 2);
                                """);
                            } else {
                                setStyle(null);
                            }
                        });
                }

                @Override
                protected void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty || item == null) {

                    } else {

                    }
                }
            });
            var items = FXCollections.observableArrayList("1", "2", "3");
            lv.setItems(items);
            Stage stage = new Stage();
            Scene scene = new Scene(lv);
            stage.setScene(scene);
            stage.show();
        });
        latch.await();
    }
}

Solution

  • Regarding your note:

    Note: the same fx-css can be applied to a table-view and this unwanted functionally does not occur.

    I assume you're applying the style to the table rows and not the table cells. I'm making that assumption for a couple reasons:

    • Table cells, at least by default, don't seem to have a background. This means the drop shadow was applied to the text of the cell in my tests rather than the entire cell.
    • It's only TableRowSkinBase that does something different to ListCellSkin that's potentially relevant to this problem.

    That difference mentioned in the second point is a call to setPickOnBounds(false). That isn't called for ListCell. Apparently the drop shadow increases the bounds of the cell causing it to overlap its adjacent cells and, since pick-on-bounds is true, that means the mouse hovers over the cell for longer than "expected".

    Simply calling setPickOnBounds(false) on your custom list cells should fix the issue. For example:

    Main.java:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        var listView = new ListView<String>();
        listView.getItems().addAll("1", "2", "3", "4", "5");
    
        listView.setCellFactory(
            lv ->
                new ListCell<>() {
    
                  {
                    setPrefHeight(38);
                    setPickOnBounds(false); // fix for issue
                  }
    
                  @Override
                  protected void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty || item == null) {
                      setText(null);
                    } else {
                      setText(item);
                    }
                  }
                });
    
        var scene = new Scene(listView, 600, 400);
        scene.getStylesheets().add(getClass().getResource("/test.css").toString());
        primaryStage.setScene(scene);
        primaryStage.show();
      }
    }
    

    test.css:

    .list-cell:selected:filled:hover {
      -fx-view-order: -1;
      -fx-effect: dropshadow(gaussian, #67676D, 12, 0.05, 0.0, 2);
    }
    

    Note: I do not know if calling setPickOnBounds(false) causes other issues or not.