java plain document_Just Java IT

淳于健
2023-12-01

在使用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将是非常容易和简单的方式。

注:这篇文章基本根据SDN的Core Java Tech Tips意译而来,代码基本跟其一致,另外还参考了M. Robinson & P. Vorobiev的Swing, Chapter 11

 类似资料:

相关阅读

相关文章

相关问答