JavaFX: optimal width of TextFlow with word-wrap


Question

With Textflow.setMaxWidth(double) I can achieve text wrapping.

But how can I adjust the width of TextFlow afterwards so that it is based on the actual wrapping position?

In other words, how to let the TextFlow bounds snap to all of its children Text bounds to get rid of the empty space on the right:

actual vs. desired

**Edit** I have made some progress on this issue. My class derived from TextFlow now contains:

    double maxChildWidth = 0;
    for (Node child : getManagedChildren()) {
        double childWidth = child.getLayoutBounds().getWidth();
        maxChildWidth = Math.max(maxChildWidth, childWidth);
    }
    double insetWidth = getInsets().getLeft() + getInsets().getRight();
    double adjustedWidth = maxChildWidth + insetWidth;
    setMaxWidth(adjustedWidth);

Unfortunately, this approach does not seem to be accurate yet, since it results in a second text flow change in some cases.

1
2
10/27/2013 4:53:20 PM

Accepted Answer

The solution you posted in your question seemed to work OK for me on testing.

In the image below the scene has been resized to trigger wrapping of the TextFlow (shown in red). On the right, the TextFlow does not exactly wrap at the last visible character, because the last character in the line is a space, so the wrapping occurs after the space. Taking into account the TextFlow bounds are flush to all of the text as required.

wrapped box

If you unclamp the max width, you get the default behavior of the text box which is to have the width grow (as you can see below).

unclamped max width

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.stage.Stage;

public class TextFlowWrapper extends Application {

  @Override
  public void start(Stage stage) {
    TextFlow textFlow = new TextFlow(
      new Text(
        "Box with long description that should be wrapped"
      )
    ) {
      @Override
      protected void layoutChildren() {
        super.layoutChildren();

        double maxChildWidth = 0;
        for (Node child : getManagedChildren()) {
          double childWidth = child.getLayoutBounds().getWidth();
          maxChildWidth = Math.max(maxChildWidth, childWidth);
        }
        double insetWidth = getInsets().getLeft() + getInsets().getRight();
        double adjustedWidth = maxChildWidth + insetWidth;

        setMaxWidth(adjustedWidth);
      }
    };

    textFlow.setStyle("-fx-background-color: red");
    textFlow.setMaxWidth(Control.USE_PREF_SIZE);
    textFlow.setMaxHeight(Control.USE_PREF_SIZE);

    Button unclamp = new Button("Unclamp max width");
    unclamp.setOnAction(e -> textFlow.setMaxWidth(Double.MAX_VALUE));

    StackPane wrappedText = new StackPane(textFlow);

    VBox vbox = new VBox(
        unclamp,
        wrappedText
    );
    VBox.setVgrow(wrappedText, Priority.ALWAYS);

    Scene scene = new Scene(
        vbox
    );

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

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

The solution is a little bit of a hack, you might want to ask one of the JavaFX developers for some input on the openjfx developer list. Maybe some additional api like a boolean pack property which would conditionally update the internal textflow layout algorithm to automatically pack the textflow to a minimal size would be a way to do this better. Part of the difficulty is that it is a hard question to write in such a way that the issue is easily understandable. It is also hard to say if this is some kind of corner case or if it is something that more people will encounter (hence perhaps justifying the complexity of an additional API).

1
10/28/2013 5:24:24 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon