I have the following code
import javax.swing.*; import java.awt.*; import net.miginfocom.swing.MigLayout; import Sorts.*; import javax.swing.event.*; import java.awt.event.*; import java.awt.Color; public class SortsGui { JFrame myMainWindow = new JFrame("Sorts"); JPanel sortPanel = new JPanel(); //first panel components public int nextTextBox = 20; JTextField[] allField = new JTextField [25]; //end first panel public void runGUI() { myMainWindow.setBounds(10, 10, 800, 800); myMainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); myMainWindow.setLayout(new GridLayout(1,1)); createSortTestPanel(); myMainWindow.getContentPane().add(sortPanel); myMainWindow.setVisible(true); } public void createSortTestPanel() { MigLayout layout = new MigLayout("" , "[grow]"); sortPanel.setLayout(layout); refreshScreen(); } public void refreshScreen() { sortPanel.removeAll(); for(int i = 0; i<nextTextBox;i++) { int fix = i+1; allField[i] = new JTextField(""); sortPanel.add(allField[i],"growx"); allField[i].addKeyListener(new KeyListener () { public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); if(Character.isDigit(c)) { } else { e.consume(); } try { int parseTest = Integer.parseInt(allField[i].getText()); } catch(Exception exc) { allField[i].setBackground(Color.RED); } } }); } } public static void main(String[] args) { SortsGui sG = new SortsGui(); sG.runGUI(); } }
My aim here is to create an array of JTextFields which have a keylistener on. This keylistener should prevent anything other than numbers being entered in the JTextField. It should also change the color of the JTextField’s background if the number entered is not an int. For example 2147483647554. However when I compile this I get the error
So how do I make this so that it is either final or effectively final on all the JTextFields?
Advertisement
Answer
My aim here is to create an array of JTextFields which have a keylistener on. This keylistener should prevent anything other than numbers being entered in the JTextField
The short answer to this is, don’t use KeyListener
, it won’t capture the use cases of the user pasting text into the field or if the field is updated programmatically
Instead you want to use a DocumentFilter
, for example
public class IntFilter extends DocumentFilter { @Override public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { StringBuilder buffer = new StringBuilder(text.length()); for (int index = 0; index < text.length(); index++) { if (Character.isDigit(text.charAt(index))) { buffer.append(text.charAt(index)); } } super.insertString(fb, offset, buffer.toString(), attr); ValidationListener listener = getValidationListener(); } @Override public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException { if (length > 0) { fb.remove(offset, length); } insertString(fb, offset, string, attr); } }
See Implementing a Document Filter for more details and DocumentFilter Examples for more examples
It should also change the color of the JTextField’s background if the number entered is not an int
You can do post validation using a InputVerifier
, but that might not meet your needs.
This creates a problem. The DocumentFilter
, shouldn’t care about the field it’s applied to, but since, it’s doing the validation, it will know when something has gone wrong, so we need some way for the filter to provide notification when the validation fails…
First, we need some callback which tells us when validation has failed or passed…
public interface ValidationListener { public void validationFailed(); public void validationPassed(); }
Then we need to update the filter to raise those notifications based on it’s rules…
public class IntFilter extends DocumentFilter { private ValidationListener validationListener; public void setValidationListener(ValidationListener validationListener) { this.validationListener = validationListener; } public ValidationListener getValidationListener() { return validationListener; } @Override public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { boolean validationFailed = false; StringBuilder buffer = new StringBuilder(text.length()); for (int index = 0; index < text.length(); index++) { if (Character.isDigit(text.charAt(index))) { buffer.append(text.charAt(index)); } else { validationFailed = true; } } super.insertString(fb, offset, buffer.toString(), attr); ValidationListener listener = getValidationListener(); if (listener != null) { if (validationFailed) { listener.validationFailed(); } else { listener.validationPassed(); } } } @Override public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException { if (length > 0) { fb.remove(offset, length); } insertString(fb, offset, string, attr); } }
Then we need to define our implementation of the ValidationListener
to perform the actions we need…
public class DefaultValidationHandler implements ValidationListener { private JTextField field; public DefaultValidationHandler(JTextField field) { this.field = field; } public JTextField getField() { return field; } @Override public void validationFailed() { getField().setBackground(Color.RED); } @Override public void validationPassed() { getField().setBackground(UIManager.getColor("TextField.background")); } }
Here, the listener maintains a reference to the field which we want to control
Then we bind it altogether…
JTextField field = new JTextField(10); DefaultValidationHandler handler = new DefaultValidationHandler(field); IntFilter filter = new IntFilter(); filter.setValidationListener(handler); ((AbstractDocument)field.getDocument()).setDocumentFilter(filter);
This is all a bit crude, but it gets the basic idea across.
Some improvements might include passing the reference of the DocumentFilter
back via the methods of the ValidationListener
, you could then use this to lookup the field which triggered the event and update it, reducing the number of handlers you might need to create, for example.
For example
Updated ValidationListener
public interface ValidationListener { public void validationFailed(DocumentFilter filter); public void validationPassed(DocumentFilter filter); }
Updated IntFilter
public class IntFilter extends DocumentFilter { private ValidationListener validationListener; public void setValidationListener(ValidationListener validationListener) { this.validationListener = validationListener; } public ValidationListener getValidationListener() { return validationListener; } @Override public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { boolean validationFailed = false; StringBuilder buffer = new StringBuilder(text.length()); for (int index = 0; index < text.length(); index++) { if (Character.isDigit(text.charAt(index))) { buffer.append(text.charAt(index)); } else { validationFailed = true; } } super.insertString(fb, offset, buffer.toString(), attr); ValidationListener listener = getValidationListener(); if (listener != null) { if (validationFailed) { listener.validationFailed(this); } else { listener.validationPassed(this); } } } @Override public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String string, AttributeSet attr) throws BadLocationException { if (length > 0) { fb.remove(offset, length); } insertString(fb, offset, string, attr); } }
Example implementation…
public class TestPane extends JPanel { private Map<DocumentFilter, JTextField> fields; public TestPane() { fields = new HashMap<>(25); ValidationListener listener = new ValidationListener() { @Override public void validationFailed(DocumentFilter filter) { JTextField field = fields.get(filter); if (field != null) { field.setBackground(Color.RED); } } @Override public void validationPassed(DocumentFilter filter) { JTextField field = fields.get(filter); if (field != null) { field.setBackground(UIManager.getColor("TextField.background")); } } }; setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; for (int index = 0; index < 10; index++) { JTextField field = new JTextField(10); IntFilter filter = new IntFilter(); filter.setValidationListener(listener); ((AbstractDocument) field.getDocument()).setDocumentFilter(filter); fields.put(filter, field); add(field, gbc); } } }