【问题标题】:How to bring JavaFX Popup to front when focused?聚焦时如何将JavaFX Popup放在前面?
【发布时间】:2019-06-08 08:56:02
【问题描述】:

我的应用程序中有一些 JavaFX Popup。当这些弹出窗口中的任何一个被关注时,我需要将它置于所有其他弹出窗口之上,无论它在 Window.getWindows() 中的索引如何。

我尝试调用像toFront 这样的方法,但它不在Popup 类中。我还尝试在Window.getWindows() 中更改焦点Popup 的索引,但这也没有用,因为我不知道如何在ObservableList 中交换两个元素的索引。

例如

假设我有两个Popup,分别称为p1p2,在每个节点中我分别有节点n1n2,用于移动这些弹出窗口,所以每当n1 被拖动@987654335 @ 应该在顶部,当 n2 被拖动时,p2 应该在顶部。

这是我的最小示例:

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();
    }
    
}

那么解决这个问题的方法是什么?

【问题讨论】:

  • @nicomp ,我问过当有很多弹出窗口时如何将任何弹出窗口置于前面,而不是如何聚焦/取消聚焦,因为如果我想检查一个弹出窗口是否聚焦,我会将鼠标按下事件添加到我在弹出窗口中使用的节点以使弹出窗口可移动。即焦点不是这里的问题。
  • setAlwaysOnTop() ?
  • @nicomp setAlwaysOnTop() 也不适用于Popup
  • 我没有说您的代码无效。我指出了解决您的问题的方法以及使修复为您工作的方法。实施的决定取决于您。我不同意“荒谬”的表征。有时我们必须做我们必须做的事情。

标签: java user-interface javafx javafx-11


【解决方案1】:

出于某种我不明白的原因,toFront/back 仅在 Stage 上实现,而不在其父类上实现,即使管理堆叠的实际协作者已经在 Window 中可用:

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() 是 Window 中的一个包私有方法,它返回内部类 TKStage。因此,如果您被允许变脏(因为访问内部类并且必须通过反射访问 - 所有这些都带有通常响亮的“当心”!)将是:

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();
    }
}

需要在 javafx.graphics 中导出/打开未导出的包 - 编译器和运行时错误会为您提供指导(无论如何,我的上下文已经过大量调整,所以不知道具体添加了哪些包)

【讨论】:

  • 如何在基于模块的项目中使用 TKStage?
  • 您必须按照错误消息告诉您的 --add-exports 和 --add-opens 进行翻译,直到全部消失;)
  • 我添加了Method m = popup.getClass().getDeclaredMethod("getPeer");m.setAccessible(true); 但它给出了错误java.lang.NoSuchMethodException: javafx.stage.Popup.getPeer(),请帮助
  • 该方法是在 Window 中声明的——如果你想弄脏你需要阅读源代码:-)
  • 我已经阅读了 Window 和 Popup 的源代码,我已经尝试过 Method m = Window.class.getDeclaredMethod("getPeer");m.setAccessible(true); 它给出了 javafx.graphcs 无法打开 javafx.stage 的错误,所以我添加了 --add-opens=javafx.graphics/javafx.stage=ALL-UNNAMED --add-exports=javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED --add-exports=javafx.base/com.sun.javafx.reflect=ALL-UNNAMED 但是打开错误仍未消除
【解决方案2】:

这是带有阶段的解决方案,它是我发现的唯一解决方法,即使您讨厌拥有多个阶段的想法,如果您想要这样的功能。如果您决定坚持将它们留在后台,那也很酷。解决您的阶段困境的一个想法是在使用时使用阶段队列删除,如果所有阶段都显示,则在隐藏阶段时添加一个新阶段发送到队列末尾

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); }

}

【讨论】:

  • 在这种方法中还有一个我要避免的问题是,当其他阶段被聚焦时,主要阶段失去焦点。所以,这不是我的问题的解决方案:(
  • 我在第一条评论中提到的问题仍然存在。并且由于 Stage 的本质是只要专注,它就会出现在前面,所以即使没有 parent.toFront();,这个程序也会以同样的方式运行。
  • 所以您想要始终专注于初级阶段?然后就看不到弹窗了
  • 是的,而且总是在其他所有事情的底部,这就是我使用Popup的原因
  • 您可以通过添加.setAlwaysOnTop(true); 来解决这个问题,以将焦点放在您的应用程序上,但窗口会显示在顶部
猜你喜欢
  • 2011-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 2015-05-16
  • 1970-01-01
  • 1970-01-01
  • 2021-05-03
相关资源
最近更新 更多