-1

This seems like a simple question, but I can't seem to find any online answers. I have a TextField with some initialized text within it, and I want to find a way to grow the TextField width as the user types in the box. For example, with this code:

TextField iterations = new TextField("1000");
iterations.setPrefWidth(50);

The width is 50, but if the user typed "1000000" into the box I'd want the width to dynamically adjust to be just wide enough. Is there a simple solution for this, or would I need to do something overkill like adding EventHandlers to constantly adjust the width?

Caedmon
  • 112
  • 7
  • 1
    just add a listener to it, that checks the content, and based on that, calls a setSize and whatever else you need – Stultuske May 24 '23 at 11:15
  • @Stultuske _calls a setSize_ no, never ;) – kleopatra May 24 '23 at 16:46
  • 2
    why? Sounds like a flawed ui to start with: sizes should be stable (not erratically changing while typing), so this smells like an xy-problem. first step is to define the _real_ goal. If you _really_ need dynamically changing width (don't forget to think about when exactly it should change and what should happen with boundary cases like empty text and reaching screen width, f.i.), use semantic API like prefColumnCount – kleopatra May 24 '23 at 16:59
  • @kleopatra It seems odd but it's pretty necessary to my UI. Its for a fractal renderer where the input length can vary greatly. For example "0. 323 + 1.34i" is a valid input, but "0. 323385932352562 - 0.000323420003025i" is also a valid input where every digit drastically changes the rendered image. These text fields overlay the rendered image in a window, meaning I'd like them to be as small as possible, but able to grow in length. I thought there might be a very simple way to do this, but it's still worth it to take a more complicated approach. – Caedmon May 24 '23 at 20:52

1 Answers1

3

The easiest approach is to bind the column count to the text length, but it won’t look very accurate, since a TextField uses its column count to account for the widest characters of the font:

iterations.prefColumnCountProperty().bind(
    Bindings.max(2, iterations.lengthProperty()));

(Looks pretty good if you only type W characters.)

A better-looking, but more complicated, solution is to use the TextField’s skin to figure out the proper size:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.skin.TextFieldSkin;
import javafx.scene.layout.HBox;

public class GrowingTextField
extends Application {
    private double margin = -1;
    private double minWidth = -1;

    @Override
    public void start(Stage stage) {
        TextField iterations = new TextField(" ");
        iterations.setPrefColumnCount(2);
        iterations.skinProperty().addListener((o, old, skin) -> {
            if (skin != null && margin < 0) {
                TextFieldSkin textFieldSkin = (TextFieldSkin) skin;
                Platform.runLater(() -> {
                    margin = textFieldSkin.getCharacterBounds(0).getMinX();
                    minWidth = iterations.prefWidth(0);
                    Platform.runLater(() -> iterations.setText(""));
                });
            }
        });

        iterations.textProperty().addListener((o, oldText, text) -> {
            double width;
            if (text.length() <= iterations.getPrefColumnCount()) {
                width = minWidth;
            } else {
                TextFieldSkin skin = (TextFieldSkin) iterations.getSkin();
                double left = skin.getCharacterBounds(0).getMinX();
                double right = skin.getCharacterBounds(
                    text.offsetByCodePoints(text.length(), -1)).getMaxX();
                width = right - left + margin * 2;
            }
            Platform.runLater(() -> iterations.setPrefWidth(width));
        });

        HBox pane = new HBox(iterations);
        pane.setPadding(new Insets(12));

        stage.setScene(new Scene(pane, 800, 150));
        stage.setTitle("Growing TextField");
        stage.show();
    }

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

Output

enter image description here


I do question why one would need this. Why not just give the TextField a large column count to start with, like a web browser’s URL field?

SedJ601
  • 12,173
  • 3
  • 41
  • 59
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thank you, that looks really good! I'm programming a fractal renderer and some of the input parameter lengths can greatly vary. For example "0.0155 - 0.2i" may be a normal input, but "0.015593829302932 - 0.034233425223i" is also a valid input where every digit is important to be accurate. These text fields also block some content when long, so I wanted them to be only as large as needed. If you click on the Parameters tab on this link you'll see what I mean: https://attr.actor/snapshots/qjthxvxlvdhwbxjx – Caedmon May 24 '23 at 20:44
  • The offered binding solution would likely work well with a fixed-width font ([CSS family 'monospace'](https://openjfx.io/javadoc/20/javafx.graphics/javafx/scene/doc-files/cssref.html#typefont)). – jewelsea May 24 '23 at 22:45
  • Given that the field is kind of managing its own size and you probably don't want to upset that, if this is adopted, you might also wish to make the node [unmanaged](https://openjfx.io/javadoc/20/javafx.graphics/javafx/scene/Node.html#managedProperty), so it's sizing is not affected by the layout pane it is in and vice-versa. – jewelsea May 24 '23 at 22:54