David
David

Reputation: 537

JavaFX: TextArea automatic height expanding without scrollbar

Can I force the TextArea control to automatic expanding the height?

In the following case, I would like to see the scrollbar at ScrollPane control, not at TextArea control.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>

<ScrollPane fitToHeight="true" fitToWidth="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
            minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.91" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="sample.Controller">
    <VBox style="-fx-background-color: bisque">
        <TextField/>
        <TextArea VBox.vgrow="ALWAYS">
            <VBox.margin>
                <Insets top="20.0"/>
            </VBox.margin>
        </TextArea>
    </VBox>
</ScrollPane>

TextArea expanding the height

Upvotes: 0

Views: 4865

Answers (2)

Marcin Radoszewski
Marcin Radoszewski

Reputation: 21

I'm not saying that I like the solution. It's hacky as hell but actually works a lot better then the one with textHolder - is more stable. It's written in Kotlin with TornadoFX. For Java it should work the same, but probably with a lot more lines of code ;)

The principle is basically the same, but instead of dedicated textHolder object we are using the actual Text node inside the TextArea.

class ExpandableTextArea : TextArea() {
    init {
        addClass("expandable")
        isWrapText = true

        children.onChange { a ->
            val scrollPane = a.list.first() as ScrollPane
            val contentView = scrollPane.content as Region
            contentView.childrenUnmodifiable.onChange { b ->
                b.next()
                if (b.list.size == 2) {
                    val group = b.list[1] as Group
                    group.children.onChange { c ->
                        val text = c.list.first() as Text
                        text.layoutBoundsProperty().onChange {
                            if (it != null) {
                                val targetHeight = it.height + font.size
                                prefHeight = targetHeight
                                minHeight = targetHeight
                            }
                        }
                    }
                }
            }
        }
    }
}

Of course you still need the same CSS to turn the scrollbars off:

.text-area > .scroll-pane{
  -fx-hbar-policy:never;
  -fx-vbar-policy:never; 
}

Please note that here I'm also setting the minHeight because I want this node to expand "brutally" so it never has to scroll. You can remove that and leave setting the prefHeight but if there is not enough space the node will stop growing and will be scrollable by mouse but without the scrollbars which can be confusing.

BE WARNED: if for some forsaken reason the TextArea children tree structure changes this will blow up.

Upvotes: 0

Bo Halim
Bo Halim

Reputation: 1776

For now the only solution that is close to your problem is given by @Uluk Biy, that i found here, what i did is just fit his logic, and hide the ScrollBars. The only problem is the size of the TextArea which is binded to that of the Text and so it starts with a minimum height at the beginning of the edition, here is the complete code :

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Launcher extends Application{

private Pane root = new Pane();
private Scene scene;

private ScrollPane scroller;
private Pane content = new Pane();

private TextField textF = new TextField();
private TextArea textA = new TextArea();

private Text textHolder = new Text(); 
private double oldHeight = 0;


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

    root.getChildren().addAll(yourSP());
    scene = new Scene(root,300,316);
    stage.setScene(scene);
    stage.show();

}

private ScrollPane yourSP(){

    content.setMinSize(300, 300); 
    textF.setPrefSize(260, 40); 
    textF.setLayoutX(20);
    textF.setLayoutY(20); 
    textA.setPrefSize(260, 200);
    textA.setLayoutX(20);
    textA.setLayoutY(80); 

    textA.getStylesheets().add(getClass().getResource("texta.css").toExternalForm());
    content.getChildren().addAll(textA,textF);

    /*************************@Uluk Biy Code**************************/
    textA.setWrapText(true);
    textHolder.textProperty().bind(textA.textProperty());
    textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
        @Override
        public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
            if (oldHeight != newValue.getHeight()) {
                oldHeight = newValue.getHeight();
                textA.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20); 
                System.out.println(textHolder.getLayoutBounds().getHeight());
            }
        }
    });
    /****************************************************************/

    scroller = new ScrollPane(content);
    scroller.setHbarPolicy(ScrollBarPolicy.NEVER); 
    scroller.setPrefSize(300, 316); 


    return scroller;
}


public static void main(String[] args) {

    launch(args); 

  }

 }

Of course the code can be adapted to fxml format, I just have not had enough time to do it, and here is the style of the TextArea:

.text-area > .scroll-pane{

-fx-hbar-policy:never;
-fx-vbar-policy:never; 

}

good luck for the continuation !

Upvotes: 2

Related Questions