Search code examples
eventsjavafxevent-handlingmouseeventoverlapping

JavaFX - Making one of two overlapped component MouseClick transparent


Situation :

I have two overlapped component which aren't related (different nodes from different parents). The front component absorbs all the events which is my problem. The component's hierarechy cannot be changed, and I am looking to solve this programmatically.

Two overlapped components

What I want :

I want the front component to be transparent to the MouseClick events (similar to the MouseTransparent(true) behavior but only for MouseClick) so the background component could capture the right click event.

What I tried :

  • Deleting EventHandlers of the front component - No effect
  • Firing the event after calculation of the background component - Very Instable

Is there a way to make the front component half transparent to specific events?

[EDIT - Code & Illustration]

Note : I wanna find a way to ignore the Left MouseClick on the front.

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

Solution

  • After deep thinking I found that the only feasable solution without altering the whole hieararechy is altering the Event Dispatch. More information about Event processing can be found here.

    I managed to alter the event processing by applying an EventFilter to the parent node of the containers. The EventFilter is called before an EventHandler therefore I captured the MouseClick event and processed it. when I calculated that the back component was in a the background, I consumed the leftClick on the frontComponent and then fired a new MouseClick Event towards the backComponent.

    This new firedEvent has as a target my backComponent which ignores my front component, and that's exacly what I need.

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