
在 java swing 应用开发中,jformattedtextfield 结合 numberformatter 是实现格式化输入(如货币、日期等)的常用方式。然而,当我们需要一个带有固定前缀(例如货币符号 "gs. ")的 jformattedtextfield 时,可能会遇到一个棘手的问题:当用户首次输入一个数字时,jformattedtextfield 会自动添加前缀,但光标位置却会意外地跳到前缀之前,而不是停留在用户输入数字的后面。这导致用户需要手动调整光标位置才能继续输入,严重影响了用户体验。
例如,当我们配置 NumberFormatter 使其在空值或仅有前缀时自动添加 "Gs. ",用户输入 '1' 后,文本框显示 "Gs. 1",但光标却可能位于 'G' 之前,而不是 '1' 之后。
开发者可能尝试在 NumberFormatter 的 install 方法中设置光标位置,期望每次组件获得焦点时都能将光标移到文本末尾,如下所示:
@Override
public void install(JFormattedTextField pField) {
super.install(pField);
pField.setCaretPosition(pField.getDocument().getLength());
}然而,这种做法并不能解决问题。根据 InternationalFormatter 的 javadoc,install 方法主要在 JFormattedTextField 对象创建时被调用一次,其主要目的是允许子类安装额外的监听器。它并不会在每次组件获得焦点或文本内容发生变化时都执行。因此,当文本内容因用户输入而改变时,install 方法中的光标设置逻辑并不会被触发,光标位置依然会出错。
要解决光标位置异常的问题,我们需要一种机制来实时监听 JFormattedTextField 中文本内容的变化,并在内容更新后立即调整光标位置。DocumentListener 正是为此目的而设计的。
DocumentListener 接口提供了三个方法,用于响应 Document(JFormattedTextField 的底层文本模型)的修改事件:
通过在 JFormattedTextField 的 Document 上注册 DocumentListener,我们可以在用户输入或删除字符时,准确地捕获到文本内容的变化。
当 DocumentListener 接收到文本更新事件时,我们希望将光标设置到文本的末尾。然而,直接在 insertUpdate 或 removeUpdate 方法中调用 pField.setCaretPosition(pField.getDocument().getLength()) 可能仍然存在问题。这是因为 JFormattedTextField 内部可能还有其他 DocumentListener 或 Swing 内部的逻辑,它们在处理文本更新时也可能修改光标位置。如果我们的光标设置操作过早执行,可能会被后续的 Swing 内部操作覆盖。
为了确保我们的光标设置操作在所有其他相关的文本处理完成后执行,我们应该使用 EventQueue.invokeLater()。EventQueue.invokeLater() 会将一个 Runnable 任务提交到 Swing 的事件调度线程(EDT)队列的末尾。这意味着,当 invokeLater() 中的代码执行时,所有当前正在处理的事件(包括其他 DocumentListener 的回调)都已完成,从而保证了光标位置设置的最终性和准确性。
下面是经过优化的 formato() 方法,它通过在 install 方法中添加 DocumentListener 并结合 EventQueue.invokeLater 来解决光标定位问题:
import javax.swing.JFormattedTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.NumberFormatter;
import java.awt.EventQueue;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
public class CurrencyFormattedTextField {
// 示例用法
public JFormattedTextField createCurrencyTextField() {
return new JFormattedTextField(formato());
}
private NumberFormatter formato() {
// 定义货币格式,例如 "Gs. 1,234,567"
DecimalFormat myFormatter = new DecimalFormat("'Gs. '###,##0;'Gs. '###,##0");
NumberFormatter numberFormatter = new NumberFormatter(myFormatter) {
// 重写 install 方法,添加 DocumentListener 以处理光标定位
@Override
public void install(JFormattedTextField pField) {
super.install(pField);
// 添加 DocumentListener 监听文本变化
pField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
// 使用 invokeLater 确保光标设置在所有其他文本处理完成后执行
EventQueue.invokeLater(() -> {
// 确保 JFormattedTextField 处于可编辑状态且不为空
if (pField.isEditable() && pField.getDocument().getLength() > 0) {
pField.setCaretPosition(pField.getDocument().getLength());
}
});
}
@Override
public void removeUpdate(DocumentEvent e) {
// 使用 invokeLater 确保光标设置在所有其他文本处理完成后执行
EventQueue.invokeLater(() -> {
// 确保 JFormattedTextField 处于可编辑状态且不为空
if (pField.isEditable() && pField.getDocument().getLength() > 0) {
pField.setCaretPosition(pField.getDocument().getLength());
} else if (pField.getDocument().getLength() == 0) {
// 如果文本完全清空,光标位置为0
pField.setCaretPosition(0);
}
});
}
@Override
public void changedUpdate(DocumentEvent e) {
// 属性或样式改变,通常不需要处理光标
}
});
}
// 将数值转换为字符串(显示在文本框中)
@Override
public String valueToString(Object value) throws ParseException {
String result = super.valueToString(value);
// 阻止负数显示,如果结果以 "-" 开头,则移除
if (result != null && result.startsWith("-")) {
result = result.replaceFirst("-", "");
}
// 如果值为 null,则返回空字符串
if (value == null) {
return "";
}
return result;
}
// 将字符串(用户输入)转换为数值
@Override
public Object stringToValue(String text) throws ParseException {
// 如果文本为空或仅包含前缀,则返回 null
if (text == null || text.length() == 0 || text.equals("Gs. ")) {
return null;
}
// 移除负号(如果存在),确保不处理负数
text = text.replaceFirst("-", "");
// 如果文本没有前缀,则添加前缀
if (!text.startsWith("Gs. ")) {
text = "Gs. " + text;
}
return super.stringToValue(text);
}
};
// 关键设置:不允许无效编辑,即只允许输入符合格式的字符
numberFormatter.setAllowsInvalid(false);
// 设置最大允许值
numberFormatter.setMaximum(new BigDecimal("999999999999"));
// 每次有效编辑(按键)后立即提交值,而不是在失去焦点时
numberFormatter.setCommitsOnValidEdit(true);
return numberFormatter;
}
}代码解释:
通过在 NumberFormatter 的 install 方法中巧妙地引入 DocumentListener,并结合 EventQueue.invokeLater 机制,我们成功解决了 JFormattedTextField 在自定义货币格式化器中光标位置异常的问题。这种方法不仅确保了光标始终定位到正确位置,提升了用户输入体验,也展示了在 Swing 中处理复杂 UI 行为时,理解事件模型和合理利用并发工具的重要性。
以上就是JFormattedTextField 自定义格式化器中光标位置异常的解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号