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

Posted

技术标签:

【中文标题】JTextField 根据最大​​输入宽度,计算字符串宽度【英文标题】:JTextField width according to max input, calculating string width 【发布时间】:2021-12-05 02:07:18 【问题描述】:

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

使用 FontMetrics.stringWidth 提供相同的宽度值。 仅使用 JTextField(int columns) 构造函数创建 textField 结果更好,但场仍然太小。

缺少什么?

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)有什么问题? @MadProgrammer 正如我所写:它给出了更好的结果,但并不令人满意。文本字段太小,无法在不滚动的情况下完全容纳最大字符。 @Jörg 仅使用 JTextField(int columns) 构造函数创建 textField 会得到更好的结果,但该字段仍然太小​​。 - 首选大小应该足够大保存“W”字符。必须承认,如果我输入 4 个“W”字符,它会被截断。但是对于所有其他组合,它对我来说都很好。因为我怀疑你是否会输入 4 个“W”字符,所以这个构造函数应该可以正常工作。使用用于测试此构造函数的数据发布您的minimal reproducible example,并且需要滚动 4 个字符。 @Jörg 真的,因为当我将您的尝试与默认实现进行比较时,我更喜欢默认实现 @Jörg 我认为文档说得很好“对于某些字体来说,列的含义可以被认为是一个相当弱的概念” - 所以,在非常最好,这是一个“猜测”。虽然固定宽度的字体通常会提供最好的效果,但可变宽度的字体会让你感到困惑。您还需要考虑 L&F 也将insets 应用于文本字段。查看JTextField#getPreferredSize 的默认实现(发布在下面),这将使您了解 Swing 团队如何尝试解决此问题 【参考方案1】:

我什至不会尝试找出你的代码有什么问题,相反,我将演示你应该做什么。

看看public JTextField​(int columns)

JTextField

public JTextField​(int columns)
构造一个新的空 具有指定列数的 TextField。默认模型是 创建并且初始字符串设置为空。 参数:列 - 用于计算首选宽度的列数;如果 列设置为零,首选宽度将是任何自然 组件实现的结果

所以,如果我们进行并排比较,这就是我们得到的(你的字段在底部)

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;
    

它可以生成类似...的东西

在文本字段的尾端添加一点空白就足够了,您不需要跳过很多希望来让它发挥作用。

可运行示例

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;
        
    

【讨论】:

感谢您的代码和解释。正如我所做的那样,使用 maxCount 参数使得在我看来必须使用等宽字体。 - 我明天会检查你的 getPreferredSize 建议。现在我无法睁开眼睛。【参考方案2】:

@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);
  



【讨论】:

【参考方案3】:

@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);
    
  

【讨论】:

对不起,MadProgrammer,我不想让你难过。相反,我一直很欣赏您的宝贵建议。但是您自己写了“注入您的'自定义'工作流程”。我非常清楚不要遵循挥杆工程师经过长期测试和确立的方式。但是等宽字体的特殊情况是不那么复杂的情况。我写道,我首先遵循了您的建议并覆盖了“getPreferredSize”,但我仍然必须插入我的“幻数”才能获得所需的结果。你肯定有充分的理由说明为什么覆盖getPreferredSize 比 --> tbc 我只是强调setPreferredSize的缺点;) setPreferredSize。如果你愿意多花两分钟,请告诉我。 就像我说的,任何人都可以过来打电话给setPreferredSize,然后毁掉你所有的努力——一般来说,这是“推荐”(我用losly这个词)来避免使用@987654328 @,您已经从 JTextField 扩展的尺寸,因此您可以限制它的水平尺寸,为什么不覆盖最适合这项工作的方法 - 这只是我的意见,但我看到人们通过调用 @ 破坏了完美工作的代码987654330@:P 还有一种“观点”认为setPreferredSize 不应该是public,实际上是一个设计缺陷;)

以上是关于JTextField 根据最大​​输入宽度,计算字符串宽度的主要内容,如果未能解决你的问题,请参考以下文章

用Java做一个简单的计算器

如何在 Java Swing 中控制 JTextField 的宽度?

以方式设置 JTextField 宽度以包装给定的文本

java gridlayout 如何实现30行2列如图示的排列?

如何根据最大选项宽度设置反应选择宽度?

如何从其他面板从 JTextField 获取输入