Binding TextArea height to its content
Asked Answered
S

4

10

JavaFX: Is it possible to bind TextArea height (row count) to the height of its content?
I would like to dynamically change height of TextArea while writing the text.

Shantay answered 23/3, 2013 at 23:27 Comment(0)
H
3

Have a look at JavaFX utility class. Although this is not a solution using binding, computeTextHeight(Font font, String text, double wrappingWidth) method can help you.

Hexahedron answered 24/3, 2013 at 17:58 Comment(0)
O
2

This is an exact, simple & working solution:

SimpleIntegerProperty count = new SimpleIntegerProperty(20);
int rowHeight = 10;

txtArea.prefHeightProperty().bindBidirectional(count);
txtArea.minHeightProperty().bindBidirectional(count);
txtArea.scrollTopProperty().addListener(new ChangeListener<Number>(){
    @Override
    public void changed(ObservableValue<? extends Number> ov, Number oldVal, Number newVal) {
        if(newVal.intValue() > rowHeight){
            count.setValue(count.get() + newVal.intValue());
        }
    }
});

Alternatively you can use lambdas to simply the syntax even further:

SimpleIntegerProperty count=new SimpleIntegerProperty(20);
int rowHeight = 10;

textArea.prefHeightProperty().bindBidirectional(count);
textArea.minHeightProperty().bindBidirectional(count);
textArea.scrollTopProperty().addListener((ov, oldVal, newVal) -> {
    if(newVal.intValue() > rowHeight){
        count.setValue(count.get() + newVal.intValue());
    }
});
Occurrence answered 3/9, 2014 at 14:5 Comment(4)
This is not sufficient. For one thing, if you stay scrolled at the top of a TextArea (and thus scrollTop stays at zero), it will never expand the size to be what is needed.Fritzfritze
Neil Brown Have you even tried this before down voting? You don't seem to have understood the logic. scrollTop increases on writing text of length > width of textArea. Therefore count increases and effectively textArea height increases. This in turn sets scrollTop to zero. If you print newval.intValue() inside changelistnener you'd understand what's happening. The printed values would be look like: 16 0 16 0 16 0...Occurrence
My point is you can't "scroll" as there's no scroll bar because scrollTop is reset to 0 after increase in textArea heightOccurrence
You're right: I hadn't understood what scrollTop would do. However I had tried the code - as I press enter in the blank text box to make blank new lines, the scroll bar appears and height increases but usually not enough to get rid of scroll bar. I now see though that typing text on the blank lines leads to the height getting corrected. When I press backspace to delete those lines, scrollTop is never modified and thus the field never shrinks to fit - which is required for me, though maybe not for the original questioner. Tried to remove my downvote, but it's locked in unless answer edited.Fritzfritze
C
1

A solution that workq fine in javafx8 (hiding toolbar is inspired from JavaFX TextArea Hiding Scroll Bars):

class MyTextArea extends TextArea {

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        ScrollBar scrollBarv = (ScrollBar) this.lookup(".scroll-bar:vertical");
        if (scrollBarv != null) {
            System.out.println("hiding vbar");
            ((ScrollPane) scrollBarv.getParent()).setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        }
        ScrollBar scrollBarh = (ScrollBar) this.lookup(".scroll-bar:horizontal");
        if (scrollBarh != null) {
            System.out.println("hiding hbar");
            ((ScrollPane) scrollBarh.getParent()).setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        }
    }

    @Override
    protected double computePrefWidth(double width) {
        Bounds bounds = getTextBounds();
        Insets insets = getInsets();
        double w = Math.ceil(bounds.getWidth() + insets.getLeft() + insets.getRight());
        return w;
    }

    @Override
    protected double computePrefHeight(double height) {
        Bounds bounds = getTextBounds();
        Insets insets = getInsets();
        double h = Math.ceil(bounds.getHeight() + insets.getLeft() + insets.getRight());
        return h;
    }

    //from https://mcmap.net/q/1101740/-binding-textarea-height-to-its-content/19717901#19717901
    public Bounds getTextBounds() {
        //String text = (textArea.getText().equals("")) ? textArea.getPromptText() : textArea.getText();
        String text = "";
        text = this.getParagraphs().stream().map((p) -> p + "W\n").reduce(text, String::concat);
        text += "W";
        helper.setText(text);
        helper.setFont(this.getFont());
        // Note that the wrapping width needs to be set to zero before
        // getting the text's real preferred width.
        helper.setWrappingWidth(0);
        return helper.getLayoutBounds();
    }
}
Contradance answered 8/5, 2014 at 23:12 Comment(1)
I've tried this solution, but I find I get bouncing size; as the library tries to show the scroll bars, the height of the window seems to bounce up and back down again as I edit the text. It's so irritating JavaFX doesn't have a size-to-fit property on TextArea!Fritzfritze
P
0

Here is a simple solution that queries the content's height and sets the preferred height of the textarea. I made it a custom component that can be reused easily:

public class AutoSizedTextArea extends TextArea {
    public AutoSizedTextArea() {
        super();
        getStyleClass().add("AutoSizedTextArea");
        setWrapText(true);


        Platform.runLater(() -> {
            var textElement = lookup(".text");
            
            textProperty().addListener((observable, oldValue, newValue) -> {
                setPrefHeight(textElement.getLayoutBounds().getHeight());
            });
        });
        
    }
}

Hiding the scrollbars was necessary, otherwise it changes width for a moment when you type at the end of a line.

// removing unnecessary inner padding and background effects
.AutoSizedTextArea .content {
  -fx-padding: 0px;
  -fx-background-color: transparent;
}
.AutoSizedTextArea {
  -fx-background-color: transparent;
}

// removing scrollbars
.AutoSizedTextArea .scroll-bar:vertical {
  -fx-pref-width: 1;
  -fx-opacity: 0;
}
.AutoSizedTextArea .scroll-bar:horizontal {
  -fx-pref-height: 1;
  -fx-opacity: 0;
}
Porterfield answered 27/3, 2023 at 14:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.