Swing Text kürzendes JLabel

jf

Bekanntes Mitglied
Hallo, ich habe eine Komponente geschrieben, in welcher Text dargestellt wird.
Ist dieser Text länger, als die Komponente breit, so wird dieser mit "..." gekürzt dargestellt.
Leider gibt es hier ein Problem: irgend etwas läuft bei der Berechnung der Länge schief - ich muss stets einen Wert von etwas 50 hinzuaddieren, damit es funktioniert.

Daher dachte ich mir, ich breche es auf das Kern-Problem herunter und erweitere JLabel, welches die erwähnte Funktionalität bietet. Dabei sollte aber beachtet werden, dass man mittels HTML-Tags, die Darstellung des Textes ändern kann:
- diese Tags müssen also bei der Berechnung der Länge übergangen werden
- außerdem müssen die Tags zudem noch ausgewertet werden, da fetter Text nunmal breiter ist als normaler Text.

Mit dieser Anforderung wird das ganze schon etwas kniffliger. Daher wollte ich fragen, ob es nicht schon etwas ähnliches gibt.
- Nicht, dass ich mir die ganze Mühe umsonst mache...
 
Verstehe ich das richtig, du möchtest Text mit automatischem Zeilenumbruch darstellen?
Dann würde ich einfach eine JTextArea nehmen. Die muss ja nicht zwingend nur zur Eingabe genutzt werden. 😉

Gruß Vanny
 
Ist dieser Text länger, als die Komponente breit, so wird dieser mit "..." gekürzt dargestellt.
Leider gibt es hier ein Problem: irgend etwas läuft bei der Berechnung der Länge schief - ich muss stets einen Wert von etwas 50 hinzuaddieren, damit es funktioniert.
Pics or it didn't happen. 😉

Welchen Text soll das JLabel haben? Wo addierst du 50 drauf? Wie sieht es aus und wie soll es aussehen? Welchen LayoutManager nutzt du? KSKB?
 
Verstehe ich das richtig, du möchtest Text mit automatischem Zeilenumbruch darstellen?
Nein, dies ist nicht mein Ziel.
Der Text soll einzeilig dargestellt werden. Wenn er länger ist, als in der PreferredSize angezeigt werden kann, dann soll er hinten abgeschnitten werden. Dabei soll der Text auf "..." enden, damit der Nutzer sieht, dass da noch ein paar Informationen fehlen, so dass er die Maus auf das Label bewegt, um diese im Tooltip zu erhalten.

Dann würde ich einfach eine JTextArea nehmen. Die muss ja nicht zwingend nur zur Eingabe genutzt werden. 😉
Kann eine JTextArea den Text mit "..." kürzen?
 
Zuletzt bearbeitet:
Pics or it didn't happen. 😉
Welchen Text soll das JLabel haben? Wo addierst du 50 drauf? Wie sieht es aus und wie soll es aussehen? Welchen LayoutManager nutzt du? KSKB?
Darauf habe ich extra verzichtet, da mir der Aufwand für das Problem zu hoch ist.

Stattdessen wollte ich das Problem an der Wurzel packen und eine allgemeine Lösung finden, welche mir evtl. später auch noch hilft. - Also ein JLabel, welches den Text kürzt, dabei "..." anhängt und in diesem Falle den vollen Text im Tooltip darstellt.

Die erste Frage hierbei wäre zunächst, ob es da nicht schon etwas gibt.
Die zweite Frage wäre, wie man am effektivsten mit den HTML-Tags umgeht, wenn man solch ein Steuerelement selbst implementieren will.
Sind diese Fragen geklärt, kann ich gern ein KSKB machen. 😉
 
Es kommt auch immer auf die Buchstaben an, welche verwendet werden.

10x i sind nicht so lang wie 10x W.

iiiiiiiiii
WWWWWWWWWW

Deshalb müsstest du die Buchstaben nach Breite filtern und dann die mögliche Anzahl an Buchstaben berechnen.
 
Also ein JLabel, welches den Text kürzt, dabei "..." anhängt und in diesem Falle den vollen Text im Tooltip darstellt.
Genau diese Kürzung erledigt das JLabel ja bereits. Nur wenn man HTML verwendet, geschieht dies anscheinend nicht. :bahnhof:

Hier ein Beispiel (ohne HTML) zum Überprüfen, ob ein Text gekürzt wurde:
Java:
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class LabelTest {
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				final JFrame frame = new JFrame();
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				final String text = "Dies ist ein relativ kurzer Satz.";
				final JLabel label = new JLabel(text);
				label.addComponentListener(new ComponentAdapter() {
					@Override
					public void componentResized(ComponentEvent e) {
						final String displayedText = displayedText(label);
						System.out.println(displayedText);
						if (!displayedText.equals(label.getText())) {
							label.setForeground(Color.RED);
							label.setToolTipText(text);
						} else {
							label.setToolTipText(null);
							label.setForeground(Color.BLACK);
						}
					}
				});
				frame.setContentPane(label);
				frame.setSize(100, 50);
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}
	
	public static String displayedText(final JLabel label) {
		final FontMetrics fontMetrics = label.getFontMetrics(label.getFont());
		final String text = label.getText();
		final Icon icon = label.getIcon();
		final int verticalAlignment = label.getVerticalAlignment();
		final int horizontalAlignment = label.getHorizontalAlignment();
		final int verticalTextPosition = label.getVerticalTextPosition();
		final int horizontalTextPosition = label.getHorizontalTextPosition();
		final Rectangle viewR = label.getBounds();
		final Rectangle iconR = label.getBounds();
		final Rectangle textR = label.getBounds();
		final int iconTextGap = label.getIconTextGap();
		return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition, horizontalTextPosition, viewR, iconR, textR, iconTextGap);
	}
}
 
Zuletzt bearbeitet:
Ich glaub du verstehst da was falsch: Die PreferredSize eines JLabels ist immer so groß, dass der Text gerade eben Platz hat. Man kann allerdings von außen einer Komponente eine neue fixe PreferredSize zuweisen, damit sie in die Maske passt. Meistens ist das aber nicht optimal, dann wird nämlich was abgeschnitten.
Ich glaube was du eher benötigst wäre eine MaximumSize und einen Layoutmanager, der diese berücksichtigt.
Btw, falls du's noch nicht weißt: Mit der Klasse FontMetrics kannst du sehr einfach die Größe eines Texts berechnen.
 
Genau diese Kürzung erledigt das JLabel ja bereits. Nur wenn man HTML verwendet, geschieht dies anscheinend nicht. :bahnhof:
Ah, danke für den Hinweis. Leider benötige ich die Unterstützung für HTML-Text, wenn man einen Teil des Textes fett darstellen will - die Komponente aber möglichst allgemein halten will...

Hier ein Beispiel (ohne HTML) zum Überprüfen, ob ein Text gekürzt wurde:
Danke für das Beispiel!
 
Ich glaub du verstehst da was falsch: Die PreferredSize eines JLabels ist immer so groß, dass der Text gerade eben Platz hat.
Ja, wenn ausreichend Platz zur Verfügung steht.

Man kann allerdings von außen einer Komponente eine neue fixe PreferredSize zuweisen, damit sie in die Maske passt.
Ich möchte es in einem CellRenderer verwenden - dort wird die Weite durch die Breite der ComboBox begrenzt.

Meistens ist das aber nicht optimal, dann wird nämlich was abgeschnitten.
Ja, aber unter bestimmten umständen kann der Text eben etwas länger ausfallen - in der Regel passt er aber in die ComboBox. Die ComboBox ist schon recht lang und sollte daher nicht vergrößert werden (und letzendlich wäre das aber auch keine Lösung, welche für jede Situation funktionierten wird).
Wenn schon abgeschnitten wird, dann sollte dies durch "..." gekennzeichnet und der Tooltip gesetzt werden. Das ist mein Ziel!
Es sollte aber eben auch mit teilweise fettem Text (also HTML-Text) funktionieren.

Ich glaube was du eher benötigst wäre eine MaximumSize und einen Layoutmanager, der diese berücksichtigt.
Nein, ich habe eine fest Label-Breite aber unterschiedlich langen Text, welcher mitunter auch mal über das Ende hinausschießen kann. Diese Situation sollte auch bei HTML-Text angemessen behandelt werden.

Btw, falls du's noch nicht weißt: Mit der Klasse FontMetrics kannst du sehr einfach die Größe eines Texts berechnen.
Ja, das habe ich bereits versucht. - Aber es hat in meiner ComboBox nicht so recht geklappt:
FontMetrics hat sich verrechnet (oder ich eben einen Fehler gemacht... :lol🙂

Da mir aber die Fummellei im ListCellRenderer zu groß wurde, wollte ich die Problematik einfach in eine eigene allgemeine Komponente auslagern. - Dadurch können wir uns hier besser auf das eigentliche Problem konzentrieren. Außerdem kann ich (und auch andere) die dabei entstehende Lösung besser für ähnliche Situationen anwenden.

Die zwei Fragen stehen also noch:
  • Gibts das schon was? (also Funktion das Standard-JLabels, nur eben auch für HTML-Text)
  • Falls nein, wie parse ich HTML am effektivsten?
 
Hmmm könnte dir das dann eventuell helfen: Changing preferred size of a html JLabel Morten Nobel's Blog (Runterscrollen zum Update mit dem resizer Beispiel)
Danke für den Link! Aber leider verrechnet sich auch diese Lösung - testet es bitte mal selbst:

Java:
import java.awt.Dimension;
import java.awt.event.ComponentEvent;

import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;


@SuppressWarnings("serial")
public class CutLabel extends JLabel {

	private static final JLabel resizer = new JLabel();

	/** Returns the preferred size to set a component at in order to render
	 *  an html string. You can specify the size of one dimension. */
	public static Dimension getPreferredSize(String html,
	                                         boolean width,
	                                         int prefSize) {

		resizer.setText(html);

		View view = (View) resizer.getClientProperty(BasicHTML.propertyKey);

		if (view != null) {
			view.setSize(width?prefSize:0, width?0:prefSize);
	
			float w = view.getPreferredSpan(View.X_AXIS);
			float h = view.getPreferredSpan(View.Y_AXIS);

			return new Dimension((int) Math.ceil(w), (int) Math.ceil(h));
		} else {
			return null;
		}
	}


	// Demo routine ///////////////////////////////////////////////////

	public static void main(String[] args) {
        try {
 			UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
        } catch(Exception ex) {
            ex.printStackTrace();
        }

		//final String html = "Dies ist ein etwas längerer Text, welcher unter Umständen gekürzt werden muss.";
		final String html = "<HTML>Dies ist ein etwas längerer <B>Text</B>, welcher unter Umständen <B>gekürzt</B> werden muss.</HTML>";
		final javax.swing.JFrame win = new javax.swing.JFrame();
		final CutLabel cutLbl = new CutLabel();
		
		cutLbl.setText(html);
		cutLbl.addComponentListener(new java.awt.event.ComponentListener() {
			@Override public void componentHidden(ComponentEvent arg0) {}
			@Override public void componentMoved(ComponentEvent arg0) {}
			@Override public void componentShown(ComponentEvent arg0) {}
			@Override
			public void componentResized(ComponentEvent arg0) {
				if (html.startsWith("<HTML>")) {
					Dimension dim = CutLabel.getPreferredSize(html, true, cutLbl.getWidth());
					cutLbl.setPreferredSize(dim);
					win.pack();
				}
			}
		});
		
		win.setLayout(new java.awt.BorderLayout());
		win.add(cutLbl);
		win.pack();
		win.setVisible(true);
		win.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
	}

}
Wenn man die Breite des Fensters verändert, passt sich dessen Höhen an den umgebrochenen Text an. Allerdings wird bei manchen Fenster-Breiten bereits die Höhe vergrößert, ohne das der Text auf eine weitere Zeile ausgedehnt wird.
Wenn man mit dieser Variante versucht die Länge des Textes für "..." zu ermitteln, wird der Fehler noch viel mehr auffallen. - Das Problem, welches ich mit meiner ComboBox hatte, sah ähnlich aus.
 
Stimmt, das geht so nicht ganz. Wenn man es aber ohne Hilfslabel macht, scheint es zu gehen:
Java:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
 
@SuppressWarnings("serial")
public class CutLabel extends JLabel {
    /** Returns the preferred size to set a component at in order to render
     *  an html string. You can specify the size of one dimension. */
    public Dimension getPreferredSize(boolean width, int prefSize) {
        final View view = (View) getClientProperty(BasicHTML.propertyKey);
        if (view != null) {
            view.setSize(width ? prefSize : 0, width ? 0 : prefSize);
    
            float w = view.getPreferredSpan(View.X_AXIS);
            float h = view.getPreferredSpan(View.Y_AXIS);
 
            return new Dimension((int) Math.ceil(w), (int) Math.ceil(h));
        } else {
            return null;
        }
    }
    
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        
        final String html = "<HTML>Dies ist<sup>1</sup> ein etwas längerer <B>Text</B>, welcher <U>unter</U> Umständen <I>gekürzt</I> werden muss.</HTML>";
        final JFrame win = new JFrame();
        final CutLabel cutLbl = new CutLabel();
        
        cutLbl.setText(html);
        cutLbl.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent arg0) {
                if (BasicHTML.isHTMLString(cutLbl.getText())) {
                    final Dimension dim = cutLbl.getPreferredSize(true, cutLbl.getWidth());
                    cutLbl.setPreferredSize(dim);
                    win.pack();
                }
            }
        });
        
        win.setLayout(new BorderLayout());
        win.add(cutLbl);
        win.pack();
        win.setVisible(true);
        win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}
 

Zurück
Oben