将鼠标事件委托给 JavaFX StackPane 中的所有 children

Delegate mouse events to all children in a JavaFX StackPane

我正在尝试提出一个解决方案,让多个 Pane 节点在组装成 StackPane 时独立处理鼠标事件

StackPane
   Pane 1
   Pane 2
   Pane 3

我希望能够在每个 child 中处理鼠标事件,并且第一个 child 调用 consume() 会阻止事件进入下一个 child。

我也知道 setPickOnBounds(false),但这并不能解决所有情况,因为某些叠加层将基于 Canvas 的像素,即不涉及场景图。

我用 Node.fireEvent() 尝试过各种实验。然而,这些总是导致递归以堆栈溢出结束。这是因为事件从根场景传播并再次触发相同的处理程序。

我正在寻找的是一些方法来单独触发 child 窗格上的事件处理程序,而不需要事件通过其正常路径传播。

到目前为止,我最好的解决方法是使用过滤器捕获事件并手动调用处理程序。我需要为 MouseMoved 等重复此操作

parent.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
    for (Node each : parent.getChildren()) {
        if (!event.isConsumed()) {
            each.getOnMouseClicked().handle(event);
        }
    }
    event.consume();
});

然而,这只会触发使用 setOnMouseClicked 添加的侦听器,而不是 addEventHandler,并且仅在该节点上,而不是 child 个节点上。

另一种解决方案是接受 JavaFX 不能像这样工作,并像这样重组窗格,这将允许正常的事件传播发生。

Pane 1
   Pane 2
      Pane 3

例子

import javafx.application.Application;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class EventsInStackPane extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    private static class DebugPane extends Pane {
        public DebugPane(Color color, String name) {
            setBackground(new Background(new BackgroundFill(color, CornerRadii.EMPTY, Insets.EMPTY)));
            setOnMouseClicked(event -> {
                System.out.println("setOnMouseClicked " + name + " " + event);
            });
            addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
                System.out.println("addEventHandler " + name + " " + event);
            });
            addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
                System.out.println("addEventFilter " + name + " " + event);
            });
        }

    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        DebugPane red = new DebugPane(Color.RED, "red");
        DebugPane green = new DebugPane(Color.GREEN, "green");
        DebugPane blue = new DebugPane(Color.BLUE, "blue");
        setBounds(red, 0, 0, 400, 400);
        setBounds(green, 25, 25, 350, 350);
        setBounds(blue, 50, 50, 300, 300);

        StackPane parent = new StackPane(red, green, blue);
        eventHandling(parent);

        primaryStage.setScene(new Scene(parent));
        primaryStage.show();
    }

    private void eventHandling(StackPane parent) {
        parent.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
            if (!event.isConsumed()) {
                for (Node each : parent.getChildren()) {
                    Event copy = event.copyFor(event.getSource(), each);
                    parent.fireEvent(copy);
                    if (copy.isConsumed()) {
                        break;
                    }
                }
            }
            event.consume();
        });
    }
    private void setBounds(DebugPane panel, int x, int y, int width, int height) {
        panel.setLayoutX(x);
        panel.setLayoutY(y);
        panel.setPrefWidth(width);
        panel.setPrefHeight(height);
    }
}

根据@jewelsea 的提示,我能够使用自定义链。我已经从添加到 StackPane 前面的“捕手”窗格中完成了此操作。然后,这将以相反的顺序使用所有子项构建一个链,不包括自身。

private void eventHandling(StackPane parent) {
    Pane catcher = new Pane() {
        @Override
        public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
            EventDispatchChain chain = super.buildEventDispatchChain(tail);
            for (int i = parent.getChildren().size() - 1; i >= 0; i--) {
                Node child = parent.getChildren().get(i);
                if (child != this) {
                    chain = chain.prepend(child.getEventDispatcher());
                }
            }
            return chain;
        }
    };
    parent.getChildren().add(catcher);
}