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

在java doc中,它说的是setMouseTransparent ,它影响所有子节点以及父节点。

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

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

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

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

样本输出

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

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

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

mouseoverline1mouseoverline2

示例代码

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

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

示例代码是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 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 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 lineChart = createChart(data); final LineChart 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 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() { @Override public void handle(MouseEvent e) { path.setEffect(glow); } }); path.setOnMouseExited(new EventHandler() { @Override public void handle(MouseEvent e) { path.setEffect(null); } }); } } private LineChart createChart(ObservableList data) { final NumberAxis xAxis = new NumberAxis(); final NumberAxis yAxis = new NumberAxis(); xAxis.setLabel("Number of Month"); final LineChart 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); } } 

而不是这样做:

 // 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)透明。

在jewelsea 回答中发布的代码不起作用。 为了使它工作我实施的建议是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 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 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 bottomLineChart = createChart(data); final LineChart 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 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() { @Override public void handle(MouseEvent e) { path.setEffect(glow); } }); path.setOnMouseExited(new EventHandler() { @Override public void handle(MouseEvent e) { path.setEffect(null); } }); } } private LineChart createChart(ObservableList data) { final NumberAxis xAxis = new NumberAxis(); final NumberAxis yAxis = new NumberAxis(); xAxis.setLabel("Number of Month"); final LineChart 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; } 

基于jewelsea答案设置窗格的顶部窗格背景颜色为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() { @Override public void handle(MouseEvent mouseEvent) { x = node.getLayoutX() - mouseEvent.getSceneX(); y = node.getLayoutY() - mouseEvent.getSceneY(); node.setCursor(Cursor.MOVE); } }); node.setOnMouseReleased(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { node.setCursor(Cursor.HAND); } }); node.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { node.setLayoutX(mouseEvent.getSceneX() + x); node.setLayoutY(mouseEvent.getSceneY() + y); } }); node.setOnMouseEntered(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { node.setCursor(Cursor.HAND); } }); } public static void main (String[] args) {launch(null); } }