Reputation: 21
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.
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.
Is there a way to make the front component half transparent to specific events?
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();
}
}
Upvotes: 1
Views: 447
Reputation: 159341
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); }
}
Upvotes: 1
Reputation: 21
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;
}
});
// ------------------------------------------------------------------------------------
Upvotes: 1