user10485339
user10485339

Reputation:

How to bring JavaFX Popup to front when focused?

I have some JavaFX Popup in my application. And when any of these popups is foucsed, I need it bring on top of every other popups regardless of it's index in Window.getWindows().

I've tried to call method like toFront but it's not in Popup class. I've also tried to change index of focused Popup in Window.getWindows() but that also didn't worked because I don't know how to interchange index of two elements in a ObservableList.

e.g.

Let's say I have two Popup called p1 and p2 and in each I have nodes n1 and n2 respectively which are used to move these popup, So whenever n1 is dragged p1 should come on top and when n2 is dragged p2 should come on top.

Here is my minimal example:

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Popup;
import javafx.stage.Stage;

public class Example extends Application{
    
    public static void main(String... arguments){
        
        launch(arguments);
    }
    
    public void applyTo(Pane node, Popup parent){
        
        final double[] dragDelta = new double[2];
        
        node.setOnMousePressed(e -> {
            dragDelta[0] = parent.getX() - e.getScreenX();
            dragDelta[1] = parent.getY() - e.getScreenY();
            //code to bring parent Popup to front
        });
        
        node.setOnMouseDragged(e -> {
            parent.setX(e.getScreenX() + dragDelta[0]);
            parent.setY(e.getScreenY() + dragDelta[1]);
        });
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception{
        
        Button b1 = new Button("Open p1");
        Button b2 = new Button("Open p2");
        
        HBox n1 = new HBox(new Label("This is p1"));
        HBox n2 = new HBox(new Label("This is p2"));
        n1.setMinSize(200, 120);
        n2.setMinSize(200, 120);
        n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
        n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
        n1.setAlignment(Pos.CENTER);
        n2.setAlignment(Pos.CENTER);
        
        Popup p1 = new Popup();
        Popup p2 = new Popup();
        p1.getContent().add(n1);
        p2.getContent().add(n2);
        
        applyTo(n1, p1);
        applyTo(n2, p2);
        
        b1.setOnAction(event -> {
            if(!p1.isShowing()) p1.show(primaryStage);
            else p1.hide();
        });
        b2.setOnAction(event -> {
            if(!p2.isShowing()) p2.show(primaryStage);
            else p2.hide();
        });
        
        HBox root = new HBox(10, b1, b2);
        root.setAlignment(Pos.CENTER);
        
        primaryStage.setScene(new Scene(root, 500, 200));
        primaryStage.show();
    }
    
}

So what is the solution for this problem?

Upvotes: 2

Views: 1973

Answers (2)

Matt
Matt

Reputation: 3187

Here is the solution with stages it is the only work around I have found at all even though you hate the idea of having multiple stages if you want the functionality this is it. If you decide to stick with leaving them in the background thats cool too. An idea to solve your too may stages dilemma is to use a queue of stages remove when in use and if all are shown add a new one when a stage is hidden send to the end of the queue

public class Example extends Application {


    public void applyTo(Pane node, Stage parent, Stage primaryStage){

        final double[] dragDelta = new double[2];

        node.setOnMousePressed(e -> {
            dragDelta[0] = parent.getX() - e.getScreenX();
            dragDelta[1] = parent.getY() - e.getScreenY();
            //code to bring parent Popup to front
        });

        node.setOnMouseDragged(e -> {
            parent.setX(e.getScreenX() + dragDelta[0]);
            parent.setY(e.getScreenY() + dragDelta[1]);
            primaryStage.requestFocus();
        });

        node.setOnMouseReleased(event -> {
            primaryStage.requestFocus();
        });
    }


    @Override
    public void start(Stage primaryStage) throws Exception{

        Button b1 = new Button("Open p1");
        Button b2 = new Button("Open p2");

        HBox n1 = new HBox(new Label("This is p1"));
        HBox n2 = new HBox(new Label("This is p2"));
        n1.setMinSize(200, 120);
        n2.setMinSize(200, 120);
        n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
        n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
        n1.setAlignment(Pos.CENTER);
        n2.setAlignment(Pos.CENTER);

        Stage p1 = new Stage(StageStyle.UNDECORATED);
        Stage p2 = new Stage(StageStyle.UNDECORATED);
        p1.setAlwaysOnTop(true);
        p2.setAlwaysOnTop(true);
        p1.setScene(new Scene(n1));
        p2.setScene(new Scene(n2));

        applyTo(n1, p1, primaryStage);
        applyTo(n2, p2, primaryStage);

        b1.setOnAction(event -> {
            if(!p1.isShowing()) {
                p1.show();
                primaryStage.requestFocus();
            }
            else
                p1.hide();
        });

        b2.setOnAction(event -> {
            if(!p2.isShowing()) {
                p2.show();
                primaryStage.requestFocus();
            }
            else
                p2.hide();
        });



        HBox root = new HBox(10, b1, b2);
        root.setAlignment(Pos.CENTER);

        primaryStage.setScene(new Scene(root, 500, 200));
        primaryStage.show();
    }

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

}

Upvotes: 0

kleopatra
kleopatra

Reputation: 51525

For some reason I don't understand, toFront/back is only implemented on Stage, not on its parent classes even though the actual collaborator that manages the stacking is already available in Window:

The implementation in Stage:

/**
 * Bring the {@code Window} to the foreground.  If the {@code Window} is
 * already in the foreground there is no visible difference.
 */
public void toFront() {
    if (getPeer() != null) {
        getPeer().toFront();
    }
}

getPeer() is a package-private method in Window that returns the internal class TKStage. So if you are allowed to go dirty (because accessing an internal class and having to access via reflection - all with the usual loud "Beware"!) would be:

protected void toFront(Popup popup) {
    // use your favorite utility method to invoke a method  
    TKStage peer = (TKStage) FXUtils.invokeGetMethodValue(Window.class, popup, "getPeer");
    if (peer != null) {
        peer.toFront();
    }
}

Requires to export/open not-exported packages in javafx.graphics - compiler and runtime errors will guide you (my context is heavily tweaked anyway, so don't know exactly which are added by this)

Upvotes: 2

Related Questions