【问题标题】:Getting the bounds of the text inside a jlabel获取 jlabel 中文本的边界
【发布时间】:2018-05-05 20:51:18
【问题描述】:

我想从 JLabel 中的文本绘制一个箭头,指向 JLabel 之外的某个点。要在适当的位置开始箭头,我需要 JLabel 中实际文本的边界。我在下面的回答将展示如何获得这些界限。

【问题讨论】:

  • camickr 和 MadProgrammer 都提供了基本相同的出色答案。我已经“接受”了我自己的答案,因为它更短而且是我的。提供图片的疯狂程序员必须加分。

标签: java text accessibility jlabel bounds


【解决方案1】:

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;
}

基本上这将提供“视图”边界(基本上是可以绘制标签的区域)、“文本”边界和“图标”边界,我只是将它们包装在一个易于使用的类中

那么为什么要使用这种方法呢?

  • 这实际上与外观委托用于布局标签的工作流程相同
  • 它不需要您创建自定义类,这意味着它可以应用于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();
        }

    }

}

其他解决方案的潜在优势是每次调用不会创建三个新的 Rectangle 对象。如果在 MouseMoved 事件侦听器中调用 getTextBounds,这可能是一个问题。以一定的复杂性为代价,最终的 Rectangle 可以与 JLabel 的宽度和高度一起缓存。

Rectangles 只需要创建一次,它们被传递到 API 并直接应用它们的值,因此您不必每次都创建新对象。

【讨论】:

    【解决方案2】:

    您可以使用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();
                }
            });
        }
    }
    

    此示例在标签中的图标上方绘制额外的线条。

    【讨论】:

      【解决方案3】:

      要获得 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 的宽度和高度一起缓存。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-21
        • 2017-04-09
        • 1970-01-01
        相关资源
        最近更新 更多