Reputation:
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
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
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