22

在 java 文档中,它说setMouseTransparent这会影响所有孩子以及父母。

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

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

4

4 回答 4

27

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

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

样本输出

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

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

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

鼠标悬停线1鼠标悬停线2

示例代码

下面的代码仅用于演示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<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); }
}
于 2013-06-02T04:01:14.797 回答
3

而不是这样做:

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

于 2013-08-07T13:06:02.977 回答
1

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<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;
}
于 2017-07-10T05:51:18.160 回答
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);  }
}
于 2017-07-10T15:48:10.957 回答