转自andycpp,http://blog.csdn.net/andycpp/article/details/1698238
文本框是使用率非常高的一个控件, 用于接收用户的输入信息。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 <</span> 4; i++) { int b = a[i]; if (b <</span> 0) b += 256; builder.append(String.valueOf(b)); if (i <</span> 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 <</span> 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 <</span> 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"));