文本框是使用率非常高的一个控件, 用于接收用户的输入信息。java中有一个最基本的文本框JTextField可以实现这一功能。但是绝大多数情况下,我们都需要对用户输入的内容做某些检测,这时候JTextField就不是那么好用了,实现起来就比较麻烦了。于是java又提供了一个复杂的文本框JFormattedTextField来帮助我们实现一些常用的功能,当然,如果你要实现的功能太复杂,还是老老实实的给 JTextField增加监听器吧。
如果 你使用的是 JTextField,那么不管这个文本框准备接受的是数字,日期,货币还是ip地址,对于你来讲,你只能通过getText方法得到一个字符串,然后自己写代码把这个字符串转化为相应的数据对象。而 JFormattedTextField的内在机制可以将字符串自动转化为相应的数据对象,你既可以通过 getText得到字符串,也可以通过getValue方法的到转化后的数据对象(该对象是一个Object对象,还需要强制转化一下)。正是因为 JFormattedTextField同时提供了外部显示的字符串和实际的数据对象,我们就可以利用他来完成一些常见的功能。下面我们来简单探讨一下 JFormattedTextField的常见的使用方式:
首先,顾名思义, JFormattedTextField最基本的功能是,
“检测格式的正确性”。最常见的就是数字和日期的格式问题,这些格式是全世界通用的,而java中也提供了相应的格式类,可以直接使用,我们来看一下代码:
JFormattedTextField intField
=
new
JFormattedTextField(NumberFormat.getIntegerInstance());
短短的一行代码就能实现如下功能:当该文本框失去焦点时,检测用户的输入是否符合整数的格式。若不符合整数的格式,则有几种方式来提示用户。默认的方式是丢弃用户的输入,文本框内显示的内容恢复到用户输入前的状态。比如文本框内原来是“123”,你输入了个“abc”,因为“abc”不是一个整数,因此当光标离开文本框后,文本框的内容立刻又变回“123”,并且内部的Value对象仍然是整数123。
我个人认为这种行为不是一个很好的用户体验,用户辛辛苦苦的输入了很长一串内容,如果仅仅是不小心错了一点,你就把所有内容都变没了,不给用户修改的机会,这是不合适的。无论用户输入的是对是错,你只能给出相应的提示,而不能擅自清除用户的输入。幸好java也考虑到了这一点,你在上述代码的后面再加一句:
intField.setFocusLostBehavior(JFormattedTextField.COMMIT);
这样用户的错误输入就不会丢失了,但也没有任何提示性信息来告知用户输入错误。同时请注意,虽然文本框内会保留“abc”,但是内部的Value对象仍然是整数123!!
JFormattedTextField提供了另外一种机制来提示用户输入错误,
“如果输入的格式错误,则不允许焦点离开文本框”。换句话说,如果你输入的是“abc”,那么你就别想在其他的文本框里输入内容,焦点会一直停留在此文本框内,直到你输入了正确的整数格式。代码如下:
JFormattedTextField intField
=
new
JFormattedTextField(NumberFormat.getIntegerInstance());
intField.setInputVerifier(
new
FormattedTextFieldVerifier());
//
定义一个InputVerifier类,把这个类添加到JFormattedTextField中
class
FormattedTextFieldVerifier
extends
InputVerifier
...
{
public boolean verify(JComponent component)
...{
JFormattedTextField field = (JFormattedTextField) component;
//若用户的输入符合格式,则返回true,否则返回false
return field.isEditValid();
}
}
大家看到了,上述代码不光为 JFormattedTextField添加了一个整数格式,还为他添加了一个验证器类。验证器类中有一个verify方法,此方法若返回ture,则焦点可以离开,此方法若返回false,则焦点不能离开。在此方法中,我们调用isEditValid方法来验证格式的正确与否。 isEditValid方法是 JFormattedTextField提供的,通过此方法可以很方便的验证格式的正确性。
当然,我们最常见的错误提示方式是:若用户输入错误,在此文本框的后面出现一个鲜红的“
×”。很遗憾 JFormattedTextField没有提供这种机制,你只能在他后面添加一个JLabel,然后监听 JFormattedTextField的失去焦点事件,若不符合格式,您自己在JLabel上打一个 “
×”。
JFormattedTextField还提供了另外一个非常有用的机制:“
不允许输入非法字符”。这样您的文本框就可以只接收数字而不接收字母,防止用户的误操作。代码如下:
//
用自定义的DocumentFilter替换格式类中默认的DocumentFilter
JFormattedTextField intField3
=
new
JFormattedTextField(
new
InternationalFormatter(NumberFormat.getIntegerInstance())
...
{
protected DocumentFilter getDocumentFilter() ...{
return filter;
}
private DocumentFilter filter = new IntFilter();
}
);
//
自定义一个DocumentFilter类
class
IntFilter
extends
DocumentFilter
...
{
//重载insertString方法
public void insertString(FilterBypass fb, int offset, String string,
AttributeSet attr) throws BadLocationException ...{
StringBuilder builder = new StringBuilder(string);
for (int i = builder.length() - 1; i >= 0; i--) ...{
int cp = builder.codePointAt(i);
if (!Character.isDigit(cp) && cp != '-') ...{
builder.deleteCharAt(i);
if (Character.isSupplementaryCodePoint(cp)) ...{
i--;
builder.deleteCharAt(i);
}
}
}
super.insertString(fb, offset, builder.toString(), attr);
}
//重载replace方法
public void replace(FilterBypass fb, int offset, int length, String string,
AttributeSet attr) throws BadLocationException ...{
if (string != null) ...{
StringBuilder builder = new StringBuilder(string);
for (int i = builder.length() - 1; i >= 0; i--) ...{
int cp = builder.codePointAt(i);
if (!Character.isDigit(cp) && cp != '-') ...{
builder.deleteCharAt(i);
if (Character.isSupplementaryCodePoint(cp)) ...{
i--;
builder.deleteCharAt(i);
}
}
}
string = builder.toString();
}
super.replace(fb, offset, length, string, attr);
}
}
原理很简单,就是添加一个过滤器。不过要注意,这个过滤器不是直接添加到 JFormattedTextField中,而是替换格式类中默认的过滤器。在过滤器中,你要重载2个方法,分别是insertString和replace。在这2个方法中,把你不希望用户输入的字符过滤掉。
但是请注意,不要被这2个方法的名字迷惑了,当你将光标定位到文本框中(不选中任何文字),然后敲击一下键盘,系统会调用哪个方法?是insertString吗?不是,是replace!!!目前为止我还不知道insertString方法会在什么时候被调用,至少在我的程序中,它从来没有被调用过。但是SUN的专家告诉我们“
您一定要将insertString这个方法重载,它将在某些时候被调用”。听专家的话一定没错,反正我每次都将他重载了。
让我们再来看看JFormattedTextField另外一个很有用的机制,“
输入固定长度的字符串”。当你的文本框准备接收一个身份证号,手机号码,邮政编码等信息的时候,由于这些信息都是固定长度,并且只有一部分字符是合法字符(0~9),如果我们能做这些限制的话,将大大减少用户出错的可能。你可以为 JFormattedTextField添加一个“掩码格式”来实现这一功能。注意,“掩码格式”和前面说的“数值格式”是并列关系,也就是说你只能二取其一,而不能同时享受这两种格式的好处。下面我们来看看代码:
MaskFormatter formatter
=
new
MaskFormatter(
"
###########
"
);
formatter.setPlaceholderCharacter(
'
0
'
);
JFormattedTextField ssnField
=
new
JFormattedTextField(formatter);
上述代码就能限制输入一个长度为11位的数字,也就是手机号。
如果我们要输入的内容格式比较特殊呢,JAVA没有提供现成的格式类,我们该怎么办?比如一个IP地址?比如一个URL网址?JFormattedTextField允许你通过两种方式来实现“
自定义特殊格式”。第一种方法,自定义一个格式类,继承DefaultFormatter类,重载valueToString和stringToValue方法(会抛出一个ParseException异常),实现IP地址格式的代码如下:
class
IPAddressFormatter
extends
DefaultFormatter
...
{
public String valueToString(Object value) throws ParseException ...{
if (!(value instanceof byte[]))
throw new ParseException("Not a byte[]", 0);
byte[] a = (byte[]) value;
if (a.length != 4)
throw new ParseException("Length != 4", 0);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 4; i++) ...{
int b = a[i];
if (b < 0)
b += 256;
builder.append(String.valueOf(b));
if (i < 3)
builder.append('.');
}
return builder.toString();
}
public Object stringToValue(String text) throws ParseException ...{
StringTokenizer tokenizer = new StringTokenizer(text, ".");
byte[] a = new byte[4];
for (int i = 0; i < 4; i++) ...{
int b = 0;
if (!tokenizer.hasMoreTokens())
throw new ParseException("Too few bytes", 0);
try ...{
b = Integer.parseInt(tokenizer.nextToken());
} catch (NumberFormatException e) ...{
throw new ParseException("Not an integer", 0);
}
if (b < 0 || b >= 256)
throw new ParseException("Byte out of range", 0);
a[i] = (byte) b;
}
if (tokenizer.hasMoreTokens())
throw new ParseException("Too many bytes", 0);
return a;
}
}
第二种实现方法是直接使用 DefaultFormatter,然后 调用 JFormattedTextField的setValue方法,传递给他的参数必须是一个符合要求的类:该类拥有一个“以一个字符串为参数的构造函数”(会抛出异常,相当于上面的stringToValue方法),还有一个toString方法(也会抛出异常,相当于上面的valueToString方法)。比如一个URL网址,JAVA就有相应的类,我们可以直接使用,代码如下:
DefaultFormatter formatter
=
new
DefaultFormatter();
formatter.setOverwriteMode(
false
);
JFormattedTextField urlField
=
new
JFormattedTextField(formatter);
urlField.setFocusLostBehavior(JFormattedTextField.COMMIT);
urlField.setValue(
new
URL(
"
http://java.sun.com
"
));
只要用户的输入不符合网址的格式,都可以利用前面讲过的各种机制来提示用户。
至此, JFormattedTextField的各种功能都讲解完了。最后在强调一下该控件的一个容易让人犯错误的地方:
以上各种机制有效的前提是,对话框没有被关闭!!!换句话说,如果是一个登陆框,用户输入完信息后点“确定”,然后登陆框被关闭,那么上述各种机制均无效。你不要天真的以为用户输入错误焦点就不会离开,对话框都关闭了,何谈焦点?因此,如果你的对话框有一个“确定”按钮能关闭对话框,那么你最好在按钮的点击事件中检测一下各个文本框的格式是否真的正确再关闭,否则后果很严重地~