在使用
Swing的JTextField時,我們常常希望只接受那些符合我們要求的錄入,如數字、電話號碼、郵政編碼、E-mail等。JFC工作組在這方面也做了很多工作,每一次新的Java Se發布,往往都提供了新的、更方便和強大的有效性驗證方式,在這里列舉幾種不同的驗證方式。
這是最直覺的方式。利用
KeyListener來選擇允許的字符,且添加FocusListener,使得
內容不符合要求時不允許焦點轉移。這種方式很繁瑣,
Sun的建議是不推薦使用這種方式。
我們知道,
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方法版本的一個包裝。
在
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中,用戶在錄入字符時不會發現任何異常,但是當他確認錄入完成后,如果內容不符合有效性,焦點將不會轉移!這兩種情況都可能讓一個沒有經驗的用戶茫然,具體使用哪一種是一個見仁見智的問題。
在
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將是非常容易和簡單的方式。
注:這篇文章基本根據SDN的Core Java Tech Tips意譯而來,代碼基本跟其一致,另外還參考了M. Robinson & P. Vorobiev的Swing, Chapter 11