JavaFX - 使两个重叠组件之一 MouseClick 透明

JavaFX - Making one of two overlapped component MouseClick transparent

情况:

我有两个不相关的重叠组件(来自不同父节点的不同节点)。前面的组件吸收了我的问题的所有事件。组件的层次结构无法更改,我希望以编程方式解决此问题。

Two overlapped components

我想要的:

我希望前端组件对 MouseClick 事件透明(类似于 MouseTransparent(true) 行为,但仅对 MouseClick ) 这样后台组件就可以捕获右键事件了。

我试过的:

有没有办法让前端组件对特定事件半透明?

[编辑 - 代码和插图]

注意:我想找到一种方法来忽略前面的鼠标左键。

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); }
}