你说:
Spinner 的文本字段允许任意输入,但只接受 commitEdit 上的数字输入。
如果您只是希望您的commitEdit() 电话接受“125 英寸”之类的内容,请尝试以下方法:
- 为
JSpinner 提供DefaultEditor。
- 获取
DefaultEditor的JFormattedTextField。
- 使其可编辑。
- 为其提供自定义(简单)
AbstractFormatterFactory。
- 您的自定义
AbstractFormatterFactory 使自定义(简单)AbstractFormatters。
- 这些
AbstractFormatters 将用户的输入字符串解析为一个值并返回,因此您可以检查任何解析错误并在需要时抛出ParseException。
遵循示例代码:
import java.awt.GridLayout;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Objects;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;
public final class CommitSpinner extends JPanel {
private String currentUnit;
/**
* Your formatter converts the text to integer by ignoring any measuring units,
* and then converts the integer back to string by appending the last measuring unit:
*/
private final class MyFormatter extends AbstractFormatter {
@Override
public Object stringToValue(final String text) throws ParseException {
currentUnit = parseUnitPart(text);
return parseIntegerPart(text);
}
@Override
public String valueToString(final Object value) throws ParseException {
return Objects.toString(value) + ' ' + currentUnit;
}
}
private final class MyFormatterFactory extends AbstractFormatterFactory {
private final HashMap<JFormattedTextField, AbstractFormatter> formatters;
private MyFormatterFactory() {
formatters = new HashMap<>();
}
/**
* Because this method is a 'getter', I implemented the factory with a HashMap.
* If it was a 'createFormatter' method for example, you could simply return a new
* instance of MyFormatter.
* @param tf the formatted text field to obtain its formatter.
* @return the formatter for the given formatted text field.
*/
@Override
public AbstractFormatter getFormatter(final JFormattedTextField tf) {
if (!formatters.containsKey(tf))
formatters.put(tf, new MyFormatter());
return formatters.get(tf);
}
}
private CommitSpinner() {
super(new GridLayout(0, 1));
currentUnit = "inch"; //Let's say the first value of the spinner is measured in inches.
final JSpinner spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
final DefaultEditor editor = new DefaultEditor(spin); //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
final JFormattedTextField field = editor.getTextField();
field.setFocusLostBehavior(JFormattedTextField.COMMIT); //Could be "PERSIST" also, but it shall not be "COMMIT_OR_REVERT" (which is the default).
field.setFormatterFactory(new MyFormatterFactory());
field.setEditable(true); //Allow user input (obvious reasons).
spin.setEditor(editor);
//The commitButton button will "commitEdit()", and the ParseException exception part works as expected!
final JButton commitButton = new JButton("Check input");
commitButton.addActionListener(e -> {
try {
spin.commitEdit();
JOptionPane.showMessageDialog(null, currentUnit + " = " + spin.getValue());
}
catch (final ParseException pe) {
JOptionPane.showMessageDialog(null, pe.toString(), "Illegal input!", JOptionPane.ERROR_MESSAGE);
}
});
add(spin);
add(commitButton);
}
public static void main(final String[] args) {
final JFrame frame = new JFrame("CommitSpinner demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new CommitSpinner());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//Let's parse user input... What could go wrong?... xD
private static int parseIntegerPart(final String text) throws ParseException {
if (text == null)
throw new ParseException("Text is null.", 0);
if (text.trim().isEmpty())
throw new ParseException("Text is empty.", 0);
final String[] args = text.split(" ");
if (args.length != 2)
throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
final String unit = args[1].trim();
if (!unit.equalsIgnoreCase("inch")
&& !unit.equalsIgnoreCase("cm"))
throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
try {
return Integer.valueOf(args[0].trim());
}
catch (final NumberFormatException nfe) {
throw new ParseException(args[0] + " is not a valid integer value.", 0);
}
}
//Let's parse user input... What could go wrong?... xD
private static String parseUnitPart(final String text) throws ParseException {
if (text == null)
throw new ParseException("Text is null.", 0);
if (text.trim().isEmpty())
throw new ParseException("Text is empty.", 0);
final String[] args = text.split(" ");
if (args.length != 2)
throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
final String unit = args[1].trim().toLowerCase();
if (!unit.equals("inch") && !unit.equals("cm"))
throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
return unit;
}
}
你也说过:
如果有垃圾输入,我希望在它尝试解析整个输入之前得到通知。
JFormattedTextField 中的自定义 DocumentFilter 可以解决问题。
在AbstractFormatter 中有一个名为getDocumentFilter() 的方法。如果您看到代码,则调用此方法将DocumentFilter 添加到JFormattedTextField。
DocumentFilters' 方法在用户输入时被调用,也就是说,您可以在用户“提交”之前解析用户的输入。
默认实现返回null,这意味着不会添加DocumentFilter。
所以我只是重写了这个方法来实现我的自定义(简单)DocumentFilter,它反过来在用户输入时检查用户输入......
如果用户的输入解析成功,那么我只需将标签的前景色设置为绿色。如果没有,那么红色。但这只是为了演示您可以在哪里处理此类事件。
以下示例代码:
import java.awt.Color;
import java.awt.GridLayout;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Objects;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
public final class RealTimeSpinner extends JPanel {
private String currentUnit;
private final JLabel stateLabel;
private final class MyFormatter extends AbstractFormatter {
@Override
public Object stringToValue(final String text) throws ParseException {
currentUnit = parseUnitPart(text);
return parseIntegerPart(text);
}
@Override
public String valueToString(final Object value) throws ParseException {
return Objects.toString(value) + ' ' + currentUnit;
}
@Override
protected DocumentFilter getDocumentFilter() {
return new DocumentFilter() {
private void after(final FilterBypass fb) throws BadLocationException {
try {
//Obtain the user's input text so far:
final String theWholeNewText = fb.getDocument().getText(0, fb.getDocument().getLength());
//Try parse the user's input so far:
parseUnitPart(theWholeNewText);
parseIntegerPart(theWholeNewText);
//If the parsing succeeds, then set the foreground color to GREEN:
stateLabel.setForeground(Color.GREEN.darker());
}
catch (final ParseException pe) {
//If the parsing fails, then set the foreground color to RED:
stateLabel.setForeground(Color.RED.darker());
}
}
@Override
public void remove(final FilterBypass fb, final int offset, final int length) throws BadLocationException {
super.remove(fb, offset, length);
after(fb);
}
@Override
public void insertString(final FilterBypass fb, final int offset, final String string, final AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, string, attr);
after(fb);
}
@Override
public void replace(final FilterBypass fb, final int offset, final int length, final String text, final AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, text, attrs);
after(fb);
}
};
}
}
private final class MyFormatterFactory extends AbstractFormatterFactory {
private final HashMap<JFormattedTextField, AbstractFormatter> formatters;
private MyFormatterFactory() {
formatters = new HashMap<>();
}
/**
* Because this method is a 'getter', I implemented the factory with a HashMap.
* If it was a 'createFormatter' method for example, you could simply return a new
* instance of MyFormatter.
* @param tf the formatted text field to obtain its formatter.
* @return the formatter for the given formatted text field.
*/
@Override
public AbstractFormatter getFormatter(final JFormattedTextField tf) {
if (!formatters.containsKey(tf))
formatters.put(tf, new MyFormatter());
return formatters.get(tf);
}
}
private RealTimeSpinner() {
super(new GridLayout(0, 1));
currentUnit = "inch"; //Let's say the first value of the spinner is measured in inches.
final JSpinner spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
final DefaultEditor editor = new DefaultEditor(spin); //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
final JFormattedTextField field = editor.getTextField();
field.setFocusLostBehavior(JFormattedTextField.COMMIT); //Could be "PERSIST" also, but it shall not be "COMMIT_OR_REVERT" (which is the default).
field.setFormatterFactory(new MyFormatterFactory());
field.setEditable(true); //Allow user input (obvious reasons).
spin.setEditor(editor);
final JButton commitButton = new JButton("Check input");
commitButton.addActionListener(e -> {
try {
spin.commitEdit();
JOptionPane.showMessageDialog(null, currentUnit + " = " + spin.getValue());
}
catch (final ParseException pe) {
JOptionPane.showMessageDialog(null, pe.toString(), "Illegal input!", JOptionPane.ERROR_MESSAGE);
}
});
stateLabel = new JLabel("This is the color of the state of the input.", JLabel.CENTER);
stateLabel.setForeground(Color.GREEN.darker());
add(spin);
add(commitButton);
add(stateLabel);
}
public static void main(final String[] args) {
final JFrame frame = new JFrame("CommitSpinner demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RealTimeSpinner());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//Let's parse user input... What could go wrong?... xD
private static int parseIntegerPart(final String text) throws ParseException {
if (text == null)
throw new ParseException("Text is null.", 0);
if (text.trim().isEmpty())
throw new ParseException("Text is empty.", 0);
final String[] args = text.split(" ");
if (args.length != 2)
throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
final String unit = args[1].trim();
if (!unit.equalsIgnoreCase("inch")
&& !unit.equalsIgnoreCase("cm"))
throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
try {
return Integer.valueOf(args[0].trim());
}
catch (final NumberFormatException nfe) {
throw new ParseException(args[0] + " is not a valid integer value.", 0);
}
}
//Let's parse user input... What could go wrong?... xD
private static String parseUnitPart(final String text) throws ParseException {
if (text == null)
throw new ParseException("Text is null.", 0);
if (text.trim().isEmpty())
throw new ParseException("Text is empty.", 0);
final String[] args = text.split(" ");
if (args.length != 2)
throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
final String unit = args[1].trim().toLowerCase();
if (!unit.equals("inch") && !unit.equals("cm"))
throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
return unit;
}
}