JavaFX如何在更改文本后强制Label更新其prefWidth?

huangapple go评论102阅读模式
英文:

JavaFX how to force Label to update its prefWidth after changing the text?

问题

只需将我的Label更新为在文本更改时适应其文本的prefWidth

首先,您可以将以下代码添加到Label以使其根据文本内容自动更新prefWidth

label.textProperty().addListener((observable, oldValue, newValue) -> {
    label.setPrefWidth(label.prefWidth(-1)); // Use -1 to compute the preferred width based on the current text
});

在您提供的代码中,将这段代码添加到Label创建后的位置,如下所示:

Label label = new Label();
// ...
box.prefWidthProperty().bind(label.prefWidthProperty().add(button.prefWidthProperty()));

// Add the text change listener here
label.textProperty().addListener((observable, oldValue, newValue) -> {
    label.setPrefWidth(label.prefWidth(-1));
});

这样,每当Label的文本更改时,它都会自动调整其prefWidth以适应文本内容。

英文:

Pretty simple thing actually: I want my Label to update its prefWidth to fit the text into itself whenever the text changes.

Initially the Labels text is null and the prefWidth is -1.0 without setting it manually. So when the Labels text changes at runtime the prefWidth is still -1.0 and the Label displays "..." instead of the set text, because it is to small.

The Label is inside an HBox. That HBoxs prefWidth is bound to the sum of its childrens prefWidths and the HBox has enough space to grow.
Here's some code to reproduce:

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);
        box.prefWidthProperty().bind(label.prefWidthProperty().add(button.prefWidthProperty()));

        window.setScene(new Scene(new StackPane(box)));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

How can I get the Label to update its width to fit its text?

EDIT: The root of the Scene is a StackPane not a Pane.

答案1

得分: 2

不要使用绑定来尝试在窗格的最小/首选/最大大小上执行布局是不明智的。HBox已经知道如何计算其首选宽度,并且默认情况下会按照您想要的方式执行(简单版本是将其计算为其子节点的首选宽度之和,尽管实际上更复杂,因为它考虑了间距、填充等)。

控件和布局窗格的prefWidth的默认值都是特殊值Region.USE_COMPUTED_SIZE,这只是一个标志,告诉节点计算其首选大小以向布局它的人报告。标签将通过检查显示其文本(和图形)所需的大小来计算其首选大小,按钮也是如此,布局窗格将检查其子节点的首选大小。Region.USE_COMPUTED_SIZE的实际值为-1.0,这就是为什么每次调用getPrefWidth()时您总是看到该值的原因。

HBox被扩展以填充堆栈窗格的原因是这正是StackPane的行为。从文档中可以看到:

堆栈窗格将尝试调整每个子节点以填充其内容区域。

您可以通过使用不具有该行为的不同根窗格来防止这种情况发生(例如,一个普通的Pane或另一个HBox等):

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);

        window.setScene(new Scene(new HBox(box)));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

或者通过将您的HBox包装在另一个窗格中(这样包装它的窗格将填充StackPane,但您的box不会填充其父窗格):

import javafx.application.Application;
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.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);

        window.setScene(new Scene(new StackPane(new HBox(box))));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

或者通过强制box不超过其首选大小来防止其增长,通过将其maxWidth设置为使用首选大小:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);
        box.setMaxWidth(Region.USE_PREF_SIZE);

        window.setScene(new Scene(new StackPane(box)));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

您没有在问题中指示为什么要在StackPane的父节点中使用HBox。由于StackPane的唯一功能是按z顺序叠加其子节点,如果您正在使用堆栈窗格,那么您可能正在在其上方或下方添加其他节点。因此,可能根本不需要防止HBox的大小调整(因为它不会影响叠加),但也许只需要确保HBox的对齐属性(以及StackPane的其他子节点)设置为与StackPane相同(默认为居中)。也就是说,您可能只需要box.setAlignment(Pos.CENTER)

英文:

It's never a good idea to try to perform layout using bindings on the min/pref/max size of a pane. The HBox already knows how to compute its preferred width, and by default does exactly what you want (the simple version is it computes it as the sum of the preferred widths of its child nodes, though in reality it's more complex, as it accounts for spacing, padding, etc.).

The default value for prefWidth for both controls and layout panes is the sentinel value Region.USE_COMPUTED_SIZE, which is just a flag to tell the node to compute its preferred size for reporting to whoever is laying it out. A label will compute its preferred size by checking the size required to display its text (and graphic), similarly for a button, and layout panes will examine the preferred sizes of their child nodes. The actual value of Region.USE_COMPUTED_SIZE is -1.0, which is why you always see that value when you call getPrefWidth().

The reason the HBox is being expanded to fill the stack pane is because that's exactly the behavior of a StackPane. From the docs:

> The stackpane will attempt to resize each child to fill its content area.

You can prevent this either by using a different root pane, which doesn't have that behavior, (e.g. a plain Pane or another HBox, etc.):

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);

        window.setScene(new Scene(new HBox(box)));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

or by wrapping your HBox in another pane (so the pane in which it is wrapped will fill the StackPane, but your box won't fill its parent):

import javafx.application.Application;
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.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);

        window.setScene(new Scene(new StackPane(new HBox(box))));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

or by forcing the box not to grow beyond its preferred size, by setting its maxWidth to use the preferred size:

import javafx.application.Application;
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.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Spielwiese extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage window) {
        Label label = new Label();
        Button button = new Button("Fill Label");
        button.setOnAction(e -> label.setText("Some Text"));
        HBox box = new HBox(label, button);
        box.setMaxWidth(Region.USE_PREF_SIZE);

        window.setScene(new Scene(new StackPane(box)));
        window.setWidth(800);
        window.setHeight(600);
        window.show();
    }
}

You didn't indicate in your question why you wanted to use a StackPane for the parent of the HBox. Since the sole functionality of a StackPane is to stack its child nodes on top of each other in z-order, if you're using a stack pane you're presumably adding other nodes on top of, or behind it. Consequently, it might actually not be necessary at all to prevent the HBox resizing (because it won't affect the stacking), but maybe it's enough just to ensure the alignment property of the HBox (and other child nodes of the StackPane) is set to the same as the StackPane (which is centered, by default). I.e. you might just need box.setAlignment(Pos.CENTER).

huangapple
  • 本文由 发表于 2020年7月23日 02:22:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/63040801.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定