JavaFX 2 TextArea that changes height depending on

2019-08-09 05:17发布

问题:

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?

回答1:

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);
    }
}


回答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.)