1

In JavaFX 2.2, is there any way to make TextArea (with setWrapText(true) and constant maxWidth) change its height depending on contents?

The desired behaviour: while user is typing something inside the TextArea it resizes when another line is needed and decreases when the line is needed no more.

Or is there a better JavaFX control that could be used in this situation?

ItachiUchiha
  • 36,135
  • 10
  • 122
  • 176
Radosław Łazarz
  • 950
  • 1
  • 11
  • 25

2 Answers2

2

You can bind the prefHeight of the text area to the height of the text it contains. This is a bit of a hack, because you need a lookup to get the text contained in the text area, but it seems to work. You need to ensure that you lookup the text node after CSS has been applied. (Typically this means after it has appeared on the screen...)

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ResizingTextArea extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextArea textArea = new TextArea();
        textArea.setWrapText(true);

        textArea.sceneProperty().addListener(new ChangeListener<Scene>() {
            @Override
            public void changed(ObservableValue<? extends Scene> obs, Scene oldScene, Scene newScene) {
                if (newScene != null) {
                    textArea.applyCSS();
                    Node text = textArea.lookup(".text");
                    textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(new Callable<Double>() {
                        @Override
                        public Double call() {
                            return 2+text.getBoundsInLocal().getHeight();
                        }
                    }), text.boundsInLocalProperty()));
                }
            }
        });

        VBox root = new VBox(textArea);
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • It is pretty hacky. I am writing a piece of UI that is used in different places throughout the application, often as a part of a bigger UI graph, that is not always attached to root and visible. Your method relies on guessing the UI state and guessing the binding moment and is therefore a no-go I am afraid. Still, thanks for the suggestion! – Radosław Łazarz May 13 '15 at 14:34
  • Updated to (maybe) make it a bit more flexible (more hacky too, though...?). Also took out lambdas since you tagged this javafx-2. – James_D May 13 '15 at 14:50
  • As there seems to be no more activity on the question I will accept your answer. It is a clever one and what is more important - it works. But honestly, I hope that somebody would post a cleaner one in the future... – Radosław Łazarz May 14 '15 at 15:02
2

Two things to add to James_D's answer (because I lack the rep to comment):

1) For big fonts like size 36+, the text area size was wrong at first but corrected itself when I clicked inside the text area. You can call textArea.layout() after applying CSS, but the text area still does not resize immediately after the window is maximized. To get around this, call textArea.requestLayout() asynchronously in a Change Listener after any change to the Text object's local bounds. See below.

2) The text area was still a few pixels short and the scroll bar still visible. If you replace the 2 with textArea.getFont().getSize() in the binding, the height fits perfectly to the text, no matter whether the font size is tiny or huge.

class CustomTextArea extends TextArea {
    CustomTextArea() {
        setWrapText(true);
        setFont(Font.font("Arial Black", 72));

        sceneProperty().addListener((observableNewScene, oldScene, newScene) -> {
            if (newScene != null) {
                applyCss();
                Node text = lookup(".text");

                // 2)
                prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
                    return getFont().getSize() + text.getBoundsInLocal().getHeight();
                }, text.boundsInLocalProperty()));

                // 1)                   
                text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
                    Platform.runLater(() -> requestLayout());
                });
            }
        });
    }
}

(The above compiles for Java 8. For Java 7, replace the listener lambdas with Change Listeners according to the JavaFX API, and replace the empty ()-> lambdas with Runnable.)