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 >= 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 Swing 中控制 JTextField 的宽度?