由于这个问题经常出现,我在这个问题上投入了更多的精力,而不是我通常会做的。
我投给JFormattedTextField..每个Swing开发人员都应该在他/她的工具箱中有一个改进的该类版本,因为它允许通过正确的选择来验证几乎所有您可以想到的东西。Format..我已经用过的例子:字符串输入,其中
String可能不是空的
坐标输入
日期输入
编辑
JSpinner
地图比例尺
数字
...
它还允许在输入无效时进行视觉反馈,例如,InputVerifier..它仍然允许用户输入任何内容,但是当该值无效时,该值就不会被接受,并且该值永远不会离开UI。我认为(但这也是我的观点),最好允许用户输入无效的输入,然后用例如DocumentFilter..当在文本字段中键入字符而不出现时,我会怀疑存在错误。
让我用一些代码(实际上是一些代码)来说明这一点。首先是小型演示应用程序。此应用程序仅显示JFormattedTextField为了数字。只需使用另一种格式,就可以重用该组件进行完全不同的验证。
import be.pcl.swing.ImprovedFormattedTextField;import javax.swing.*;import java.awt.BorderLayout;import java.awt.EventQueue;
import java.awt.event.ActionEvent;import java.beans.PropertyChangeEvent;import java.beans.PropertyChangeListener;
import java.text.NumberFormat;/**
* See http://stackoverflow.com/q/1313390/1076463
*/public class FormattedTextFieldDemo {
public static void main( String[] args ) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame testFrame = new JFrame( "FormattedTextFieldDemo" );
NumberFormat integerNumberInstance = NumberFormat.getIntegerInstance();
ImprovedFormattedTextField integerFormattedTextField = new ImprovedFormattedTextField( integerNumberInstance, 100 );
integerFormattedTextField.setColumns( 20 );
testFrame.add( createButtonPanel( integerFormattedTextField ), BorderLayout.NORTH );
final JTextArea textArea = new JTextArea(50, 50);
PropertyChangeListener updateTextAreaListener = new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent evt ) {
textArea.append( "New value: " + evt.getNewValue() + "\n" );
}
};
integerFormattedTextField.addPropertyChangeListener( "value", updateTextAreaListener );
testFrame.add( new JScrollPane( textArea ), BorderLayout.CENTER );
testFrame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
testFrame.pack();
testFrame.setVisible( true );
}
} );
}
private static JPanel createButtonPanel( final JFormattedTextField aTextField ){
JPanel panel = new JPanel( new BorderLayout( ) );
panel.add( aTextField, BorderLayout.WEST );
Action action = new AbstractAction() {
{
aTextField.addPropertyChangeListener( "editValid", new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent evt ) {
setEnabled( ( ( Boolean ) evt.getNewValue() ) );
}
} );
putValue( Action.NAME, "Show current value" );
}
@Override
public void actionPerformed( ActionEvent e ) {
JOptionPane.showMessageDialog( null, "The current value is [" + aTextField.getValue() + "]
of class [" + aTextField.getValue().getClass() + "]" );
}
};
panel.add( new JButton( action ), BorderLayout.EAST );
return panel;
}}
它只显示了ImprovedFormattedTextField和一个JButton它只在输入有效时才启用(啊哈,请吃那个。)DocumentFilter解决办法)。它还显示了JTextArea其中,每次遇到新的有效值时都会打印该值。按下按钮显示值。
的代码ImprovedFormattedTextField可以在下面找到,以及ParseAllFormat它所依赖的package be.pcl.swing;import javax.swing.JFormattedTextField;import javax.swing.JTextField;import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;import javax.swing.event.DocumentEvent;import javax.swing.event.DocumentListener;
import java.awt.Color;import java.awt.event.FocusAdapter;import java.awt.event.FocusEvent;import java.awt.event.KeyEvent;
import java.text.Format;import java.text.ParseException;/**
*
Extension of {@code JFormattedTextField} which solves some of the usability issues
*/public class ImprovedFormattedTextField extends JFormattedTextField {
private static final Color ERROR_BACKGROUND_COLOR = new Color( 255, 215, 215 );
private static final Color ERROR_FOREGROUND_COLOR = null;
private Color fBackground, fForeground;
/**
* Create a new {@code ImprovedFormattedTextField} instance which will use {@code aFormat} for the
* validation of the user input.
*
* @param aFormat The format. May not be {@code null}
*/
public ImprovedFormattedTextField( Format aFormat ) {
//use a ParseAllFormat as we do not want to accept user input which is partially valid
super( new ParseAllFormat( aFormat ) );
setFocusLostBehavior( JFormattedTextField.COMMIT_OR_REVERT );
updateBackgroundOnEachUpdate();
//improve the caret behavior
//see also http://tips4java.wordpress.com/2010/02/21/formatted-text-field-tips/
addFocusListener( new MousePositionCorrectorListener() );
}
/**
* Create a new {@code ImprovedFormattedTextField} instance which will use {@code aFormat} for the
* validation of the user input. The field will be initialized with {@code aValue}.
*
* @param aFormat The format. May not be {@code null}
* @param aValue The initial value
*/
public ImprovedFormattedTextField( Format aFormat, Object aValue ) {
this( aFormat );
setValue( aValue );
}
private void updateBackgroundOnEachUpdate() {
getDocument().addDocumentListener( new DocumentListener() {
@Override
public void insertUpdate( DocumentEvent e ) {
updateBackground();
}
@Override
public void removeUpdate( DocumentEvent e ) {
updateBackground();
}
@Override
public void changedUpdate( DocumentEvent e ) {
updateBackground();
}
} );
}
/**
* Update the background color depending on the valid state of the current input. This provides
* visual feedback to the user
*/
private void updateBackground() {
boolean valid = validContent();
if ( ERROR_BACKGROUND_COLOR != null ) {
setBackground( valid ? fBackground : ERROR_BACKGROUND_COLOR );
}
if ( ERROR_FOREGROUND_COLOR != null ) {
setForeground( valid ? fForeground : ERROR_FOREGROUND_COLOR );
}
}
@Override
public void updateUI() {
super.updateUI();
fBackground = getBackground();
fForeground = getForeground();
}
private boolean validContent() {
AbstractFormatter formatter = getFormatter();
if ( formatter != null ) {
try {
formatter.stringToValue( getText() );
return true;
} catch ( ParseException e ) {
return false;
}
}
return true;
}
@Override
public void setValue( Object value ) {
boolean validValue = true;
//before setting the value, parse it by using the format
try {
AbstractFormatter formatter = getFormatter();
if ( formatter != null ) {
formatter.valueToString( value );
}
} catch ( ParseException e ) {
validValue = false;
updateBackground();
}
//only set the value when valid
if ( validValue ) {
int old_caret_position = getCaretPosition();
super.setValue( value );
setCaretPosition( Math.min( old_caret_position, getText().length() ) );
}
}
@Override
protected boolean processKeyBinding( KeyStroke ks, KeyEvent e, int condition, boolean pressed ) {
//do not let the formatted text field consume the enters. This allows to trigger an OK button by
//pressing enter from within the formatted text field
if ( validContent() ) {
return super.processKeyBinding( ks, e,
condition, pressed ) && ks != KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 );
}
else {
return super.processKeyBinding( ks, e,
condition, pressed );
}
}
private static class MousePositionCorrectorListener extends FocusAdapter {
@Override
public void focusGained( FocusEvent e ) {
/* After a formatted text field gains focus, it replaces its text with its
* current value, formatted appropriately of course. It does this after
* any focus listeners are notified. We want to make sure that the caret
* is placed in the correct position rather than the dumb default that is
* before the 1st character ! */
final JTextField field = ( JTextField ) e.getSource();
final int dot = field.getCaret().getDot();
final int mark = field.getCaret().getMark();
if ( field.isEnabled() && field.isEditable() ) {
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
// Only set the caret if the textfield hasn't got a selection on it
if ( dot == mark ) {
field.getCaret().setDot( dot );
}
}
} );
}
}
}}
这个ParseAllFormat班级:package be.pcl.swing;import java.text.AttributedCharacterIterator;import java.text.FieldPosition;import java.text.Format;
import java.text.ParseException;import java.text.ParsePosition;/**
*
Decorator for a {@link Format Format} which only accepts values which can be completely parsed
* by the delegate format. If the value can only be partially parsed, the decorator will refuse to
* parse the value.
*/public class ParseAllFormat extends Format {
private final Format fDelegate;
/**
* Decorate aDelegate to make sure if parser everything or nothing
*
* @param aDelegate The delegate format
*/
public ParseAllFormat( Format aDelegate ) {
fDelegate = aDelegate;
}
@Override
public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) {
return fDelegate.format( obj, toAppendTo, pos );
}
@Override
public AttributedCharacterIterator formatToCharacterIterator( Object obj ) {
return fDelegate.formatToCharacterIterator( obj );
}
@Override
public Object parseObject( String source, ParsePosition pos ) {
int initialIndex = pos.getIndex();
Object result = fDelegate.parseObject( source, pos );
if ( result != null && pos.getIndex()
int errorIndex = pos.getIndex();
pos.setIndex( initialIndex );
pos.setErrorIndex( errorIndex );
return null;
}
return result;
}
@Override
public Object parseObject( String source ) throws ParseException {
//no need to delegate the call, super will call the parseObject( source, pos ) method
return super.parseObject( source );
}}
可能的改进:这个
setBackground并不是所有的外表和感觉都尊重。有时,您可以使用
setForeground相反,即便如此,也不能保证所有的L&F都会尊重这一点。因此,为了获得视觉反馈,最好在字段旁边使用感叹号。缺点是,如果突然添加/移除图标,这可能会破坏布局。
反馈只表示输入有效/无效。没有任何东西表明预期格式是什么。的一个可能的解决方案是使用自创建的
Format,它包括一个有效输入的描述/示例,并将其作为工具提示放在
JFormattedTextField.