Reputation: 412
I want to draw an arrow from the text in a JLabel to a point outside the JLabel. To start the arrow at an appropriate place, I need the bounds of the actual text inside the JLabel. My answer below will show how to get those bounds.
Upvotes: 1
Views: 1554
Reputation: 347194
The Swing API already provides, if rather verbose, API to perform this operation - SwingUtilities#layoutCompoundLabel
protected TextBounds calculateTextBounds(JLabel label) {
Rectangle paintIconR = new Rectangle();
Rectangle paintTextR = new Rectangle();
Dimension size = label.getSize();
Insets insets = label.getInsets(null);
String text = label.getText();
Icon icon = (label.isEnabled()) ? label.getIcon()
: label.getDisabledIcon();
Rectangle paintViewR = new Rectangle();
paintViewR.x = insets.left;
paintViewR.y = insets.top;
paintViewR.width = size.width - (insets.left + insets.right);
paintViewR.height = size.height - (insets.top + insets.bottom);
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
String result = SwingUtilities.layoutCompoundLabel(
(JComponent) label,
label.getFontMetrics(label.getFont()),
text,
icon,
label.getVerticalAlignment(),
label.getHorizontalAlignment(),
label.getVerticalTextPosition(),
label.getHorizontalTextPosition(),
paintViewR,
paintIconR,
paintTextR,
label.getIconTextGap());
TextBounds bounds = new TextBounds(paintViewR, paintTextR, paintIconR);
return bounds;
}
Basically this will provide the "view" bounds (basically the area in which the label can be painted), the "text" bounds and the "icon" bounds, I just wrapped them in an easy to use class
So why would you use this approach?
JLabel
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TextBounds {
private Rectangle paintBounds;
private Rectangle textBounds;
private Rectangle iconBounds;
public TextBounds(Rectangle paintBounds, Rectangle textBounds, Rectangle iconBounds) {
this.paintBounds = paintBounds;
this.textBounds = textBounds;
this.iconBounds = iconBounds;
}
public Rectangle getPaintBounds() {
return paintBounds;
}
public Rectangle getTextBounds() {
return textBounds;
}
public Rectangle getIconBounds() {
return iconBounds;
}
}
public class TestPane extends JPanel {
private JLabel label = new JLabel("Hello");
public TestPane() {
setLayout(new BorderLayout());
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
add(label);
}
protected TextBounds calculateTextBounds(JLabel label) {
Rectangle paintIconR = new Rectangle();
Rectangle paintTextR = new Rectangle();
Dimension size = label.getSize();
Insets insets = label.getInsets(null);
String text = label.getText();
Icon icon = (label.isEnabled()) ? label.getIcon()
: label.getDisabledIcon();
Rectangle paintViewR = new Rectangle();
paintViewR.x = insets.left;
paintViewR.y = insets.top;
paintViewR.width = size.width - (insets.left + insets.right);
paintViewR.height = size.height - (insets.top + insets.bottom);
paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
String result = SwingUtilities.layoutCompoundLabel(
(JComponent) label,
label.getFontMetrics(label.getFont()),
text,
icon,
label.getVerticalAlignment(),
label.getHorizontalAlignment(),
label.getVerticalTextPosition(),
label.getHorizontalTextPosition(),
paintViewR,
paintIconR,
paintTextR,
label.getIconTextGap());
TextBounds bounds = new TextBounds(paintViewR, paintTextR, paintIconR);
System.out.println(result);
System.out.println("view " + paintViewR);
System.out.println("paint " + paintIconR);
System.out.println("text " + paintTextR);
return bounds;
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
TextBounds bounds = calculateTextBounds(label);
g2d.setColor(Color.RED);
g2d.draw(bounds.getPaintBounds());
g2d.setColor(Color.GREEN);
g2d.draw(bounds.getTextBounds());
g2d.setColor(Color.BLUE);
g2d.draw(bounds.getIconBounds());
g2d.dispose();
}
}
}
Other solutions have the potential advantage of not creating three new Rectangle objects per call. This could be a problem if getTextBounds is called in a MouseMoved event listener. At some cost in complexity, the final Rectangle could be cached along with the JLabel width and height.
The Rectangle
s only need to be created once, they are passed into the API and the their values applied directly, so you're not creating new objects each time.
You also don't need to make use of a specialised component and can make use of the workflow on an pre-existing label
Upvotes: 1
Reputation: 412
To get the bounds of text in a JLabel, we need the accessible context, a protected field. So we extend JLabel. The accessible context only knows about the text if it is HTML, so I prepend "<html>" in the constructor; a more general version would first check to see if the text already begins with that string.
class FieldLabel extends JLabel {
FieldLabel(String text) {
super("<html>"+text);
}
Rectangle getTextBounds() {
JLabel.AccessibleJLabel acclab
= (JLabel.AccessibleJLabel) getAccessibleContext();
if (acclab.getCharCount() <= 0)
return null;
Rectangle r0 = acclab.getCharacterBounds(0);
Rectangle rn = acclab
.getCharacterBounds(acclab.getCharCount()-1);
return new Rectangle(r0.x, r0.y,
rn.x+rn.width-r0.x, Math.max(r0.height, rn.height));
}
}
Other solutions have the potential advantage of not creating three new Rectangle objects per call. This could be a problem if getTextBounds is called in a MouseMoved event listener. At some cost in complexity, the final Rectangle could be cached along with the JLabel width and height.
Upvotes: 0
Reputation: 324108
You can use the SwingUtilities.layoutCompoundLabel
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class LabelLayout extends JLabel
{
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics grid = g.create();
grid.setColor( Color.ORANGE );
Rectangle viewR = new Rectangle();
viewR.width = getSize().width;
viewR.height = getSize().height;
Rectangle iconR = new Rectangle();
Rectangle textR = new Rectangle();
String clippedText = SwingUtilities.layoutCompoundLabel
(
this,
grid.getFontMetrics(),
getText(),
getIcon(),
getVerticalAlignment(),
getHorizontalAlignment(),
getVerticalTextPosition(),
getHorizontalTextPosition(),
viewR,
iconR,
textR,
getIconTextGap()
);
int gridSize = 10;
int start = iconR.x;
int end = iconR.x + iconR.width;
System.out.println("Text bounds: " + textR );
System.out.println("Icon bounds: " + iconR );
for (int i = start; i < end; i += gridSize)
{
grid.drawLine(i, iconR.y, i, iconR.y + iconR.height);
}
grid.dispose();
}
private static void createAndShowGUI()
{
LabelLayout label = new LabelLayout();
label.setBorder( new LineBorder(Color.RED) );
label.setText( "Some Text" );
label.setIcon( new ImageIcon( "DukeWaveRed.gif" ) );
label.setVerticalAlignment( JLabel.CENTER );
label.setHorizontalAlignment( JLabel.CENTER );
// label.setVerticalTextPosition( JLabel.BOTTOM );
label.setVerticalTextPosition( JLabel.TOP );
label.setHorizontalTextPosition( JLabel.CENTER );
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( label );
frame.setLocationByPlatform( true );
frame.pack();
frame.setSize(300, 200);
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
This example paints extra lines above the icon in the label.
Upvotes: 1