获取 jlabel 中文本的边界
Getting the bounds of the text inside a jlabel
我想绘制一个从 JLabel 中的文本到 JLabel 外的点的箭头。要在适当的位置开始箭头,我需要 JLabel 中实际文本的边界。我在下面的回答将展示如何获得这些界限。
要获取 JLabel 中文本的边界,我们需要可访问的上下文,一个受保护的字段。所以我们扩展了 JLabel。可访问的上下文只知道 HTML 的文本,所以我在构造函数中添加了“”;更通用的版本将首先检查文本是否已经以该字符串开头。
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));
}
}
其他解决方案具有潜在 优势,即不会在每次调用时创建三个新的 Rectangle 对象。如果在 MouseMoved 事件侦听器中调用 getTextBounds,这可能会成为问题。以复杂性为代价,最终的 Rectangle 可以与 JLabel 的宽度和高度一起缓存。
Swing API 已经提供了 API 来执行此操作 - 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;
}
基本上这将提供"view"边界(基本上是可以绘制标签的区域),"text"边界和"icon"边界,我只是将它们包裹在易于使用 class
那么你为什么要使用这种方法?
- 这实际上与外观委托用于布局标签的工作流程相同
- 不需要您创建自定义 class,这意味着它可以应用于
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.
Rectangle
只需要创建一次,它们被传递到 API 并直接应用它们的值,因此您不会每次都创建新对象。
您可以使用 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();
}
});
}
}
此示例在标签中的图标上方绘制了额外的线条。
我想绘制一个从 JLabel 中的文本到 JLabel 外的点的箭头。要在适当的位置开始箭头,我需要 JLabel 中实际文本的边界。我在下面的回答将展示如何获得这些界限。
要获取 JLabel 中文本的边界,我们需要可访问的上下文,一个受保护的字段。所以我们扩展了 JLabel。可访问的上下文只知道 HTML 的文本,所以我在构造函数中添加了“”;更通用的版本将首先检查文本是否已经以该字符串开头。
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));
}
}
其他解决方案具有潜在 优势,即不会在每次调用时创建三个新的 Rectangle 对象。如果在 MouseMoved 事件侦听器中调用 getTextBounds,这可能会成为问题。以复杂性为代价,最终的 Rectangle 可以与 JLabel 的宽度和高度一起缓存。
Swing API 已经提供了 API 来执行此操作 - 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;
}
基本上这将提供"view"边界(基本上是可以绘制标签的区域),"text"边界和"icon"边界,我只是将它们包裹在易于使用 class
那么你为什么要使用这种方法?
- 这实际上与外观委托用于布局标签的工作流程相同
- 不需要您创建自定义 class,这意味着它可以应用于
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.
Rectangle
只需要创建一次,它们被传递到 API 并直接应用它们的值,因此您不会每次都创建新对象。
您可以使用 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();
}
});
}
}
此示例在标签中的图标上方绘制了额外的线条。