1

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();
    }
}
  • I am curious if you can use an if statement to bring the correct node forward based on left click or right click. Or could your handle both the right and left click in the top nodes onClick? – SedJ601 Mar 01 '17 at 15:37
  • You should create a Minimal, Complete, and Verifiable example. http://stackoverflow.com/help/mcve – SedJ601 Mar 01 '17 at 15:39
  • Possible duplicate of [JavaFX: How to make a Node partially mouse transparent?](http://stackoverflow.com/questions/15525001/javafx-how-to-make-a-node-partially-mouse-transparent) – Hypnic Jerk Mar 01 '17 at 16:15
  • @SedrickJefferson That's a good idea, I haven't thought about switching the components Z-Order dynamically. Unfortunately they both use the right click so maybe I could use an other way for trigerring the order switch... – FoxHound-Amine Mar 01 '17 at 16:28
  • @HypnicJerk the two topics are quite similar, but it is not what I'm looking for. Thanks – FoxHound-Amine Mar 01 '17 at 16:31
  • Can you provide an [mcve] so we can help out some more? – Hypnic Jerk Mar 01 '17 at 16:40
  • Since a mouse click happens when a mouse is pressed an released, unsing only `MOUSE_CLICKED` and not `MOUSE_PRESSED` and `MOUSE_RELEASED` seldom makes much sense... – fabian Mar 01 '17 at 16:40
  • @HypnicJerk Here is a code illustration. I tryed to reproduce the situation. – FoxHound-Amine Mar 01 '17 at 21:48
  • @fabian I am not sure I understood your comment. But here I don't make any difference between M_Pressed and M_Released. They have no significant difference in my context. I'm trying to mute the LeftMouse Click if not, Mute All the mouseClicks on that front component. – FoxHound-Amine Mar 01 '17 at 21:51
  • When you supply an mcve which somebody could not just copy and paste to run (i.e. an incomplete program), it requires more work for somebody to try to replicate your issue and lowers the probabilities of people answering. – jewelsea Mar 02 '17 at 00:19
  • Thanks @jewelsea I'm just getting started on stackoverflow. I've pasted the whole code. – FoxHound-Amine Mar 02 '17 at 07:51
  • @SedrickJefferson Your suggestion about switching the Z-Order dynamically will not solve the problem. Because the Z-Order will be already given when processing the event and especially when I use the same click for both components. – FoxHound-Amine Mar 06 '17 at 10:03

2 Answers2

1

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;

        }
    });

    // ------------------------------------------------------------------------------------
  • A proper way was to include both of components in a hierarechy such us FrontComponent > BackComponent in order to use the EventFilter without calculating the positions. But since the contexts oblige to no change the hierarechy this was the best solution. – FoxHound-Amine Mar 06 '17 at 10:01
  • A proper way was to include both of components in a hierarechy such us FrontComponent > BackComponent in order to use the EventFilter without calculating the positions. But since the contexts oblige to no change the hierarechy this was the best solution – FoxHound-Amine Mar 10 '17 at 10:41
  • A proper way was to include both of components in a hierarechy such us FrontComponent > BackComponent in order to use the EventFilter without calculating the positions. But since the contexts oblige to no change the hierarechy this was the best solution – FoxHound-Amine Mar 10 '17 at 10:41
  • A proper way was to include both of components in a hierarechy such us FrontComponent > BackComponent in order to use the EventFilter without calculating the positions. But since the contexts oblige to no change the hierarechy this was the best solution – FoxHound-Amine Mar 10 '17 at 10:41
1

This solution is somewhat similar to Amine's. Instead of defining an event filter, it just implements interception of events in the event handler of the front component and forwards the mouse click event to the rear component. Perhaps the event filter approach is preferred, but the handler in this answer seems to do the job OK at least in this case.

To work out if the back component is under the point clicked on the front component the following logic is used:

boolean isInBackComponent = backComponent.contains(
        backComponent.sceneToLocal(
                mouseClicked.getSceneX(), mouseClicked.getSceneY()
        )
);

Sample code:

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); }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406