JavaFX - 将阶段大小绑定到根节点的首选大小

时间:2016-05-26 23:09:02

标签: java data-binding javafx javafx-8

每当javafx.stage.Stage根节点的首选宽度/高度发生变化时,我想自动调整Scene的宽度/高度。

它是一个包含多个javafx.scene.control.TitledPane s的小实用程序窗口(不可由用户调整大小),当其中一个TitledPanes扩展时,窗口的高度应该会增加(否则TitlePane&#39 ; s内容可能超出界限)。

不幸的是,我只能找到javafx.stage.Window.sizeToScene(),它最初将窗口的大小设置为根目录的首选大小。

有没有办法将Stage大小永久绑定到根节点大小?

2 个答案:

答案 0 :(得分:2)

根据DVarga的例子,我制作了以下"解决方案":
InvalidationListener安装在每个子节点的heightProperty上,可能会缩小/增加高度(本例中为两个TitlePanes)。
heightProperty无效时,将重新计算包含窗口的高度(同样遵循窗口装饰)。

示例:

public class SampleApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        final Label lbl1 = new Label("content");
        final TitledPane tp1 = new TitledPane("First TP", lbl1);

        final Label lbl2 = new Label("more content");
        final TitledPane tp2 = new TitledPane("Second TP", lbl2);

        final VBox rootPane = new VBox(tp1, tp2);

        tp1.heightProperty().addListener((InvalidationListener) observable -> {
            updateWindowHeight(rootPane);
        });

        tp2.heightProperty().addListener((InvalidationListener) observable -> {
            updateWindowHeight(rootPane);
        });

        final Scene scene = new Scene(rootPane);
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();
        primaryStage.setResizable(false);
        primaryStage.show();
    }

    private void updateWindowHeight(final VBox rootPane) {
        final Scene scene = rootPane.getScene();
        if (scene == null) 
            return;
        final Window window = scene.getWindow();
        if (window == null)
            return;
        final double rootPrefHeight = rootPane.prefHeight(-1);
        final double decorationHeight = window.getHeight() - scene.getHeight(); // window decorations
        window.setHeight(rootPrefHeight + decorationHeight);
    }

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

尽管它按预期工作,但此解决方案存在一些主要缺点:

  • 您必须在可能缩小/增大的每个节点上安装侦听器
  • 从CCD的角度来看,这是错误的:儿童节点不应该对它们所包含的舞台的大小负责。

我无法获得更清洁的解决方案。 javafx.stage.Stagejavafx.scene.Scene过于受阻(可能的扩展点是最终的或包私有的)来实现它所属的此功能。

<强>更新
仅使用window.sizeToScene()代替

final double rootPrefHeight = rootNode.prefHeight(-1);
final double decorationHeight = window.getHeight() - scene.getHeight();
window.setHeight(rootPrefHeight + decorationHeight);

产生更少的&#34;口吃&#34;调整窗口大小时!

答案 1 :(得分:1)

如果你想/可以设置场景根容器的首选大小

班级Region有一个prefHeightProperty和一个prefWidthProperty,班级Stage有一个setWidth方法和一个setHeight方法。

您可以收听舞台场景的根区域的属性更改,并且在侦听器中可以调用舞台的相应mutator方法:

root.prefHeightProperty().addListener((obs, oldVal, newVal) -> primaryStage.setHeight(newVal.doubleValue()));
root.prefWidthProperty().addListener((obs, oldVal, newVal) -> primaryStage.setWidth(newVal.doubleValue()));

我用于测试的一个例子:

public class Main extends Application {
    @Override
    public void start(final Stage primaryStage) {
        try {
            // Add some controls to set the pref size of the root VBox
            final VBox par = new VBox();
            final TextField tf1 = new TextField();
            final TextField tf2 = new TextField();

            Button b1 = new Button();
            b1.setOnAction(new EventHandler<ActionEvent>() {

                @Override
                public void handle(ActionEvent arg0) {
                    // On button press set the pref size of the VBox to the values of the TextFields
                    par.setPrefSize(Double.parseDouble(tf1.getText()), Double.parseDouble(tf2.getText()));

                }
            });

            par.getChildren().addAll(tf1, tf2, b1);

            Scene scene = new Scene(par,400,400);

            // Attach the listeners
            par.prefHeightProperty().addListener((obs, oldVal, newVal) -> primaryStage.setHeight(newVal.doubleValue()));
            par.prefWidthProperty().addListener((obs, oldVal, newVal) -> primaryStage.setWidth(newVal.doubleValue()));


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

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

更新:如果您不想/无法设置根的首选大小

  

Layouts通过调用来查询节点的首选大小   prefWidth(高度)和prefHeight(宽度)方法。默认情况下,UI   控制计算其首选大小的默认值   关于控件的内容。例如,a的计算大小   Button对象由文本的长度和大小决定   用于标签的字体,加上任何图像的大小。通常情况下,   计算出的大小对于控件和标签而言足够大   完全可见。

     

UI控件还提供默认的最小和最大尺寸   基于控件的典型用法。例如,最大值   Button对象的大小默认为其首选大小,因为您   通常不希望按钮增长任意大。然而   ScrollPane对象的最大大小是无限制的,因为通常是你   希望他们成长以填补他们的空间。

基于此,我们的想法是以ScrollPane为根,使用隐藏的滚动条,因为此控件允许内容自由地&#34;成长。

随着内容的增长,您可以收听ScrollPane内容的heightPropertywidthProperty,并设置Stage的大小。

示例:

public class Main extends Application {
    @Override
    public void start(final Stage primaryStage) {
        try {
            // ScrollPane will be the root
            ScrollPane sp = new ScrollPane();

            // Don't show the ScrollBars
            sp.setHbarPolicy(ScrollBarPolicy.NEVER);
            sp.setVbarPolicy(ScrollBarPolicy.NEVER);

            // TitledPane will be the content of the root
            TitledPane tp = new TitledPane();
            sp.setContent(tp);

            // Set the layout bounds of the TitledPane
            tp.setMinHeight(400);
            tp.setMinWidth(400);
            tp.setMaxHeight(900);
            tp.setMaxWidth(900);

            // Fill the TitledPane with some Buttons and containers to test the growing and shrinking progress
            final VBox vboxTPContentVertical = new VBox();
            final VBox vboxTexts = new VBox();
            final HBox hboxTexts = new HBox();

            HBox hboxButtons = new HBox();
            vboxTPContentVertical.getChildren().addAll(hboxButtons, hboxTexts, vboxTexts);


            Button b1 = new Button("Add row");
            b1.setOnAction((event) -> vboxTexts.getChildren().addAll(new Text("Row1"), new Text("Row2")));

            Button b2 = new Button("Add column");
            b2.setOnAction((event) -> hboxTexts.getChildren().addAll(new Text("Col1"), new Text("Col2")));

            Button b3 = new Button("Remove row");
            b3.setOnAction((event) -> {
                vboxTexts.getChildren().remove(0);
                vboxTexts.getChildren().remove(0);
            });

            Button b4 = new Button("Remove column");
            b4.setOnAction((event) -> {
                hboxTexts.getChildren().remove(0);
                hboxTexts.getChildren().remove(0);
            });

            hboxButtons.getChildren().addAll(b1, b2, b3, b4);
            tp.setContent(vboxTPContentVertical);

            // Set the ScrollPane as root
            Scene scene = new Scene(sp, 400, 400);

            // Now just listen to the heightProperty and widthProperty of the TitledPane
            tp.heightProperty().addListener((obs, oldVal, newVal) -> primaryStage.setHeight(to.doubleValue()));
            tp.widthProperty().addListener((obs, oldVal, newVal) -> primaryStage.setWidth(to.doubleValue()));

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

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