在使用 Swing的JTextField时,我们常常希望只接受那些符合我们要求的录入,如数字、电话号码、邮政编码、E-mail等。JFC工作组在这方面也做了很多工作,每一次新的Java Se发布,往往都提供了新的、更方便和强大的有效性验证方式,在这里列举几种不同的验证方式。
利用键盘和焦点事件
这是最直觉的方式。利用 KeyListener来选择允许的字符,且添加FocusListener,使得
内容不符合要求时不允许焦点转移。这种方式很繁琐, Sun的建议是不推荐使用这种方式。
使用自定义的 Document
我们知道, Swing组件是基于MVC实现的。JTextComponent的Model是一个叫做Document的Interface,我们可以通过限制Document的内容来达到有效性验证的目的。javax.swing.text包中有多个不同的Document的实现,JTextField使用的是PlainDocument。如果我们希望JTextField只接受数字,可以实现我们特定的Document并使之替换默认的Document:
package sdn; import javax.swing.text.*; public class IntegerDocument extends PlainDocument { int currentValue = 0; public int getValue() { return currentValue; } public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException { if (string == null) { return; } else { String newValue; int length = getLength(); if (length == 0) { newValue = string; } else { String currentContent = getText(0, length); StringBuffer currentBuffer = new StringBuffer(currentContent); currentBuffer.insert(offset, string); newValue = currentBuffer.toString(); } currentValue = checkInput(newValue, offset); super.insertString(offset, string, attributes); } } public void remove(int offset, int length) throws BadLocationException { int currentLength = getLength(); String currentContent = getText(0, currentLength); String before = currentContent.substring(0, offset); String after = currentContent.substring(length+offset, currentLength); String newValue = before + after; currentValue = checkInput(newValue, offset); super.remove(offset, length); } public int checkInput(String proposedValue, int offset) throws BadLocationException { if (proposedValue.length() > 0) { try { int newValue = Integer.parseInt(proposedValue); return newValue; } catch (NumberFormatException e) { throw new BadLocationException(proposedValue, offset); } } else { return 0; } } }
然后用 IntegerDocument去替换JTextField默认的Document:
package sdn; import javax.swing.*; import javax.swing.text.*; import java.awt.*; import java.awt.event.*; public class NumericInput { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Numeric Input"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout(2, 2)); frame.add(new JLabel("Number")); JTextField fieldOne = new JTextField(); Document doc= new IntegerDocument(); fieldOne.setDocument(doc); frame.add(fieldOne); frame.add(new JLabel("All")); JTextField fieldTwo = new JTextField(); frame.add(fieldTwo); frame.setSize(250, 90); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
代码很简单,一目了然。这里说点题外话, Sun建议的Swing Application的main函数写法如上所示:先建一个Runnable,然后把这个Runnable放到event-dispatch thread中去执行。另外,以前有的Developer(比如我)喜欢用SwingUtilities.invokeLater(runner)来将一个thread放到event-dispatch thread中,现在Sun也建议用EventQueue.invokeLater(runner),因为SwingUtilities方法版本仅仅是对EventQueue方法版本的一个包装。
用 InputVerifier来实现
在 J2SE 1.3中加入了一个名为InputVerifier的抽象类,可用于任何JComponent。其中定义了boolean verifiy(JComponent input)方法。如果组件中的文本是有效的,当焦点转移时(如按下Tab或Shift-Tab),verify方法返回true;否则返回false,使得焦点仍停留在当前组件上。我们仍以数字为例:
package sdn; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class NumericVerifier{ public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Numeric Verifier"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panel1 = new JPanel(new BorderLayout()); JLabel label1 = new JLabel("Numeric-only"); JTextField textField1 = new JTextField(); panel1.add(label1, BorderLayout.WEST); panel1.add(textField1, BorderLayout.CENTER); JPanel panel2 = new JPanel(new BorderLayout()); JLabel label2 = new JLabel("Anything"); JTextField textField2 = new JTextField(); panel2.add(label2, BorderLayout.WEST); panel2.add(textField2, BorderLayout.CENTER); JPanel panel3 = new JPanel(new BorderLayout()); JLabel label3 = new JLabel("Numeric-only"); JTextField textField3 = new JTextField(); panel3.add(label3, BorderLayout.WEST); panel3.add(textField3, BorderLayout.CENTER); InputVerifier verifier = new InputVerifier() { public boolean verify(JComponent comp) { boolean returnValue; JTextField textField = (JTextField)comp; try { Integer.parseInt(textField.getText()); returnValue = true; } catch (NumberFormatException e) { Toolkit.getDefaultToolkit().beep(); returnValue = false; } return returnValue; } }; textField1.setInputVerifier(verifier); textField3.setInputVerifier(verifier); frame.add(panel1, BorderLayout.NORTH); frame.add(panel2, BorderLayout.CENTER); frame.add(panel3, BorderLayout.SOUTH); frame.setSize(300, 95); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
这个例子的效果和上一个是不同的。自定义 Document的App中,用户将会发现任何非数字的字符都不会在JTextField中出现;而在使用InputVerifier的App中,用户在录入字符时不会发现任何异常,但是当他确认录入完成后,如果内容不符合有效性,焦点将不会转移!这两种情况都可能让一个没有经验的用户茫然,具体使用哪一种是一个见仁见智的问题。
使用 Document Filter
在 J2SE 1.4中,又加入了一个新的类:DocumentFilter。你无需再实现一个新的Document,而是对现有的Document过滤一遍。它的结果与实现自定义的Document并无二样,仅仅是思路不同而已。
package snd; import javax.swing.text.*; import java.awt.Toolkit; public class IntegerDocumentFilter extends DocumentFilter { int currentValue = 0; public IntegerDocumentFilter() { } public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { if (string == null) { return; } else { replace(fb, offset, 0, string, attr); } } public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException { replace(fb, offset, length, "", null); } public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { Document doc = fb.getDocument(); int currentLength = doc.getLength(); String currentContent = doc.getText(0, currentLength); String before = currentContent.substring(0, offset); String after = currentContent.substring( length+offset, currentLength); String newValue = before + (text == null ? "" : text) + after; currentValue = checkInput(newValue, offset); fb.replace(offset, length, text, attrs); } private int checkInput(String proposedValue, int offset) throws BadLocationException { int newValue = 0; if (proposedValue.length() > 0) { try { newValue = Integer.parseInt(proposedValue); } catch (NumberFormatException e) { throw new BadLocationException( proposedValue, offset); } } return newValue; } }
再将这个 Filter应用于Document:
package sdn; import javax.swing.*; import javax.swing.text.*; import java.awt.*; public class NumericInputFilter { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Numeric Input Filter"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout(2, 2)); frame.add(new JLabel("Number")); JTextField textFieldOne = new JTextField(); Document doc= textFieldOne.getDocument(); DocumentFilter filterOne = new IntegerDocumentFilter(); ((AbstractDocument)doc).setDocumentFilter(filterOne); textFieldOne.setDocument(doc); frame.add(textFieldOne); frame.add(new JLabel("All")); JTextField textFieldTwo = new JTextField(); frame.add(textFieldTwo); frame.setSize(250, 90); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
DocumentFilter只能用于Swing中的与text有关的组件(而InputVerifier可用于任何组件)。除了这几种方法,在对于TextField而言,我们还有JFormattedTextField,很多时候用JFormattedTextField将是非常容易和简单的方式