在 JavaFX 中获取节点的高度(生成布局传递)

2022-09-03 09:42:39

如何在JavaFX中获取节点的高度或首选高度,我有3个,我想将节点添加到最自由的面板中,例如:VBox

           Childrens      Total Height of the children's(Sum)
VBoxA          5                     890
VBoxB          4                     610
VBoxC          2                     720

在这种情况下,最自由的是,我用这种方法计算最自由的窗格:VBoxB

private int getFreerColumnIndex() {
    if(columns.isEmpty())
        return -1;

    int columnIndex = 0;
    int minHeight = 0;
    for(int i = 0; i < columns.size(); i++) {
        int height = 0;
        for(Node n : columns.get(i).getChildren()) {
            height += n.getBoundsInLocal().getHeight();
        }

        if(i == 0) {
            minHeight = height;
        } else if(height < minHeight) {
            minHeight = height;
            columnIndex = i;
        }

        if(height == 0)
            break;
    }

    return columnIndex;
}

此方法仅在我当时添加1个元素时才有效。但是,如果我当时添加更多元素:

for (int i = 0; i < 10; i++) {
    SomeNode r1 = new SomeNode ();
    myPane.addElement(r1);
}

该方法返回相同的索引。这是因为新节点在本地还没有 heigth。
所以这行:getFreerColumnIndex

height += n.getBoundsInLocal().getHeight(); 

将随新节点一起返回。0

有人知道如何获得节点的强度吗?


额外:

SomeNode扩展Node

Method addElement() at myPane:

public void addElement(final Node element) {
     index = getFreerColumnIndex();
     columns.get(index).getChildren().add(element);
}

额外2:

假设我们有3个vbox:之前:

 A      B      C
 |      |      |
        |      |
        |

跑:

for (int i = 0; i < 10; i++) {
    SomeNode r1 = new SomeNode ();
    myPane.addElement(r1);                      
}

后:

 A      B      C
 |      |      |
 |      |      |
 |      |
 |
 |
 |
 |
 |
 |
 |
 |

正确

 A      B      C
 |      |      |
 |      |      |
 |      |      |
 |      |      |
 |      |      |
 |

|= 某个节点


答案 1

您需要做的是:

  1. 创建要放置在 VBox 中的节点。
  2. 将它们添加到某个场景(它可以只是一个新的虚拟场景,没有附加到舞台,但具有与主场景相同的CSS规则)。
  3. 调用 和 在每个节点(或虚拟场景根)上。applyCss()layout()
  4. 测量每个节点的布局边界。
  5. 根据布局算法将节点添加到真实场景中的 VBox。

相关

要了解如何测量节点的大小,请参阅以下问题的答案:

背景

JavaFX 布局计算通过应用 CSS 和布局传递来工作。通常,这是作为脉冲的一部分发生的(内部JavaFX系统中的一种自动60fps刻度,用于检查场景中需要应用新css或布局的任何脏对象)。在大多数情况下,您可以只指定要对场景进行的更改,然后让自动脉冲布局机制处理当时的布局;这样做非常有效,因为这意味着脉冲之间的任何变化都会被批处理,您不需要手动触发布局传递。但是,当您需要在布局发生之前实际获取事物的大小时(如您的情况),则需要在尝试查询节点的高度和宽度范围之前手动触发CSS应用程序和布局传递。

文档

不幸的是,node.applyCSS() 方法的详细 Java 8u20 Javadoc 目前已损坏,因此我将在此处重现 Javadoc 代码中的示例,以便您可以在上下文中看到推荐的用法。对于下一个Java功能版本(8u40),损坏的javadoc被修复,因为RT-38330的Javadoc的一部分对于Node类的几种方法丢失,因此在该版本中,您将能够在JavaFX javadoc中找到以下文本:

如果需要,请将样式应用于此节点及其子节点(如果有)。此方法通常不需要直接调用,但可以与在下一个脉冲之前或场景不在阶段中时调整节点的大小结合使用。layout()

如果节点的场景不为空,则 CSS 将应用于此节点,而不管此节点的 CSS 状态是否为干净。CSS 样式从此节点的最顶层父级应用,其 CSS 状态不是 clean,这可能会影响其他节点的样式。如果节点不在场景中,则此方法为 no-op。场景不必位于舞台上。

此方法不调用该方法。通常,调用方将使用以下操作序列。layout()

parentNode.applyCss();
parentNode.layout();

作为更完整的示例,下面的代码使用 和 查找按钮的宽度和高度,然后再显示舞台。如果对或调用的调用被注释掉,则对 和 的调用将返回零(直到显示舞台后的一段时间)。applyCss()layout()applyCss()layout()getWidth()getHeight()

public void start(Stage stage) throws Exception {
   Group root = new Group();
   Scene scene = new Scene(root);

   Button button = new Button("Hello World");
   root.getChildren().add(button);

   root.applyCss();
   root.layout();

   double width = button.getWidth();
   double height = button.getHeight();

   System.out.println(width + ", " + height);

   stage.setScene(scene);
   stage.show();
}

layout() vs requestLayout()

调用 layout() 将立即执行布局传递。

调用 requestLayout() 将在将来执行布局传递:

请求在渲染下一个场景之前执行布局传递。这是异步批处理的,每个“脉冲”或动画帧发生一次。

因此,requestLayout() 会通知系统需要进行布局,但不会立即进行布局。

通常,您不需要直接调用 requestLayout(),因为大多数时候,JavaFX 系统能够在内部确定场景的某个区域是脏的,并且需要在需要时自动执行布局。

另类

此处的另一个选项是重写父节点的 layoutChildren() 方法,该方法是“在布局传递期间调用以布局此父节点中的子节点”。在这样做时,可能还需要覆盖计算PrefHeight()的方法以及用于prefWidth和min &max height &width的其他计算方法。使用此方法的详细说明很复杂,超出了本答案的范围。


答案 2
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;
import java.io.Closeable;

public class Sizing  extends Application {

  @Override
  public void start(Stage stage) {
    var label = new Label();
    var labelHeight = label.getHeight();
    System.out.println(labelHeight);

    try (var s = new SizingSandbox(label)) {
      labelHeight = label.getHeight();
    }

    System.out.println(labelHeight);
  }

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


class SizingSandbox extends Group  implements Closeable {

  public SizingSandbox(Node... nodes) {
    new Scene(this);
    getChildren().addAll(nodes);
    layItOut();
  }

  @Override
  public void close() {
    try {
      getChildren().removeAll();
    } catch (Exception e) { }
  }

  private void layItOut() {
    applyCss();
    layout();
  }
}

// output:
// 0.0
// 17.0

推荐