Reputation: 41
I am currently working on a JavaFX based GUI Program for video editing. Here I have run into a problem with ScrollPane
s. I cannot figure out how to make a ScrollPane
(truly) pickOnBounds=„false“
. Meaning that I can actually click through the ScrollPane
onto the Node
s below. I think that this may be an issue with the Viewport of the ScrollPane
. I have tried to give a massively simplified version of our program below:
Styles.css
.scroll-pane > .viewport {
-fx-background-color: transparent;
-fx-background-radius: 4;
}
sample.fxml
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.ScrollPane?>
<StackPane
xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="sample.Controller" fx:id="myStackPane">
<Pane onMousePressed="#onMouseInteraction" style="-fx-background-color: red"/>
<AnchorPane fx:id="markerIconPane" pickOnBounds="false">
<ScrollPane style="-fx-background-color: transparent" pickOnBounds="false" vbarPolicy="ALWAYS" hbarPolicy="ALWAYS"/>
</AnchorPane>
</StackPane>
Controller.java
package sample;
import javafx.scene.input.MouseEvent;
import javafx.fxml.FXML;
public class Controller implements Initializable {
//...
public void onMouseInteraction(MouseEvent mouseEvent) {
System.out.println(mouseEvent.getX());
}
}
Main.java
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
root.getStylesheets().add("./Styles.css");
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Now you can clearly see the scrollbars in the top left. When you press on the red outside the scrollbars, onMouseInteraction()
gets called but when you press within the scrollbars nothing happens.
Thanks in advance
Philipp
Upvotes: 4
Views: 1229
Reputation: 1918
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.
Upvotes: 1
Reputation: 20914
I don't know if this is a suitable solution for you, but you can change the sample.fxml file and add onMousePressed="#onMouseInteraction"
to the ScrollPane
.
Here is the file sample.fxml with my proposed change.
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.control.ScrollPane?>
<StackPane
xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="guitests.jfxtests.sample.Controller" fx:id="myStackPane">
<Pane onMousePressed="#onMouseInteraction" style="-fx-background-color: red"/>
<AnchorPane fx:id="markerIconPane" pickOnBounds="false">
<ScrollPane onMousePressed="#onMouseInteraction" style="-fx-background-color: transparent" pickOnBounds="false" vbarPolicy="ALWAYS" hbarPolicy="ALWAYS"/>
</AnchorPane>
</StackPane>
Then when you click inside the ScrollPane
, method onMouseInteraction()
is invoked just as it is when you click inside the Pane
.
Upvotes: 0