Test for Label overrun
Asked Answered
E

4

12

I have a multiline label whose text has a chance of overrunning. If it does this, I want to decrease the font size until it isn't overrunning, or until it hits some minimum size. This would hopefully make it so that the label will change size until the entire string is visible.

My problem it that I am not sure how to test to see if the text has overrun. I have tried testing to see if the label's text ends with the ellipse string, but I believe the ellipse is not technically added to the textProperty of the label. So does anyone know of a good way to test for this?

Elanorelapid answered 12/7, 2013 at 18:39 Comment(2)
Bump, I want to know this as well...Blowing
Check also this stackoverflow.com/a/35065526Thomson
K
3

The short and disappointing answer: You simply cannot do this in a reliable way.

The slightly longer answer is, that the label itself does not even know whether it's overflown or not. Whenever a label is resized, the skin class (LabeledSkinBase) is responsible for updating the displayed text. This class, however, uses a JavaFX utils class to compute the ellipsoided text. The problem here is that the respective method just returns a string that is ellipsoid if this is required by the label's dimensions. The skin itself never gets informed about whether the text was actually ellipsoided or not, it just updates the displayed text to the returned result.

What you could try is to check the displayed text of the skin class, but it's protected. So you would need to do is to subclass LabelSkin, and implement something like that:

package com.sun.javafx.scene.control.skin;

import java.lang.reflect.Field;

import javafx.scene.control.Label;

@SuppressWarnings("restriction")
public class TestLabel extends LabelSkin {
  private LabeledText labelledText;

  public TestLabel(Label label) throws Exception {
    super(label);

    for (Field field : LabeledSkinBase.class.getDeclaredFields()) {
      if (field.getName().equals("text")) {
        field.setAccessible(true);
        labelledText = (LabeledText) field.get(this);
        break;
      }
    }
  }

  public boolean isEllipsoided() {
    return labelledText != null && labelledText.getText() != null && !getSkinnable().getText().equals(labelledText.getText());
  }
}

If you use this skin for you Label, you should be able to detect whether your text is ellipsoided. If you wonder about the loop and the reflection: Java didn't allow me to access the text field by other means, so this may be a strong indicator that you really should not do this ;-) Nevertheless: It works!

Disclaimer: I've only checked for JavaFX 8

Krystakrystal answered 19/12, 2014 at 16:13 Comment(0)
P
3

minisu posted a way to detect an overrun in this answer: https://mcmap.net/q/1011473/-when-a-javafx-39-s-controller-is-not-enough-to-display-content-it-will-display-quot-quot-at-the-end

This way works for all labeled and I tested it on Buttons with JavaFX 8. You can add a listener for example to the needsLayoutProperty:

labeled.needsLayoutProperty().addListener((observable, oldValue, newValue) -> {
   String originalString = labeled.getText();
   Text textNode = (Text) labeled.lookup(".text"); // "text" is the style class of Text
   String actualString = textNode.getText();

   boolean clipped = !actualString.isEmpty() && !originalString.equals(actualString);

   System.out.println("is " + originalString + " clipped: " + clipped);
});
Pardon answered 8/6, 2018 at 8:25 Comment(1)
Using lookup() feels less intrusive than messing up with the text attribute of the LabeledSkinBase. However, having a listener to the needsLayoutProperty was not reliable in my case (where i needed to set a tooltip in case of text overrun). I tried many different properties (width, boundsInLocal, boundsInParent, etc.), but since I needed a tooltip, I ended up listening to the hoverProperty.Guerrilla
S
1

This work for me !! In javafx

public class LabelHelper{
  public static boolean necesitaTooltip(Font font, Label label){
    Text text = new Text(label.getText());
    text.setFont(font);
    Bounds tb = text.getBoundsInLocal();
    Rectangle stencil = new Rectangle(
            tb.getMinX(), tb.getMinY(), tb.getWidth(), tb.getHeight()
    );

    Shape intersection = Shape.intersect(text, stencil);

    Bounds ib = intersection.getBoundsInLocal();
    return ib.getWidth() > label.getPrefWidth();
  }

  public static void asignarTexto(Label label, String texto){
      label.setText(texto);
      if (necesitaTooltip(label.getFont(), label)){
          Tooltip tp = new Tooltip(texto);

        label.setTooltip(tp);
      }
  }
} 

Only call a asignarTexto(label, texto) for set text a label and check if the text is overrun in the label then add a tooltip for label.

Sacramental answered 25/3, 2019 at 19:8 Comment(1)
This solution may not work well depending on the CSS styling... For the same use case as yours, I used Johann Alfred Verdant Anderson's answer but added a listener on the 'hovered' property instead of "needsLayout".Guerrilla
F
1

It's already been mentioned by Johann that you can use (Text)labeled.lookup(".text") to get the actual displayed text for a Label, then compare it to the intended String... However, in my case, this did not work. Perhaps it was because I was updating the Label with a high frequency, but the actual String was always a few chars less than the intended...

So, I opted to use the setEllipsisString(String value) method to set the ellipsis string (what's appended to the end of a Label when there's overrun, the default being "...") to an (unused) ASCII control character like 0x03 (appropriately named "end of text"), then after each time I set the Label text I check if the last char of the actual String is the control char.

Example using Platform.runLater(Runnable):

import javafx.scene.control.Label;
import javafx.application.Platform;
import javafx.scene.text.Text;
...
Label label = new Label();
...
label.setEllipsisString("\003");
...
final String newText = "fef foo";
Platform.runLater(() -> {
    label.setText(newText);
    String actual = ((Text)label.lookup(".text")).getText();
    // \003 is octal for the aforementioned "end of text" control char
    if (actual.length() > 0 && actual.charAt(actual.length()-1) == '\003') {
        // Handle text now that you know it's clipped
    }
});

Note that you can set the control char to anything really, and it doesn't need to be just one char; however if you opt for a control character, check that it isn't commonly used.

Floor answered 9/12, 2019 at 6:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.