JavaFX Pass MouseEvents through Transparent Node to Children

BAR picture BAR · Jun 1, 2013 · Viewed 14.2k times · Source

In the java doc it says for setMouseTransparent that is affects all children as well as the parent.

How can it be made so only the parent's transparent areas (can see other nodes below it but not responding to mouse events) are transparent to mouse events so that the nodes below it may receive them.

This happens when stacking two XYCharts in the same pane. Only the last one added can receive events.

Answer

jewelsea picture jewelsea · Jun 2, 2013

Set pickOnBounds for the relevant nodes to false, then clicking on transparent areas in a node won't register a click with that node.

Defines how the picking computation is done for this node when triggered by a MouseEvent or a contains function call. If pickOnBounds is true, then picking is computed by intersecting with the bounds of this node, else picking is computed by intersecting with the geometric shape of this node.

Sample Output

This sample is actually far more complicated than is necessary to demonstrate the pickOnBounds function - but I just did something this complicated so that it shows what happens "when stacking two XYCharts in the same pane" as mentioned in the poster's question.

In the sample below two line charts are stacked on top of each other and the mouse is moved over the data line in one chart which has a glow function attached to it's mouseenter event. The mouse is then moved off of the first line chart data and the glow is removed from it. The mouse is then placed over the second line chart data of an underlying stacked chart and the glow is added to that linechart in the underlying stacked chart.

This sample was developed using Java8 and the coloring and behaviour described is what I exeperienced running the program on Mac OS X and Java 8b91.

mouseoverline1mouseoverline2

Sample Code

The code below is just for demonstrating that pickOnBounds does work for allowing you to pass mouse events through transparent regions stacked on top of opaque node shapes. It is not a recommended code practice to follow for styling lines in charts (you are better off using style sheets than lookups for that), it is also not necessary that you use a line chart stack to get multiple series on a single chart - it was only necessary or simpler to do these things to demonstrate the pick on bounds concept application for this answer.

Note the recursive call to set the pickOnBounds property for the charts after the charts have been shown on a stage and all of their requisite nodes created.

Sample code is an adaption of JavaFX 2 XYChart.Series and setOnMouseEntered:

import javafx.application.Application;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Path;
import javafx.stage.Stage;

public class LineChartSample extends Application {
  @SuppressWarnings("unchecked")
  @Override public void start(Stage stage) {
    // initialize data
    ObservableList<XYChart.Data> data = FXCollections.observableArrayList(
      new XYChart.Data(1, 23),new XYChart.Data(2, 14),new XYChart.Data(3, 15),new XYChart.Data(4, 24),new XYChart.Data(5, 34),new XYChart.Data(6, 36),new XYChart.Data(7, 22),new XYChart.Data(8, 45),new XYChart.Data(9, 43),new XYChart.Data(10, 17),new XYChart.Data(11, 29),new XYChart.Data(12, 25)
    );
    ObservableList<XYChart.Data> reversedData = FXCollections.observableArrayList(
        new XYChart.Data(1, 25), new XYChart.Data(2, 29), new XYChart.Data(3, 17), new XYChart.Data(4, 43), new XYChart.Data(5, 45), new XYChart.Data(6, 22), new XYChart.Data(7, 36), new XYChart.Data(8, 34), new XYChart.Data(9, 24), new XYChart.Data(10, 15), new XYChart.Data(11, 14), new XYChart.Data(12, 23)
    );

    // create charts
    final LineChart<Number, Number> lineChart        = createChart(data);
    final LineChart<Number, Number> reverseLineChart = createChart(reversedData);
    StackPane layout = new StackPane();
    layout.getChildren().setAll(
      lineChart,
      reverseLineChart
    );

    // show the scene.
    Scene scene = new Scene(layout, 800, 600);
    stage.setScene(scene);
    stage.show();

    // make one line chart line green so it is easy to see which is which.
    reverseLineChart.lookup(".default-color0.chart-series-line").setStyle("-fx-stroke: forestgreen;");

    // turn off pick on bounds for the charts so that clicks only register when you click on shapes.
    turnOffPickOnBoundsFor(lineChart);
    turnOffPickOnBoundsFor(reverseLineChart);

    // add a glow when you mouse over the lines in the line chart so that you can see that they are chosen.
    addGlowOnMouseOverData(lineChart);
    addGlowOnMouseOverData(reverseLineChart);
  }

  @SuppressWarnings("unchecked")
  private void turnOffPickOnBoundsFor(Node n) {
    n.setPickOnBounds(false);
    if (n instanceof Parent) {
      for (Node c: ((Parent) n).getChildrenUnmodifiable()) {
        turnOffPickOnBoundsFor(c);
      }
    }
  }

  private void addGlowOnMouseOverData(LineChart<Number, Number> lineChart) {
    // make the first series in the chart glow when you mouse over it.
    Node n = lineChart.lookup(".chart-series-line.series0");
    if (n != null && n instanceof Path) {
      final Path path = (Path) n;
      final Glow glow = new Glow(.8);
      path.setEffect(null);
      path.setOnMouseEntered(new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent e) {
          path.setEffect(glow);
        }
      });
      path.setOnMouseExited(new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent e) {
          path.setEffect(null);
        }
      });
    }
  }

  private LineChart<Number, Number> createChart(ObservableList<XYChart.Data> data) {
    final NumberAxis xAxis = new NumberAxis();
    final NumberAxis yAxis = new NumberAxis();
    xAxis.setLabel("Number of Month");
    final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
    lineChart.setTitle("Stock Monitoring, 2010");
    XYChart.Series series = new XYChart.Series(data);
    series.setName("My portfolio");
    series.getData().addAll();
    lineChart.getData().add(series);
    lineChart.setCreateSymbols(false);
    lineChart.setLegendVisible(false);
    return lineChart;
  }

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