Assuming the Pane
element is the viewport you mentioned; You could add the onMouseInteraction
handler as an EventFilter to the stackpane so it is in the correct eventchain. E.g.
myStackPane.addEventFilter(Controller::onMouseInteraction) ;
That way the Pane
is not needed as an extra eventlayer, although the downside is that you cannot achieve adding the EventFilter from only the fxml file. You can however add the statement in the init method of the controller as an alternative.
If you want a visual overlay in the Pane
you can set its mouseTransparent
property to true. This way it won't interfere with the event (as if it weren't there)
If you cannot put the EventFilter in the eventchain there is not really a good solution. In javafx8 I 'hacked' the system by propagating the event with a modified PickResult
. But in the later versions the used (depricated) methods has been made private (i.e. Not usable without heavy reflection).
EDIT
After carefully rereading your comments, I think I understand the problem a bit more. It is quit tricky to pull something like that off, so I created a small test on how I would solve it. I am not sure if it is usable for you though, as I have no idea how the substructere is layout/managed. So here we go:
The idea is to make a Parent Node which has no size itself, but only which only layout its children. That way it won't block an eventPick, but is still able to render components on top of others (as clipping is not on by default in JavaFX).
The downside of this is that you need to take care of a lot yourself, as the parent has to have a size of '0'. So things like scrolling/children layout you need to do by yourself.
Below I created an example which only uses an overridden #layoutChildren
method. But in some cases (if you need scrolling for example) it can be handy to implement your own skin specialized for this task. Whatever suits your needs.
public class Test extends Application
{
public static void main(String[] args)
{
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception
{
Scene s = new Scene(this.createContent());
primaryStage.setScene(s);
primaryStage.show();
}
protected Parent createContent()
{
Pane forGround= this.createForGround();
StackPane root = new StackPane(this.createBackground(), forGround)
{
@Override
protected void layoutChildren()
{
// since the foreGround is not managed, you need to trigger the 'child-layout' manually by calling #requestLayout
super.layoutChildren();
forGround.requestLayout();
}
};
return root;
}
protected Node createBackground()
{
GridPane pane = new GridPane();
pane.setPadding(new Insets(5.0));
pane.setVgap(5.0);
pane.setHgap(5.0);
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
Button b = new Button("test " + ((i * 2) + j));
// some layout to make things testable
GridPane.setHgrow(b, Priority.ALWAYS);
GridPane.setVgrow(b, Priority.ALWAYS);
GridPane.setFillHeight(b, true);
GridPane.setFillWidth(b, true);
GridPane.setHalignment(b, HPos.CENTER);
GridPane.setValignment(b, VPos.CENTER);
b.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
// let us know when clicked
b.setOnAction(E -> System.out.println(b.getText()));
pane.add(b, i, j);
}
}
return pane;
}
protected Pane createForGround()
{
Circle c1 = new Circle(20.0);
c1.setFill(Color.BLUE);
c1.setManaged(false);
c1.setPickOnBounds(false);
// let us know when clicked
c1.setOnMouseClicked(E -> System.out.println("clicked blue"));
Circle c2 = new Circle(20.0);
c2.setFill(Color.RED);
c2.setManaged(false);
c2.setPickOnBounds(false);
// let us know when clicked
c2.setOnMouseClicked(E -> System.out.println("clicked red"));
////////////////////////////////////////////////////////////////
// this is the old/current method
////////////////////////////////////////////////////////////////
// VBox pane = new VBox();
// pane.setAlignment(Pos.CENTER);
// pane.setStyle("-fx-background-color:#AAAAAAAA");
// pane.getChildren().addAll(c1, c2);
////////////////////////////////////////////////////////////////
// this is the new/improved method
////////////////////////////////////////////////////////////////
Pane pane = new Pane()
{
protected void layoutChildren()
{
// in here you need to layout the children yourself as the default method will not work due to this Pane being size '0'
// get parent bounds instead of this Pane, as it has size '0'
double pw = ((Region)this.getParent()).getWidth();
double ph = ((Region)this.getParent()).getHeight();
// resize
c1.setRadius(ph / 4);
c2.setRadius(ph / 4);
c1.relocate(((pw - (ph / 2)) / 2), 0);
c2.relocate(((pw - (ph / 2)) / 2), ph / 2);
};
};
// make sure you set the pickOnBounds to 'false' on the pane as well, otherwise it will still create a(n event pickable)box around its children, even though it is not rendered!
pane.setPickOnBounds(false);
// set managed to false, so it will remain size 0
pane.setManaged(false);
// set style to make things visible in case of oddities
pane.setStyle("-fx-background-color:#AAAAAAAA");
pane.getChildren().addAll(c1, c2);
return pane;
}
}
Again, I have no clue if this is usable in your case (as I miss implementation details). But whichever way you turn it, I suspect you won't be able to achieve this witout either overriding default mechanics or creating your own Parent-type /skin.