8

Hello guys I am building a chat server where I use a textfield on the screen to type in the chat message that the user writes, the idea is that it works like a bubble over a persons head when he types a message.

my question is in order to not make a textbox that is too large or too small is there a way to make the textbox resize (trim if you will) so it adjust to the text written in the textfield?

P.S. I'm using JavaFx scenebuilder to do all of this.

Marc Rasmussen
  • 19,771
  • 79
  • 203
  • 364

6 Answers6

20

You can use computeTextWidth method in the com.sun.javafx.scene.control.skin.Utils. the method is used in javafx.scene.control.Label class to calculate the minimum width for label content.

I solved my problem as below:

field.textProperty().addListener(new ChangeListener<String>() {
        @Override
        public void changed(ObservableValue<? extends String> ob, String o,
                String n) {
            // expand the textfield
            field.setPrefWidth(TextUtils.computeTextWidth(field.getFont(),
                    field.getText(), 0.0D) + 10);
        }
    });

I have added a listener to textProperty, and with every text change i change the prefWidth of textfield.

Note: as long as the Utils.computeTextWidth() is not public, I have copied the source code to a new class (TextUtils).

Here is the full source code:

package me.jone30rw.fxcontrol;

import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;

public class TextUtils {

    static final Text helper;
    static final double DEFAULT_WRAPPING_WIDTH;
    static final double DEFAULT_LINE_SPACING;
    static final String DEFAULT_TEXT;
    static final TextBoundsType DEFAULT_BOUNDS_TYPE;
    static {
        helper = new Text();
        DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth();
        DEFAULT_LINE_SPACING = helper.getLineSpacing();
        DEFAULT_TEXT = helper.getText();
        DEFAULT_BOUNDS_TYPE = helper.getBoundsType();
    }

    public static double computeTextWidth(Font font, String text, double help0) {
        // Toolkit.getToolkit().getFontLoader().computeStringWidth(field.getText(),
        // field.getFont());

        helper.setText(text);
        helper.setFont(font);

        helper.setWrappingWidth(0.0D);
        helper.setLineSpacing(0.0D);
        double d = Math.min(helper.prefWidth(-1.0D), help0);
        helper.setWrappingWidth((int) Math.ceil(d));
        d = Math.ceil(helper.getLayoutBounds().getWidth());

        helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH);
        helper.setLineSpacing(DEFAULT_LINE_SPACING);
        helper.setText(DEFAULT_TEXT);
        return d;
    }
}
Adowrath
  • 701
  • 11
  • 24
Mohammad Hassany
  • 898
  • 1
  • 14
  • 30
  • Is it possible to make use of computeTextWidth() somehow by actually creating a Label? I've tried creating a label using the text and then using getMinWidth() and widthProperty().doubleValue(), but both always return 0.0... – Porthos3 Jul 08 '14 at 23:47
  • 2
    Great solution! – Andrew Bystrov Mar 16 '17 at 11:53
11

In JavaFX 8, there is a solution for that, here is the code:

TextField tf = new TextField();
// Set Max and Min Width to PREF_SIZE so that the TextField is always PREF
tf.setMinWidth(Region.USE_PREF_SIZE);
tf.setMaxWidth(Region.USE_PREF_SIZE);
tf.textProperty().addListener((ov, prevText, currText) -> {
    // Do this in a Platform.runLater because of Textfield has no padding at first time and so on
    Platform.runLater(() -> {
        Text text = new Text(currText);
        text.setFont(tf.getFont()); // Set the same font, so the size is the same
        double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField
                + tf.getPadding().getLeft() + tf.getPadding().getRight() // Add the padding of the TextField
                + 2d; // Add some spacing
        tf.setPrefWidth(width); // Set the width
        tf.positionCaret(tf.getCaretPosition()); // If you remove this line, it flashes a little bit
    });
});
tf.setText("Hello World!");
  • In JavaFX 2.2 this code works with little limitations. You can't set the Font(so if you do not use the std-font, you must set it manually).
  • You can't get the padding from a TextField(so if you know the padding, write it hardcoded).

Happy Coding,
Kalasch

Kalaschni
  • 2,301
  • 24
  • 37
  • This was the best answer for me. This answer does not leave a lot of space to the right of the actual words. I did have to do `tf.setPrefWidth(width + 14)` though. – SedJ601 Oct 06 '17 at 19:33
8

Since JavaFX 8, this is by far the simplest:

textField.prefColumnCountProperty().bind(textField.textProperty().length());
Emily L.
  • 5,673
  • 2
  • 40
  • 60
  • 3
    Note that this doesn't work nicely with variable-width fonts (which will most likely be default on a typical TextField). JavaFX seems to compute the pref width out of width of the widest character ('W'), which overestimates the length of actually entered text. – TeMPOraL Nov 23 '17 at 14:32
  • That doesn't work. – Diarsid Nov 27 '21 at 18:07
4

It is time to do some coding behind the scenes(builder) :).
The following code chunk is not a neat solution but better than none. :)

// define width limits
textField.setMinWidth(50);
textField.setPrefWidth(50);
textField.setMaxWidth(400);
// add listner
textField.textProperty().addListener(new ChangeListener<String>() {
    @Override
    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        textField.setPrefWidth(textField.getText().length() * 7); // why 7? Totally trial number.
    }
});
Uluk Biy
  • 48,655
  • 13
  • 146
  • 153
  • 1
    First of all... NICE WORK 2nd can you explain in details what it does?! :) – Marc Rasmussen Oct 05 '12 at 14:42
  • 7
    this assumes that every character of every javafx font in existence is exactly 7px wide, under every circumstance ... which might just be the worst assumption in the history of mankind. Its like flying into space with water pressure from a (giant) hose. – specializt Jun 08 '15 at 13:41
  • Hey @specializt that is not so bad. This assumption may save a bunch of days if there is no other use case than this in your app ;). Otherwise you may always evaluate other answers here or on the similar questions, and of course post your answer if those are not satisfactory. Beside this I will try to update the answer with more recent options, there is a lot improvements since it was posted. Thanks for reminding. – Uluk Biy Jun 08 '15 at 13:53
  • The number 7 works for me, what are you talking about @specializt? – Don Larynx Aug 23 '15 at 07:59
  • 2
    @DonLarynx exactly what i wrote. Its not possible to write it any more simple ... maybe you should ask someone who speaks english for help. – specializt Aug 23 '15 at 09:06
  • @UlukBiy Why do not each of these work on their own? That is, just minHeight or maxHeight or prefWidth. – AMZ Feb 08 '22 at 18:02
3

No font dependent magic required if you use setPrefColumnCount

        tf.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> ob, String o, String n) {
                tf.setPrefColumnCount(tf.getText().length() +1);
            }
        });
adekcz
  • 340
  • 2
  • 13
0

The best / easiest way to do this is to use JavaFX's "USE_COMPUTED_SIZE" option. You can either define it in the FXML, or programatically like this:

TextField textField = new TextField("hello");
textField.setPrefWidth(Control.USE_COMPUTED_SIZE);
Jerry H.
  • 309
  • 2
  • 3
  • This works for other controls like Label, whose computed size is based on the text length. It does *not* work for TextField, whose computed size is "fixed" (defaults to 12EM, can be changed programmatically), and does not change as user types. – pavon Oct 20 '22 at 16:13