将鼠标事件委托给 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);
}
我正在尝试提出一个解决方案,让多个 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);
}