【问题标题】:JTextField width according to max input, calculating string widthJTextField 根据最大​​输入宽度,计算字符串宽度
【发布时间】: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 团队如何尝试解决此问题

标签: java swing jtextfield


【解决方案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,实际上是一个设计缺陷;)
    猜你喜欢
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 1970-01-01
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多