JavaFX通过透明节点将MouseEvents传递给子节点

时间:2013-06-01 18:47:18

标签: java javafx-2

在java doc中,它描述了setMouseTransparent,它影响所有孩子和父母。

如何才能这样做只有父的透明区域(可以看到它下面的其他节点但不响应鼠标事件)对鼠标事件是透明的,以便它下面的节点可以接收它们。

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

4 个答案:

答案 0 :(得分:24)

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

  

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

示例输出

这个示例实际上比演示pickOnBounds函数要复杂得多 - 但我只做了一些复杂的事情,以便它显示在同一窗格中堆叠两个XYCharts时会发生什么“正如海报的问题所述。

在下面的示例中,两个折线图堆叠在一起,鼠标在一个图表中的数据线上移动,该图表中有一个附加到其mouseenter事件的发光函数。然后将鼠标从第一个折线图数据移开,并从中移除光晕。然后将鼠标放在底层堆积图表的第二个折线图数据上,并将辉光添加到底层堆积图表中的该折线图。

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

mouseoverline1 mouseoverline2

示例代码

下面的代码仅用于演示pickOnBounds是否有效,允许您通过堆叠在不透明节点形状顶部的透明区域传递鼠标事件。对于图表中的样式线,不建议使用代码实践(最好使用样式表而不是查找),也不必使用折线图堆栈在单个图表上获得多个系列 - 它只是必须或更简单地做这些事情来演示这个答案的选择边界概念应用程序。

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

示例代码是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); }
}

答案 1 :(得分:2)

而不是这样做:

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

这样做:

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

使用后续方法。

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

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

答案 2 :(得分:0)

在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:

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

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

答案 3 :(得分:0)

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