Search code examples
javajavafxword-wrap

Truncating text after a specific number of lines in JavaFX


Is it possible to truncate a Label or a Text after a fixed number of lines in JavaFX? For the Web there is a CSS property called 'line-clamp'. Unfortunately there doesn't seem to be an equivalent in JavaFX.

With a Label you can either choose to wrap the text or truncate it with ellipsis shown. With the Text object it is at least possible to specify the wrapping with.

Not wrapped and not truncated:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eir mod tempor invidunt ut labore et dolore magna aliquyam

Wrapped and not truncated:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore
magna aliquyam

Not wrapped and truncated:

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, ...

Wrapped and truncated after 2 lines (desired behavior):

Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore ...

Solution

  • I've came up with fairly basic solution (that can be further improved).

    public class MultiLineLabel extends VBox {
    
        private final String text;
        private final List<Label> lines = new ArrayList<>();
    
        public MultiLineLabel(String text, int maxNumberOfLines) {
    
            if (maxNumberOfLines < 1) {
                throw new IllegalArgumentException();
            }
    
            this.text = text;
    
            // Create num. of lines labels
            for (int i=0; i<maxNumberOfLines; i++) {
                final Label label = new Label();
                lines.add(label);
                label.setTextOverrun(OverrunStyle.CLIP);
            }
    
            getChildren().addAll(lines);
        }
    
        @Override
        protected void layoutChildren() {
            super.layoutChildren();
    
            String remainingText =  this.text;
    
            for (Iterator<Label> iterator = lines.iterator(); iterator.hasNext(); ) {
                final Label currentLine = iterator.next();
    
                // If this is the last line, just set the text and finish
                if (!iterator.hasNext()) {
                    currentLine.setText(remainingText);
                    currentLine.setTextOverrun(OverrunStyle.ELLIPSIS);
                    break;
                } else {
                    final String textThatFitsInThisLine = Utils.computeClippedText(currentLine.getFont(), remainingText, this.getWidth(), OverrunStyle.CLIP, null);
                    currentLine.setText(textThatFitsInThisLine);
                    remainingText = remainingText.substring(textThatFitsInThisLine.length());
                }
            }
        }
    
    }
    

    ... that can then be used like:

    public class MultiLineLabelSSCCE extends Application {
    
        @Override
        public void start(Stage stage) {
            final MultiLineLabel multiLineLabel =
                      new MultiLineLabel("This is some interesting text. The question is whether it's truncated properly. Let's see.", 3);
            stage.setScene(new Scene(multiLineLabel));
            stage.show();
        }
    
    }
    

    Demo (of the above solution):

    enter image description here

    I hope this suffices for what you need it for.

    Side note:

    This solutions is based on com.sun.javafx.scene.control.skin.Utils which is considered an implementation detail and is not part of JavaFx API. To run the code using this solution you might need to add: --add-exports=javafx.controls/com.sun.javafx.scene.control.skin=org.example to your run configuration (VM options)