合理输入问题 在有些情况下,程序不希望用户在输入框内随意地输入,例如,程序需要用户输入一个有效的时间,或者需要用户输入一个有效的物品价格,如果用户输入不合理,程序应该阻止用户输入 。
使用事件监听实现 对于这种需求,通常的做法是为该文本框添加失去焦点的监听器,再添加回车按键的监听器,当该文本框失去焦点时,或者该用户在该文本框内按回车键时,就检测用户输入是否合法。这种做法基本可以解决该问题,但编程比较烦琐!
使用JFormattedTextField Swing
提供的JFormattedTextField
可以更优雅地解决该问题。
JFormattedTextField和普通文本行的区别 使用JFormattedTextField
与使用普通文本行有一个区别:
它需要指定一个文本格式,只有当用户的输入满足该格式时,JFormattedTextField
才会接收用户输入 。
JFormattedTextField格式分类 JFormattedTextField
可以使用如下两种类型的格式。
JFormattedTextField.AbstractFormatter
:该内部类有一个子类DefaultFormatter
,而DefaultFormatter
又有一个非常实用的MaskFormatter
子类,允许程序以掩码的形式指定文本格式
Format
:主要由DateFormat
和NumberFormat
两个格式器组成,这两个格式器可以指定JFormattedTextField
所能接收的格式字符串。
JFormattedTextField方法 创建JFormattedTextField
对象时可以传入上面任意一个格式器:
构造器
描述
JFormattedTextField(Format format)
Creates a JFormattedTextField.
成功地创建了JFormattedTextField
对象之后,JFormattedTextField
对象的用法和普通TextField
的用法基本相似,一样可以调用setColumns()
来设置该文本框的宽度,调用setFont()
来设置该文本框内的字体等。
除此之外,JFormattedTextField
还包含如下三个特殊方法
JFormattedTextField
类的方法
描述
Object getValue()
获取该格式化文本框里的值
void setValue(Object value)
设置该格式化文本框的初始值
void setFocusLostBehavior(int behavior)
设置该格式化文本框失去焦点时的行为。
getValue方法 上面三个方法中获取格式化文本框内容的getValue
方法返回Object
类型,而不是返回String
类型;
setValue方法 与之对应的是,设置格式化文本框初始值的setValue
方法需要传入Object
类型参数,而不是String
类型参数。 这都是因为格式化文本框会将文本框内容转换成指定格式对应的对象,而不再是普通字符串。
setFocusLostBehavior方法 setFocusLostBehavior方法可以接收如下4个值:
setFocusLostBehavior方法参数值
描述
JFormattedTextField.COMMIT
如果用户输入的内容满足格式器的要求,则该格式化文本框显示的文本变成用户输入的内容,调用getValue()
方法返回的是该文本框内显示的内容;如果用户输入的内容不满足格式器的要求,则该格式化文本框显示的依然是用户输入的内容,但调用getValue()
方法返回的不是该文本框内显示的内容,而是上一个满足要求的值
JFormattedTextField.COMMIT_OR_REVERT
这是默认值 。如果用户输入的内容满足格式器的要求,则该格式化文本框显示的文本、getValue()
方法返回的都是用户输入的内容;如果用户输入的内容不满足格式器的要求,则该格式化文本框显示的文本、getValue()
方法返回的都是上一个满足要求的值。
JFormattedTextField.REVERT
不管用户输入的内容是否满足格式器的要求,该格式化文本框都显示用户输入的内容,getValue()
方法返回的都是上一个满足要求的值。
JFormattedTextField.PERSIST
不管用户输入的内容是否满足格式器的要求,该格式化文本框显示的内容、getValue()
方法返回的都是上一个满足要求的值。在这种情况下,不管用户输入什么内容对该文本框都没有任何影响。
DefaultFormatter
是一个功能非常强大的格式器,它可以格式化任何类的实例,只要该类包含带一个字符串参数的构造器,并提供对应的toString()
方法(该方法的返回值就是传入给构造器字符串参数的值)即可
例如,URL
类包含一个URL(String spec)
构造器,且URL
对象的toString()
方法恰好返回刚刚传入的spec
参数,因此可以使用DefaultFormatter
来格式化URL
对象。当格式化文本框失去焦点时,该格式器就会调用带一个字符串参数的构造器来创建新的对象,如果构造器抛出了异常,即表明用户输入无效。
DefaultFormatter
格式器默认采用改写方式来处理用户输入,即当用户在格式化文本框内输入时,每输入一个字符就会替换文本框内原来的一个字符。如釆想关闭这种改写方式,采用插入方式,则可通过调用它的setOverwriteMode(false)
方法来实现。
MaskFormatter
格式器的功能有点类似于正则表达式,它要求用户在格式化文本框内输入的内容必须匹配一定的掩码格式。例如,若要匹配广州地区的电话号码,则可采用020-########
的格式,这个掩码字符串和正则表达式有一定的区别,因为该掩码字符串只支持如下通配符。
#
:代表任何有效数字
'
:转义字符,用于转义具有特殊格式的字符。例如,若想匹配#
,则应该写成'#
U
:任何字符,将所有小写字母映射为大写。
L
:任何字符,将所有大写字母映射为小写。
A
:任何字符或数字。
?
:任何字符。
*
:可以匹配任何内容。
H
:任何十六进制字符(0~9
、a~f
或A~F
)。
值得指出的是,格式化文本框内的字符串总是和掩码具有相同的格式,连长度也完全相同。如果用户删除了格式化文本框内的字符,这些被删除的字符将由占位符替代。默认使用空格作为占位符,当然也可以调用MaskFormatter
的setPlaceholderCharacter()
方法来设置该格式器的占位符。例如如下代码:
1 formatter.setPlaceholdercharacter(' ' );
程序 下面程序示范了关于JFormattedTextField
的简单用法。
上面程序添加了6个格式化文本框,其中两个是基于NumberFormat
生成的整数格式器、货币格式器,两个是基于DateFormat
生成的日期格式器,一个是使用DefaultFormatter
创建的URL
格式器,最后一个是使用MaskFormatter
创建的掩码格式器,程序中的粗体字代码是创建这些格式器的关键代码。
除此之外,程序还添加了4个单选按钮,用于控制这些格式化文本框失去焦点后的行为。运行上面程序,并选中“COMMIT
”行为,将看到如图12.59所示的界面
从图12.59中可以看出,虽然用户向格式化文本框内输入的内容与该文本框所要求的格式不符,但该文本框依然显示了用户输入的内容,只是后面显示该文本框的getValue()
方法返回值时看到的依然是100,即上一个符合格式的值。
大部分时候,使用基于Format
的格式器,DefaultFormatter
和MaskFormatter
已经能满足绝大部分要求;但对于一些特殊的要求,则可以采用扩展DefaultFormatter
的方式来定义自己的格式器。定义自己的格式器通常需要重写DefaultFormatter
的如下两个方法
方法
描述
Object stringToValue(String string)
根据格式化文本框内的字符串来创建符合指定格式的对象。
String valueToString(Object value)
将符合格式的对象转换成文本框中显示的字符串
例如,若需要创建一个只能接收IP
地址的格式化文本框,则可以创建一个自定义的格式化文本框,因为IP地址是由4个0~255之间的整数表示的,所以程序采用长度为4的byte
数组来保存IP
地址,程序可以采用如下方法将用户输入的字符串转换成byte
数组。
如何保证用户输入的有效性 除此之外,Swing
还提供了如下两种机制来保证用户输入的有效性
输入过滤:输入过滤机制允许程序拦截用户的插入、替换、删除等操作,并改变用户所做的修改。
输入校验:输入验证机制允许用户离开输入组件时,验证机制自动触发——如果用户输入不符合要求,校验器强制用户重新输入。
输入过滤器 输入过滤器需要继承DocumentFilter
类,程序可以重写该类的如下三个方法来拦截用户的插入、删除和替换等操作。
方法
描述
void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr)
该方法会拦截用户向文档中插入字符串的操作。
void remove(DocumentFilter.FilterBypass fb, int offset, int length)
该方法会拦截用户从文档中删除字符串的操作。
void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
该方法会拦截用户替换文档中字符串的操作。
输入校验器 为了创建自己的输入校验器,可以通过扩展InputVerifier
类来实现。实际上,InputVerifier
输入校验器可以绑定到任何输入组件,InputVerifier
类里包含了一个verify(JComponent component)
方法,当用户在该输入组件内输入完成,且该组件失去焦点时,该方法被调用,如果该方法返回false
,即表明用户输入无效,该输入组件将自动得到焦点。也就是说,如果某个输入组件绑定了InputVerifier
,则用户必须为该组件输入有效内容,否则用户无法离开该组件 。
有一种情况例外,如果输入焦点离开了带InputVerifier
输入校验器的组件后,立即单击某个按钮,则该按钮的事件监听器将会在焦点重新回到原组件之前被触发。
程序 为格式化文本框 添加输入过滤器 输入校验器 下面程序示范了如何为格式化文本框添加输入过滤器、输入校验器,程序还自定义了一个IP
地址格式器,该IP
地址格式器扩展了DefaultFormatter
格式器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 import java.util.*;import java.text.*;import java.net.*;import java.awt.*;import javax.swing.*;import javax.swing.border.*;import javax.swing.text.*;public class JFormattedTextFieldTest { private JFrame mainWin = new JFrame ("测试格式化文本框" ); private JButton okButton = new JButton ("确定" ); private JPanel mainPanel = new JPanel (); JFormattedTextField[] fields = new JFormattedTextField [6 ]; String[] behaviorLabels = new String [] { "COMMIT" , "COMMIT_OR_REVERT" , "PERSIST" , "REVERT" }; int [] behaviors = new int [] { JFormattedTextField.COMMIT, JFormattedTextField.COMMIT_OR_REVERT, JFormattedTextField.PERSIST, JFormattedTextField.REVERT }; ButtonGroup bg = new ButtonGroup (); public void init () { JPanel buttonPanel = new JPanel (); buttonPanel.add(okButton); mainPanel.setLayout(new GridLayout (0 , 3 )); mainWin.add(mainPanel, BorderLayout.CENTER); fields[0 ] = new JFormattedTextField (NumberFormat .getIntegerInstance()); fields[0 ].setValue(100 ); addRow("整数格式文本框 :" , fields[0 ]); fields[1 ] = new JFormattedTextField (NumberFormat .getCurrencyInstance()); fields[1 ].setValue(100.0 ); addRow("货币格式文本框:" , fields[1 ]); fields[2 ] = new JFormattedTextField (DateFormat.getDateInstance()); fields[2 ].setValue(new Date ()); addRow("默认的日期格式器:" , fields[2 ]); DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT); format.setLenient(false ); fields[3 ] = new JFormattedTextField (format); fields[3 ].setValue(new Date ()); addRow("SHORT类型的日期格式器(语法严格):" , fields[3 ]); try { DefaultFormatter formatter = new DefaultFormatter (); formatter.setOverwriteMode(false ); fields[4 ] = new JFormattedTextField (formatter); fields[4 ].setValue(new URL ("http://www.crazyit.org" )); addRow("URL:" , fields[4 ]); } catch (MalformedURLException e) { e.printStackTrace(); } try { MaskFormatter formatter = new MaskFormatter ("020-########" ); formatter.setPlaceholderCharacter('□' ); fields[5 ] = new JFormattedTextField (formatter); fields[5 ].setValue("020-28309378" ); addRow("电话号码:" , fields[5 ]); } catch (ParseException ex) { ex.printStackTrace(); } JPanel focusLostPanel = new JPanel (); for (int i = 0 ; i < behaviorLabels.length ; i++ ) { final int index = i; final JRadioButton radio = new JRadioButton (behaviorLabels[i]); if (i == 1 ) { radio.setSelected(true ); } focusLostPanel.add(radio); bg.add(radio); radio.addActionListener(e -> { if (radio.isSelected()) { for (int j = 0 ; j < fields.length ; j++) { fields[j].setFocusLostBehavior(behaviors[index]); } } }); } focusLostPanel.setBorder(new TitledBorder (new EtchedBorder (), "请选择焦点失去后的行为" )); JPanel p = new JPanel (); p.setLayout(new BorderLayout ()); p.add(focusLostPanel , BorderLayout.NORTH); p.add(buttonPanel , BorderLayout.SOUTH); mainWin.add(p , BorderLayout.SOUTH); mainWin.pack(); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.setVisible(true ); } private void addRow (String labelText, final JFormattedTextField field) { mainPanel.add(new JLabel (labelText)); mainPanel.add(field); final JLabel valueLabel = new JLabel (); mainPanel.add(valueLabel); okButton.addActionListener(event -> { Object value = field.getValue(); valueLabel.setText(value.toString()); }); } public static void main (String[] args) { new JFormattedTextFieldTest ().init(); } }
运行上面程序,会看到窗口中出现三个格式化文本框,其中第一个格式化文本框只能输入数字,其他字符无法输入到该文本框内;第二个格式化文本框有输入校验器,只有当用户输入的内容符合该文本框的要求时,用户才可以离开该文本框;第三个格式化文本框的格式器是自定义的格式器,它要求用户输入的内容是一个合法的IP地址。