【问题标题】:JavaFX binding only applied after resizing the windowJavaFX 绑定仅在调整窗口大小后应用
【发布时间】:2018-11-01 20:27:49
【问题描述】:

当我在我的 Main (JavaFX) 类的 start 方法中运行以下代码时,我得到了奇怪的结果。窗口被显示,但pane(带有绿色边框)的宽度为 0。它应该与容器的高度具有相同的宽度,因为我将 prefWidth 绑定到了 height 属性。 然后,当我调整窗口大小时,绑定生效,窗格变为正方形。请注意,如果我最大化窗口,它也不会应用绑定。

谢谢!

//Create a pane with a min width of 10 and a green border to be able to see it
Pane pane = new Pane();
pane.setStyle("-fx-border-color: green; -fx-border-width: 2");

//Bind the pane's preferred width to the pane's height
pane.prefWidthProperty().bind(pane.heightProperty());

//Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
VBox container = new VBox(pane);
container.setFillWidth(false);
VBox.setVgrow(pane, Priority.SOMETIMES);

//Show the vbox
primaryStage.setScene(new Scene(container, 600, 400));
primaryStage.show();

【问题讨论】:

  • 啊,终于。我一直怀疑如果您使用绑定来管理布局,可能会发生这样的事情;我只是还没有遇到一个例子。谢谢:)。
  • 我认为有一些因素导致了这种行为。首先,绑定是惰性求值的。只要 JavaFX 场景图渲染脉冲不调用 prefWidthProperty 的 getter,那么 prefWidthProperty 将保持旧值,并将 invalid 设置为 true。显然,JavaFX 不会在每个脉冲都查询这个值,这就是绑定似乎不起作用的原因。您可以用 ChangeListener 替换它应该可以工作 - 但它完全基于我的理解。
  • @Jai 使用更改侦听器:pane.heightProperty().addListener((obs, oldH, newH) -> pane.setPrefWidth(newH.doubleValue())); 产生相同的行为...我认为这只是布局代码中的操作顺序。
  • @James_D 我明白了.. 好吧,几个月前我在玩布局,类似于 OP 所做的。一些布局行为很奇怪,比如 OP 提到的这个问题。
  • @Jai Sure;使用绑定或侦听器对于执行布局并不可靠。您无法控制是否针对当前布局通道及时更新属性。从理论上讲,您可能会使用这些技术创建堆栈溢出异常(通过无限递归),但我怀疑库布局代码可以防止这种可能性。

标签: javafx layout javafx-bindings


【解决方案1】:

您在这里遇到的问题是,当容器被布置时,它没有关于计算pane 的宽度和高度的顺序的合理信息。所以本质上发生的是它计算宽度,它(因为它是空的)为零;然后计算高度(填充容器,因为您告诉VBox 这样做)。 之后,prefWidth 属性发生了变化,但到那时实际宽度已经设置好了,所以基本上为时已晚。下次布局传递时,会考虑新的首选项宽度。

我没有检查实际的布局代码,但是(因为默认内容偏差为 null)vbox 的布局代码很可能会执行与以下伪代码等效的操作:

protected void layoutChildren() {

    // content bias is null:
    double prefWidth = pane.prefWidth(-1);
    double prefHeight = pane.prefHeight(-1);

    // no fill width:
    double paneWidth = Math.max(this.getWidth(), prefWidth);
    // vgrow, so ignore preferred height and size to height of the vbox:
    double paneHeight = this.getHeight();
    pane.resizeRelocate(0, 0, paneWidth, paneHeight);

}

最后一次调用实际上会导致窗格的高度发生变化,然后通过绑定导致prefWidth 发生变化。当然,这对于当前的布局通道来说已经太晚了,它已经根据之前的首选宽度计算设置了宽度。

基本上,像这样依赖绑定来管理布局并不是一种可靠的处理方式,因为您正在更改属性(例如本例中的prefWidth)在布局过程中,当调整组件的大小可能已经太晚了。

像这样管理窗格布局的可靠方法是覆盖适当的布局方法,这些方法由布局传递调用以调整组件大小。

对于这个例子,由于宽度取决于高度,你应该为contentBias返回VERTICAL,你应该覆盖computePrefWidth(double height)来返回高度(所以宽度设置为高度):

@Override
public void start(Stage primaryStage) {
    Pane pane = new Pane() {
        @Override
        public Orientation getContentBias() {
            return Orientation.VERTICAL ;
        }
        @Override
        public double computePrefWidth(double height) {
            return height ;
        }
    };
    pane.setStyle("-fx-border-color: green; -fx-border-width: 2");


    //Bind the pane's preferred width to the pane's height
    //    pane.prefWidthProperty().bind(pane.heightProperty());

    //Put the pane in a vbox that does not fill the stage's width and make the pane grow in the vbox
    VBox container = new VBox(pane);
    container.setFillWidth(false);
    VBox.setVgrow(pane, Priority.SOMETIMES);

    //Show the vbox
    primaryStage.setScene(new Scene(container, 600, 400));
    primaryStage.show();
}

【讨论】:

    猜你喜欢
    • 2015-02-11
    • 1970-01-01
    • 1970-01-01
    • 2014-07-04
    • 2020-06-07
    • 2015-06-01
    • 2021-01-08
    • 2019-09-22
    • 1970-01-01
    相关资源
    最近更新 更多