0

hello iam facing a problem with my textarea. my goal is to make an expandable textarea which is just a normal textarea except it has no scrollbar, it wraps text and if the user wants to resize the width of the textarea the height needs to be updated.

everything works fine except one thing. lets say I already typed a paragraph into my textarea and then reduce the width of it just as much so that 1 or 2 letter are pushed in the next line because of wrapping then the height is not updating for some weird reason.

beofre i reduce width

after I reduced the width

iam not sure what iam doing wrong, do i use wrong listeners? or is there an problem with my text node? i would be very thankful if anyone can help me because iam sitting at this problem for days now.

here is my code:

@Override
public void start(Stage stage) throws IOException {

    VBox box = new VBox();
    TextArea area = new TextArea();

    area.setWrapText(true);

    area.setMinHeight(27);
    area.setPrefHeight(27);
    area.setMaxHeight(27);

    box.getChildren().add(area);

    Scene scene = new Scene(box);
    scene.getStylesheets().add(this.getClass().getResource("/gui/dumps/test.css").toExternalForm());

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

    area.textProperty().addListener((obs, old, niu) -> {
        setHeight(area);
    });

    area.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
        setHeight(area);
    });
}

public void setHeight(TextArea area) {
    Text text = (Text) area.lookup(".text");

    double height = text.getLayoutBounds().getHeight() + 10;

    area.setMinHeight(height);
    area.setPrefHeight(height);
    area.setMaxHeight(height);
}

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

and my css stylesheet:

.text-area .scroll-pane {
-fx-hbar-policy: NEVER;
-fx-vbar-policy: NEVER;

}

  • 1
    (maybe) unrelated: don't hard-code sizing constraints – kleopatra Apr 25 '22 at 21:42
  • The "right" way to do this is to create a custom skin for the TextArea which does not make use of a ScrollPane. Anything else, as far as I could tell, (and I tried a couple of things), is a bit of a hack that is likely to fail (and always did in my case). Unfortunately, a custom TextAreaSkin (or subclassing and customizing the existing skin) is difficult. I spent a bit of time on it but gave up after I realized how tricky it was. – jewelsea Apr 25 '22 at 23:09
  • Perhaps a different approach would be to have a standard text area with scroll panes, etc when in the editing view, and on commit, replace it with a label (which elides excess content when not in editing view. Like this [editable label](https://stackoverflow.com/questions/25572398/how-do-i-create-an-editable-label-in-javafx-2-2), but making it multi-line and backed with a TextArea rather than a TextField. May not work for you, and may be tricky to get right, just a thought. – jewelsea Apr 25 '22 at 23:14

1 Answers1

3

May be you can start with the below solution and see if you need any further changes.

The idea is to compute the height when the layoutChildren() of the TextArea is completed and then requesting the layout of TextArea to update to the computed height.

There may be other conditions you may need to consider, but I will leave that for you to work with.

enter image description here

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.io.IOException;

public class ResizableTextAreaDemo extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        VBox box = new VBox();
        TextArea area = new TextArea() {
            double insets;
            Node text;

            @Override
            protected void layoutChildren() {
                super.layoutChildren();
                // By this line, all the children layouts are computed.

                // Avoiding repetitive lookup calls.
                if (text == null) {
                    Region scrollPane = (Region) lookup(".scroll-pane");
                    Region content = (Region) lookup(".content");
                    double textAreaInsets = getInsets().getTop() + getInsets().getBottom();
                    double scrollInsets = scrollPane.getInsets().getTop() + scrollPane.getInsets().getBottom();
                    double contentInsets = content.getInsets().getTop() + content.getInsets().getBottom();
                    insets = textAreaInsets + scrollInsets + contentInsets;
                    text = lookup(".text");
                }

                // Compute the total height considering all the required insets.
                double totalHeight = insets + Math.ceil(text.getLayoutBounds().getHeight());
                setTextAreaHeight(this, totalHeight);

                // Finally, requesting layout of TextArea to resize to new value.
                requestLayout();
            }
        };
        area.setWrapText(true);
        box.getChildren().add(area);
        Scene scene = new Scene(box);
        stage.setScene(scene);
        stage.setTitle("TextArea Demo");
        stage.show();
    }

    public void setTextAreaHeight(TextArea area, double height) {
        area.setMinHeight(height);
        area.setPrefHeight(height);
        area.setMaxHeight(height);
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Sai Dandem
  • 8,229
  • 11
  • 26
  • 2
    Neat. The lookups make up for the lack of insight into what is going on privately in the text area skin (which is complex, especially the derivation of the Text nodes in the content and the content size calculation). Lookups are still a bit brittle if the internal implementation of the skin was changed (they kind of break encapsulation). But, in this case, this is far better than trying to implement a new skin or modify the existing skin. – jewelsea Apr 26 '22 at 07:03
  • 2
    nice :) But: generally we should not keep references to looked-up nodes, they will break when the skin is replaced. So either null the reference (f.i. in an overridden createDefault skin, or in a listener to the skin property) or look them up always - curious if you experience any side-effects without keeping the text? @jewelsea structure specified in the css spec is safe for lookup, here everthing except text which isn't mentioned ;) – kleopatra Apr 26 '22 at 08:47
  • 1
    @kleopatra, I agree to what you said about keeping references of the skin properties. I totally missed the point that the skin can be updated/changed. The only reason for keeping the references is to avoid lookup calls repetitively.. but i think in this case it does'nt matter. – Sai Dandem Apr 26 '22 at 11:23
  • 1
    agree with your evaluation (doesn't matter here) - but we have to keep it in mind for production code :) – kleopatra Apr 26 '22 at 11:28