JavaFX Pass MouseEvents through Transparent Node t

2019-01-11 22:51发布

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.

标签: java javafx-2
4条回答
聊天终结者
2楼-- · 2019-01-11 23:39

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); }
}
查看更多
Evening l夕情丶
3楼-- · 2019-01-11 23:40

Based on jewelsea answer setting top pane background color of the pane to null and topPane.setPickOnBounds(false); works fine:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class PropagateEvents extends Application {

    private double x, y;

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

        StackPane root = new StackPane(getBottomPane(), getTopPane());
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Pane getBottomPane() {

        Pane pane = new Pane();
        pane.setStyle("-fx-background-color : yellow;");
        pane.setPrefSize(250,200);
        pane.setOnMouseClicked(e-> System.out.println("Bottom pane recieved click event"));
        return pane;
    }

    private Pane getTopPane() {

        Label label = new Label();
        label.setPrefSize(20,10);
        label.setStyle("-fx-background-color:red;");
        label.layoutXProperty().setValue(30); label.layoutYProperty().setValue(30);
        addDragSupport(label);

        Pane pane = new Pane(label);
        // NULL color setPickOnBounds do the trick
        pane.setPickOnBounds(false);
        pane.setStyle("-fx-background-color: null; ");

        return pane;
    }
    //drag support for red label
    private void addDragSupport(Node node) {

        node.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent mouseEvent) {
                x = node.getLayoutX() - mouseEvent.getSceneX();
                y = node.getLayoutY() - mouseEvent.getSceneY();
                node.setCursor(Cursor.MOVE);
            }
        });
        node.setOnMouseReleased(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent mouseEvent) {
                node.setCursor(Cursor.HAND);
            }
        });
        node.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent mouseEvent) {
                node.setLayoutX(mouseEvent.getSceneX() + x);
                node.setLayoutY(mouseEvent.getSceneY() + y);
            }
        });
        node.setOnMouseEntered(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent mouseEvent) {
                node.setCursor(Cursor.HAND);
            }
        });
    }

    public static void main (String[] args) {launch(null);  }
}
查看更多
迷人小祖宗
4楼-- · 2019-01-11 23:46

Instead of doing this:

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

do this:

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

with the folling methods.

private boolean turnOffPickOnBoundsFor(Node n, boolean plotContent) {
    boolean result = false;
    boolean plotContentFound = false;
    n.setPickOnBounds(false);
    if(!plotContent){
        if(containsStyle(n)){
            plotContentFound = true;
            result=true;
        }
        if (n instanceof Parent) {
            for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
                if(turnOffPickOnBoundsFor(c,plotContentFound)){
                    result = true;
                }
            }
        }
        n.setMouseTransparent(!result);
    }
    return result;
}

private boolean containsStyle(Node node){
    boolean result = false;
    for (String object : node.getStyleClass()) {
        if(object.equals("plot-content")){
            result = true;
            break;
        }                
    }
    return result;
}

Also you will need to make the chart in front(reverseLineChart) transparent.

查看更多
三岁会撩人
5楼-- · 2019-01-11 23:51

The code posted in jewelsea answer does not work. To make it work I implemented the changes proposed is user1638436 answer and Julia Grabovska comment.
Here is a working version for the sake of future readers:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
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> bottomLineChart  = createChart(data);
        final LineChart<Number, Number> topLineChart     = createChart(reversedData);

        //add css to make top chart line transparent as pointed out by Julia Grabovska
        //and user1638436, as well as make line green
        topLineChart.getStylesheets().add(getClass().getResource("LineChartSample.css").toExternalForm());

        StackPane layout = new StackPane(bottomLineChart, topLineChart);

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

        // turn off pick on bounds for the charts so that clicks only register when you click on shapes.
        turnOffPickOnBoundsFor(topLineChart, false); //taken from user1638436 answer

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

    //taken from user1638436 answer (https://stackoverflow.com/a/18104172/3992939)
    private boolean turnOffPickOnBoundsFor(Node n, boolean plotContent) {
        boolean result = false;
        boolean plotContentFound = false;
        n.setPickOnBounds(false);
        if(!plotContent){
            if(containsPlotContent(n)){
                plotContentFound = true;
                result=true;
            }
            if (n instanceof Parent) {
                for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
                    if(turnOffPickOnBoundsFor(c,plotContentFound)){
                        result = true;
                    }
                }
            }
            n.setMouseTransparent(!result);
        }
        return result;
    }

    private boolean containsPlotContent(Node node){
        boolean result = false;
        for (String object : node.getStyleClass()) {
            if(object.equals("plot-content")){
                result = true;
                break;
            }
        }
        return result;
    }

    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); }
}

LineChartSample.css:

.chart-plot-background {
    -fx-background-color:transparent;
}
.default-color0.chart-series-line{
    -fx-stroke: forestgreen;
}

A simpler version of turnOffPickOnBoundsFor method:

private boolean turnOffPickOnBoundsFor(Node n) {

    n.setPickOnBounds(false);

    boolean isContainPlotContent = containsPlotContent(n);

    if (! isContainPlotContent && (n instanceof Parent) ) {

        for (Node c : ((Parent) n).getChildrenUnmodifiable()) {

            if(turnOffPickOnBoundsFor(c)){
                isContainPlotContent = true;
            }
        }
    }

    n.setMouseTransparent(!isContainPlotContent);
    return isContainPlotContent;
}
查看更多
登录 后发表回答