Hatefiend Hatefiend - 5 months ago 37
Java Question

JavaFX - Adding a ScrollPane popup which closes when user clicks out of it

My title is badly worded because my problem is very hard to describe, so I drew an image for it:

enter image description here

I have an ImageView object which represents a pile of cards (not poker cards but just used them as an example). When this image view is clicked, I need a window to popup that features a ScrollPane and shows them all the card objects that are in the linked list. When the user clicks anywhere outside of the window (and later on, any right mouse button click) the scrollpane window needs to close.

Ways that I have already tried:


  • Scene with APPLICATION_MODAL. Then did Scene.showAndWait(). I didn't like this method because it made another application on the user's taskbar. It also felt clunky and just bad.

  • Changed my root pane to a StackPane, then added this Scrollpane to the stackpane when the user clicked on the deck. This for some reason was really slow and seemed really obtrusive. It was also annoying because my alternate class needed to have access to the root pane (since when it closes, it needs to go to the root StackPane and call .remove() on itself).



Are there any other better ways to accomplish this? My application is going to have many of these piles and so this framework needs to work very well.

Answer

I would still propose to open a new Stage with some restrictions to solve your issues with this approach.

You can use the initOwner property of the Stage to have another Stage as owner, therefore no other icon will appear on the taskbar.

You can use the initStyle property with TRANSPARENT or UNDECORATED StageStlye, this will ensure that only the content is visible.

And in the end you can use the focusedProperty to check whether the Stage lost focus to close it.

Example

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = new BorderPane();
            Scene scene = new Scene(root,400,400);

            Button b = new Button("Open deck");
            b.setOnAction(new EventHandler<ActionEvent>() {

                @Override
                public void handle(ActionEvent event) {
                    Stage popupStage = new Stage();

                    popupStage.initOwner(primaryStage);
                    popupStage.initStyle(StageStyle.UNDECORATED);
                    Scene sc = new Scene(new ScrollPane(), 300, 300);
                    popupStage.setScene(sc);

                    popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {

                        @Override
                        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
                                Boolean newValue) {
                            if(!newValue)
                                popupStage.close();

                        }
                    });

                    popupStage.show();

                }
            });

            root.setCenter(b);


            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

It is also possible of course to not open it in a new Stage, but draw a ScrollPane inside the current Stage which overlaps the content of the Stage using for example an AnchorPane or Group as root, but the first solution has the advantage that you are not bound to the dimensions of main Stage (the popup can have any size that you want).

Comments