Подтвердить что ты не робот

JavaFX Pass MouseEvents через прозрачный Node для детей

В java-документе говорится о setMouseTransparent, который затрагивает всех детей, а также родителя.

Как это сделать, так что только прозрачные области родителя (могут видеть другие узлы ниже него, но не реагировать на события мыши) прозрачны для событий мыши, поэтому узлы ниже этого могут их получать.

Это происходит при складывании двух XYCharts в одной панели. Только последние могут принимать события.

4b9b3361

Ответ 1

Установите pickOnBounds для соответствующих узлов в значение false, а при нажатии на прозрачные области в node не будет зарегистрирован щелчок с этим node.

Определяет, как выполняется вычисление выбора для этого node при запуске MouseEvent или вызова функции. Если pickOnBounds истинно, то выбор вычисляется путем пересечения с границами этого node, иначе сбор вычисляется путем пересечения с геометрической формой этого node.

Результат вывода

Этот пример на самом деле намного сложнее, чем необходимо для демонстрации функции pickOnBounds, но я просто сделал что-то такое сложное, чтобы он показывал, что происходит "при укладке двух XYCharts в ту же самую панель", как указано в вопросе о посте.

В приведенном ниже примере две линейные диаграммы сложены друг над другом, а мышь перемещается по линии данных в одной диаграмме, которая имеет функцию свечения, прикрепленную к этому событию mouseenter. Мышь затем перемещается из данных первой линейной диаграммы, и свечение удаляется из нее. Мышь затем помещается поверх данных второй линейной диаграммы лежащей в основе диаграммы, а свечение добавляется к этой строке в лежащей в основе диаграмме.

Этот образец был разработан с использованием Java8, и описанная окраска и поведение - это то, что я использовал для запуска программы в Mac OS X и Java 8b91.

mouseoverline1mouseoverline2

Пример кода

Приведенный ниже код предназначен только для демонстрации того, что pickOnBounds работает, позволяя передавать события мыши через прозрачные области, уложенные поверх непрозрачных форм node. Для схем стилей в диаграммах не рекомендуется применять правила кода (вам лучше использовать таблицы стилей, чем поисковые запросы), также нет необходимости использовать стек линейной диаграммы для получения нескольких рядов на одной диаграмме - это было необходимо или просто сделать это, чтобы продемонстрировать приложение концепции ограничений для этого ответа.

Обратите внимание на рекурсивный вызов, чтобы установить свойство 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); }
}

Ответ 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).

Ответ 3

Код, отправленный в jewelsea answer, не работает. Чтобы сделать его работу, я внедрил предложенные изменения user1638436 и комментарий Юлии Грабовской.
Вот рабочая версия для будущих читателей:

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 (/questions/277376/javafx-pass-mouseevents-through-transparent-node-to-children/1389048#1389048)
    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;
}

Ответ 4

На основе 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);  }
}