java JavaFX 通过透明节点将鼠标事件传递给子节点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16876083/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-01 00:14:26  来源:igfitidea点击:

JavaFX Pass MouseEvents through Transparent Node to Children

javajavafx-2

提问by BAR

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

在java doc中它说setMouseTransparent影响所有孩子以及父母。

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.

在同一窗格中堆叠两个 XYCharts 时会发生这种情况。只有最后添加的才能接收事件。

回答by jewelsea

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

pickOnBounds相关节点设置为false,然后单击节点中的透明区域不会向该节点注册单击。

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.

定义在由 MouseEvent 或 contains 函数调用触发时如何为此节点完成拾取计算。如果pickOnBounds为真,则通过与该节点的边界相交来计算拾取,否则通过与该节点的几何形状相交来计算拾取。

Sample Output

样本输出

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

这个示例实际上比演示该pickOnBounds功能所需的复杂得多- 但我只是做了一些如此复杂的事情,以便它显示XYCharts海报问题中提到的“在同一窗格中堆叠两个时”会发生什么。

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.

在下面的示例中,两个折线图彼此堆叠在一起,鼠标移动到一个图表中的数据线上,该图表具有附加到其 mouseenter 事件的发光功能。然后将鼠标移离第一个折线图数据,并从其中移除发光。然后将鼠标放置在底层堆叠图表的第二个折线图数据上,并在底层堆叠图表中向该折线图添加发光。

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

该示例是使用Java8开发的,所描述的颜色和行为是我在 Mac OS X 和 Java 8b91 上运行该程序时所经历的。

mouseoverline1mouseoverline2

鼠标悬停1鼠标悬停2

Sample Code

示例代码

The code below is just for demonstrating that pickOnBoundsdoes 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.

下面的代码只是为了演示它pickOnBounds确实可以让您通过堆叠在不透明节点形状顶部的透明区域传递鼠标事件。不建议遵循在图表中设置线条样式的代码实践(您最好使用样式表而不是查找),也没有必要使用折线图堆栈在单个图表上获取多个系列 - 它只需要或更简单地做这些事情来演示这个答案的选择边界概念应用程序。

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

请注意在pickOnBounds图表显示在舞台上并创建了所有必需的节点之后为图表设置属性的递归调用。

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

示例代码改编自JavaFX 2 XYChart.Series 和 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); }
}

回答by user1638436

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.

您还需要使前面的图表(reverseLineChart)透明。

回答by c0der

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

在jewelsea answer中发布的代码不起作用。为了使它工作,我实施了提议的更改是user1638436答案和 Julia Grabovska评论。
为了将来的读者,这是一个工作版本:

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:

LineChartSample.css:

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

A simpler version of turnOffPickOnBoundsFormethod:

一个更简单的turnOffPickOnBoundsFor方法版本:

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

回答by c0der

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

基于jewelsea answer将窗格的顶部窗格背景颜色设置为null并且topPane.setPickOnBounds(false);工作正常:

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