JavaFX - 使两个重叠组件之一 MouseClick 透明
JavaFX - Making one of two overlapped component MouseClick transparent
情况:
我有两个不相关的重叠组件(来自不同父节点的不同节点)。前面的组件吸收了我的问题的所有事件。组件的层次结构无法更改,我希望以编程方式解决此问题。
Two overlapped components
我想要的:
我希望前端组件对 MouseClick 事件透明(类似于 MouseTransparent(true) 行为,但仅对 MouseClick ) 这样后台组件就可以捕获右键事件了。
我试过的:
- 正在删除前端组件的 EventHandlers - 无效
- 计算背景组件后触发事件 - 非常不稳定
有没有办法让前端组件对特定事件半透明?
[编辑 - 代码和插图]
注意:我想找到一种方法来忽略前面的鼠标左键。
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
initScene(primaryStage);
}
public void initScene(Stage primaryStage) {
/* Initialization */
StackPane root = new StackPane();
Group frontContainer = new Group();
Group backContainer = new Group();
Scene _scene = new Scene(root, 500, 500);
Rectangle frontComponent = new Rectangle(180, 180);
Rectangle backComponent = new Rectangle(200, 200);
frontComponent.setFill(Color.GREY);
backComponent.setFill(Color.ORANGE);
/* Scene Graph construction */
frontContainer.getChildren().add(frontComponent);
backContainer.getChildren().add(backComponent);
root.getChildren().add(backContainer);
root.getChildren().add(frontContainer);
backContainer.toBack();
frontContainer.toFront();
// --- MouseEvents Handlers
// --------------------------------------------------------
frontContainer.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.PRIMARY)
System.out.println("FrontComponent right click");
});
frontContainer.setOnDragDetected((mouseDrag) -> {
// Does something very important for a DragNDrop feature that
// depends on Right Mouse Click
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("BackComponent left click");
});
// ------------------------------------------------------------------------------------
// frontContainer.setOnMouseClicked(null); /* Disables the handling but
// still consumes the event */
primaryStage.setScene(_scene);
primaryStage.show();
}
}
经过深思熟虑,我发现在不改变整个层次结构的情况下唯一可行的解决方案是改变事件调度。
有关事件处理的更多信息,请参见 here。
我设法通过将 EventFilter 应用于容器的父节点来改变事件处理。 EventFilter 在 EventHandler 之前被调用,因此我捕获了 MouseClick 事件并对其进行了处理。当我计算出 back 组件在背景中时,我消耗了 frontComponent 上的 leftClick,然后向 backComponent 触发了一个新的 MouseClick 事件。
这个新的 firedEvent 有一个 target 我的 backComponent 忽略了我的 front 组件,而这正是我所需要的。
// --- MouseEvents Handlers --------------------------------------------------------
frontContainer.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.PRIMARY)
System.out.println("FrontComponent right click");
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("FrontComponent left click");
});
frontContainer.setOnDragDetected((mouseDrag) -> {
// Does something very important for a DragNDrop feature that
// depends on Right Mouse Click
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("BackComponent left click");
});
root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
System.out.println("click FILTERED");
if (event.getButton() == MouseButton.SECONDARY && !dispatchFlag) {
if (event.getX() >= backContainer.getLayoutX()
&& backContainer.getLayoutX() + backComponent.getWidth() >= event.getX()) {
System.out.println("It is behind me !");
MouseEvent monEvent = new MouseEvent(event.getSource(), backContainer, MouseEvent.MOUSE_CLICKED,
event.getSceneX(), event.getY(), event.getScreenX(), event.getScreenY(),
event.getButton(), event.getClickCount(), false, false, false, false,
event.isPrimaryButtonDown(), event.isMiddleButtonDown(), event.isSecondaryButtonDown(),
event.isSynthesized(), event.isPopupTrigger(), event.isStillSincePress(),
event.getPickResult());
event.consume();
dispatchFlag = true;
Event.fireEvent(backComponent, monEvent);
System.out.println("Event dispatched !");
}
}
dispatchFlag = false;
}
});
// ------------------------------------------------------------------------------------
此解决方案与Amine 的解决方案有些相似。它没有定义事件过滤器,只是在前端组件的事件处理程序中实现事件的拦截,并将鼠标点击事件转发给后端组件。也许事件过滤器方法是首选,但至少在这种情况下,此答案中的处理程序似乎可以完成工作。
要确定后部组件是否在前部组件上单击的点下方,使用以下逻辑:
boolean isInBackComponent = backComponent.contains(
backComponent.sceneToLocal(
mouseClicked.getSceneX(), mouseClicked.getSceneY()
)
);
示例代码:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception {
Rectangle backComponent = new Rectangle(200, 200, Color.ORANGE);
Rectangle frontComponent = new Rectangle(180, 180, Color.GREY);
StackPane root = new StackPane(backComponent, frontComponent);
frontComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY) {
boolean isInBackComponent = backComponent.contains(
backComponent.sceneToLocal(
mouseClicked.getSceneX(), mouseClicked.getSceneY()
)
);
if (isInBackComponent) {
backComponent.fireEvent(mouseClicked);
return;
}
}
if (mouseClicked.getButton() == MouseButton.PRIMARY) {
System.out.println("FrontComponent left click");
}
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY) {
System.out.println("BackComponent right click");
}
});
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
情况:
我有两个不相关的重叠组件(来自不同父节点的不同节点)。前面的组件吸收了我的问题的所有事件。组件的层次结构无法更改,我希望以编程方式解决此问题。
Two overlapped components
我想要的:
我希望前端组件对 MouseClick 事件透明(类似于 MouseTransparent(true) 行为,但仅对 MouseClick ) 这样后台组件就可以捕获右键事件了。
我试过的:
- 正在删除前端组件的 EventHandlers - 无效
- 计算背景组件后触发事件 - 非常不稳定
有没有办法让前端组件对特定事件半透明?
[编辑 - 代码和插图]
注意:我想找到一种方法来忽略前面的鼠标左键。
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
initScene(primaryStage);
}
public void initScene(Stage primaryStage) {
/* Initialization */
StackPane root = new StackPane();
Group frontContainer = new Group();
Group backContainer = new Group();
Scene _scene = new Scene(root, 500, 500);
Rectangle frontComponent = new Rectangle(180, 180);
Rectangle backComponent = new Rectangle(200, 200);
frontComponent.setFill(Color.GREY);
backComponent.setFill(Color.ORANGE);
/* Scene Graph construction */
frontContainer.getChildren().add(frontComponent);
backContainer.getChildren().add(backComponent);
root.getChildren().add(backContainer);
root.getChildren().add(frontContainer);
backContainer.toBack();
frontContainer.toFront();
// --- MouseEvents Handlers
// --------------------------------------------------------
frontContainer.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.PRIMARY)
System.out.println("FrontComponent right click");
});
frontContainer.setOnDragDetected((mouseDrag) -> {
// Does something very important for a DragNDrop feature that
// depends on Right Mouse Click
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("BackComponent left click");
});
// ------------------------------------------------------------------------------------
// frontContainer.setOnMouseClicked(null); /* Disables the handling but
// still consumes the event */
primaryStage.setScene(_scene);
primaryStage.show();
}
}
经过深思熟虑,我发现在不改变整个层次结构的情况下唯一可行的解决方案是改变事件调度。 有关事件处理的更多信息,请参见 here。
我设法通过将 EventFilter 应用于容器的父节点来改变事件处理。 EventFilter 在 EventHandler 之前被调用,因此我捕获了 MouseClick 事件并对其进行了处理。当我计算出 back 组件在背景中时,我消耗了 frontComponent 上的 leftClick,然后向 backComponent 触发了一个新的 MouseClick 事件。
这个新的 firedEvent 有一个 target 我的 backComponent 忽略了我的 front 组件,而这正是我所需要的。
// --- MouseEvents Handlers --------------------------------------------------------
frontContainer.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.PRIMARY)
System.out.println("FrontComponent right click");
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("FrontComponent left click");
});
frontContainer.setOnDragDetected((mouseDrag) -> {
// Does something very important for a DragNDrop feature that
// depends on Right Mouse Click
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY)
System.out.println("BackComponent left click");
});
root.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
System.out.println("click FILTERED");
if (event.getButton() == MouseButton.SECONDARY && !dispatchFlag) {
if (event.getX() >= backContainer.getLayoutX()
&& backContainer.getLayoutX() + backComponent.getWidth() >= event.getX()) {
System.out.println("It is behind me !");
MouseEvent monEvent = new MouseEvent(event.getSource(), backContainer, MouseEvent.MOUSE_CLICKED,
event.getSceneX(), event.getY(), event.getScreenX(), event.getScreenY(),
event.getButton(), event.getClickCount(), false, false, false, false,
event.isPrimaryButtonDown(), event.isMiddleButtonDown(), event.isSecondaryButtonDown(),
event.isSynthesized(), event.isPopupTrigger(), event.isStillSincePress(),
event.getPickResult());
event.consume();
dispatchFlag = true;
Event.fireEvent(backComponent, monEvent);
System.out.println("Event dispatched !");
}
}
dispatchFlag = false;
}
});
// ------------------------------------------------------------------------------------
此解决方案与Amine 的解决方案有些相似。它没有定义事件过滤器,只是在前端组件的事件处理程序中实现事件的拦截,并将鼠标点击事件转发给后端组件。也许事件过滤器方法是首选,但至少在这种情况下,此答案中的处理程序似乎可以完成工作。
要确定后部组件是否在前部组件上单击的点下方,使用以下逻辑:
boolean isInBackComponent = backComponent.contains(
backComponent.sceneToLocal(
mouseClicked.getSceneX(), mouseClicked.getSceneY()
)
);
示例代码:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception {
Rectangle backComponent = new Rectangle(200, 200, Color.ORANGE);
Rectangle frontComponent = new Rectangle(180, 180, Color.GREY);
StackPane root = new StackPane(backComponent, frontComponent);
frontComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY) {
boolean isInBackComponent = backComponent.contains(
backComponent.sceneToLocal(
mouseClicked.getSceneX(), mouseClicked.getSceneY()
)
);
if (isInBackComponent) {
backComponent.fireEvent(mouseClicked);
return;
}
}
if (mouseClicked.getButton() == MouseButton.PRIMARY) {
System.out.println("FrontComponent left click");
}
});
backComponent.setOnMouseClicked((mouseClicked) -> {
if (mouseClicked.getButton() == MouseButton.SECONDARY) {
System.out.println("BackComponent right click");
}
});
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(args); }
}