JTextField宽度根据最大输入,计算字符串宽度

JTextField width according to max input, calculating string width

我想要一个 TextField,它按宽度显示允许的最大值 输入字符。
我用Font.getStringBounds来计算最大长度的宽度。 但令我惊讶的是,示例中的结果宽度看起来像是省略了一个 完整角色!

缺少什么?

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;

public class InputWidthTextField extends JFrame {

  public InputWidthTextField() {
    setSize(250, 230);
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);
    setLayout(new FlowLayout(FlowLayout.CENTER, 60, 20));
    Font font= new Font(Font.MONOSPACED, Font.PLAIN, 12);
    int max= 4;
    JLabel lb= new JLabel("Enter up to "+max+" characters:");
    add(lb);
    MaxInputWidthTextField tf= new MaxInputWidthTextField(font, max);
    add(tf);
    setVisible(true);
  }


  public static void main(String... args) {
    EventQueue.invokeLater(InputWidthTextField::new);
  }


  class MaxInputWidthTextField extends JTextField {
    public MaxInputWidthTextField(Font font, int maxCount) {
      super();
      if (font==null)
        font= getFont();
      else
        setFont(font);
      FontMetrics fm= getFontMetrics(font);
      FontRenderContext frc= fm.getFontRenderContext();
      String buf= "8".repeat(maxCount);
      Rectangle2D rect= font.getStringBounds(buf, frc);
      Dimension dim= new Dimension((int)rect.getWidth(), (int)rect.getHeight());
      setPreferredSize(dim);
      System.out.println((int)rect.getWidth()+", "+(int)rect.getHeight());
      System.out.println(fm.stringWidth(buf));
      System.out.println(getGraphics());
    }    
  }

}

我什至不打算尝试找出您的代码中的所有错误,相反,我将演示您应该怎么做。

看看public JTextField​(int columns)

JTextField

public JTextField​(int columns)
Constructs a new empty TextField with the specified number of columns. A default model is created and the initial string is set to null.

Parameters: columns -
the number of columns to use to calculate the preferred width; if columns is set to zero, the preferred width will be whatever naturally results from the component implementation

因此,如果我们进行并排比较,这就是我们得到的结果(您的字段在底部)

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            add(new JTextField(8), gbc);
            add(new MaxInputWidthTextField(null, 8), gbc);
        }

    }

    class MaxInputWidthTextField extends JTextField {

        public MaxInputWidthTextField(Font font, int maxCount) {
            super();
            if (font == null) {
                font = getFont();
            } else {
                setFont(font);
            }
            FontMetrics fm = getFontMetrics(font);
            FontRenderContext frc = fm.getFontRenderContext();
            String buf = "8".repeat(maxCount);
            Rectangle2D rect = font.getStringBounds(buf, frc);
            Dimension dim = new Dimension((int) rect.getWidth(), (int) rect.getHeight());
            setPreferredSize(dim);
        }
    }
}

我推荐的是看一下已有的实现

JTextField#getPreferredSize 实现看起来像...

/**
 * Returns the column width.
 * The meaning of what a column is can be considered a fairly weak
 * notion for some fonts.  This method is used to define the width
 * of a column.  By default this is defined to be the width of the
 * character <em>m</em> for the font used.  This method can be
 * redefined to be some alternative amount
 *
 * @return the column width &gt;= 1
 */
protected int getColumnWidth() {
    if (columnWidth == 0) {
        FontMetrics metrics = getFontMetrics(getFont());
        columnWidth = metrics.charWidth('m');
    }
    return columnWidth;
}

/**
 * Returns the preferred size <code>Dimensions</code> needed for this
 * <code>TextField</code>.  If a non-zero number of columns has been
 * set, the width is set to the columns multiplied by
 * the column width.
 *
 * @return the dimension of this textfield
 */
public Dimension getPreferredSize() {
    Dimension size = super.getPreferredSize();
    if (columns != 0) {
        Insets insets = getInsets();
        size.width = columns * getColumnWidth() +
            insets.left + insets.right;
    }
    return size;
}

我不会“设置”首选大小,而是“考虑”覆盖 getPreferredSize 并将您的“自定义”工作流程注入

进一步实验...

所以,我做了一个非常快速的测试...

Font monoFont = new Font(Font.MONOSPACED, Font.PLAIN, 13);
JTextField field = new JTextField();
field.setFont(monoFont);
FontMetrics metrics = field.getFontMetrics(getFont());
for (int i = 32; i < 127; i++) {
    System.out.println(i + " " + ((char) i) + " = " + metrics.charWidth((char)i));
}

并且发现每个字符都是 8 点宽。默认字体使用的字符 m 是(在我的测试中)12 点宽。

所以,这让我开始思考,真正的问题不是 getPreferredSize 错误,而是我们实际上需要“填充”getColumnWidth 的结果。 ..

public class MaxInputWidthTextField extends JTextField {

    public MaxInputWidthTextField(Font font, int maxCount) {
        super(maxCount);
        setFont(font);
    }

    @Override
    protected int getColumnWidth() {
        return super.getColumnWidth() + 1;
    }
}

可以生成类似...

在文本字段的末尾添加一点白色 space 就足够了,你不需要跳过很多希望让它工作。

可运行示例

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;

            Font monoFont = new Font(Font.MONOSPACED, Font.PLAIN, 13);

            add(new JTextField(8), gbc);
            add(new MaxInputWidthTextField(monoFont, 8), gbc);
        }

    }

    public class MaxInputWidthTextField extends JTextField {

        public MaxInputWidthTextField(Font font, int maxCount) {
            super(maxCount);
            setFont(font);
        }

        @Override
        protected int getColumnWidth() {
            return super.getColumnWidth() + 1;
        }
    }
}

@Camickr
罗布,你昨天似乎很严厉。 ;-)
但可以肯定的是,不理解也站在我这边。因为当我告诉您在等宽字体中“W”和“i”具有相同的宽度时,这对您来说肯定不是什么新鲜事。所以我不明白你为什么要我申请 MRE,或者为什么只有“WWWW”不适合你。我只能想象,你没有使用等宽字体, 但这是预先计算最大可能宽度的必要条件,给定最大字符数,对于用户将输入的任何字符串。
我希望,由于分辨率或比例差异,我们不会在屏幕上看到不同的东西。所以我发送了一张它在我的网站上的样子的照片。每当我在其中一个文本字段中输入最后一个字符时,整个字符串都会向左移动,因此第一个字符会被部分隐藏。因此,在我的例子中,零总是被削减。您可能认为这是可以接受的,但我更希望每个字符都完全可见。

import java.awt.*;
import javax.swing.*;

public class FieldWidthTest extends JFrame {

  public FieldWidthTest() {
    setSize(250, 230);
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);
    setLayout(new GridLayout(4, 1));
    Font font= new Font(Font.MONOSPACED, Font.PLAIN, 12);
    int max= 4;
    for (int i=0; i<4; i++) {
      JPanel p= new JPanel();
      JTextField tf= new JTextField(max++);
      tf.setFont(font);
      p.add(tf);
      add(p);
    }
    setVisible(true);
  }


  public static void main(String... args) {
    EventQueue.invokeLater(FieldWidthTest::new);
  }

}

@MadProgrammer
正如您所建议的那样,覆盖 getPreferredSize() 效果很好;但是使用 setPreferredSize() 也完成了工作并保存了覆盖。
如果参数为空,则不创建和设置等宽字体是错误的。然而,解决方案是将维度的宽度加 2,这适用于所有字体大小。 Camickr 会称其为“使用魔法数字”,但我不知道从哪里得到这个数字。
感谢大家。

  class MaxInputWidthTextField extends JTextField {
    public MaxInputWidthTextField(Font monoFont, int maxCount) {
      super();
      if (monoFont==null) monoFont= new Font(Font.MONOSPACED, Font.PLAIN, 13);
      setFont(monoFont);
      FontMetrics fm= getFontMetrics(monoFont);
      FontRenderContext frc= fm.getFontRenderContext();
      String buf= "8".repeat(maxCount);
      Rectangle2D rect= monoFont.getStringBounds(buf, frc);
//      Insets insets= getMargin(); // Parameters are all 0.
      Insets insets= getInsets();
      int iWidth= insets.left + (int)rect.getWidth() + insets.right +2;
      int iHeight= insets.top + (int)rect.getHeight() + insets.bottom;
      Dimension dim= new Dimension(iWidth, iHeight);
      setPreferredSize(dim);
    }
  }