1

I am adapting code from here:

Value Change Listener to JTextField

EDIT 2

The following code gives me an infinite loop of dialogs when I press the up spinner arrow:

STRING: STRING: 10 VALS: 10 STRING: STRING: 10 VALS: 10 STRING: STRING: 10 VALS: 10 .....

Warning you will need to use taskmanager to kill it.

    public static void main(String[] args) {
    // TODO Auto-generated method stub

    JFrame F = new JFrame();
    F.setVisible(true);
    JPanel p = new JPanel();


    final JSpinner spin2 = new JSpinner();
    spin2.setModel(new SpinnerNumberModel(10, 10, 100, 1));

    JComponent comp = spin2.getEditor();
    JFormattedTextField field = (JFormattedTextField) comp.getComponent(0);
    DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
    formatter.setCommitsOnValidEdit(true);


    ((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getDocument().addDocumentListener(new DocumentListener() {
          public void changedUpdate(DocumentEvent e) {
                warn();
              }
              public void removeUpdate(DocumentEvent e) {
                warn();
              }
              public void insertUpdate(DocumentEvent e) {
                warn();
              }

              public void warn() {
                  String text = ((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getText();
                  JOptionPane.showMessageDialog(null,   "STRING: "+text, "Error Massage",     JOptionPane.ERROR_MESSAGE);
                  if (text != null && !text.trim().isEmpty()) {
                      int stringValue = Integer.parseInt(((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getText());
                      JOptionPane.showMessageDialog(null,
                              "VALS: "+spin2.getValue(), "Error Massage",
                              JOptionPane.ERROR_MESSAGE);
                     if (stringValue<10 || stringValue >100){
                       JOptionPane.showMessageDialog(null,
                          "Error: Number outside bounds", "Error Massage",
                          JOptionPane.ERROR_MESSAGE);
                     }

                  }
              }
            });


    p.add(spin2);   


    F.add(p);
    F.pack();
    F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


}

EDIT 3

This changes background to red when invalid, BUT values reverts to previous (if invalid) when field looses focus. I want to be able to put up a JOptionPane at that point saying value is STILL invalid, instead of reverting to previous:

        ((JSpinner.DefaultEditor)Position.getEditor()).getTextField().addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                //LOG.info("" + evt);
                if ("editValid".equals(evt.getPropertyName())) {
                    if (Boolean.FALSE.equals(evt.getNewValue())) {
                        SpinnerNumberModel model = (SpinnerNumberModel) Position.getModel();  

                        ((JSpinner.DefaultEditor)Position.getEditor()).getTextField().setBackground(Color.RED);
                        ((JSpinner.DefaultEditor)Position.getEditor()).getTextField().setToolTipText("Amount must be in range [ " + model.getMinimum() + " ... " + model.getMaximum() + " ] for this symbol");

                    }
                    else{
                        ((JSpinner.DefaultEditor)Position.getEditor()).getTextField().setBackground(Color.WHITE);
                    }
                }

            }
        });

//////////////////////////////////////////////////////////////////////////////////////////////////

ORIGINAL QUESTION

But if I use spinner to put in value lower than lower bound. I get a "(" in the text field and this error:

Exception in thread "AWT-EventQueue-0" java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:504) at java.lang.Integer.parseInt(Integer.java:527) at com.NResearch.ValueAtRisk.Sigma$7.warn(Sigma.java:626) at com.NResearch.ValueAtRisk.Sigma$7.removeUpdate(Sigma.java:619) at javax.swing.text.AbstractDocument.fireRemoveUpdate(AbstractDocument.java:260) at javax.swing.text.AbstractDocument.handleRemove(AbstractDocument.java:623) at javax.swing.text.AbstractDocument.remove(AbstractDocument.java:591) at javax.swing.text.AbstractDocument.replace(AbstractDocument.java:667) at javax.swing.text.JTextComponent.setText(JTextComponent.java:1718) at javax.swing.JFormattedTextField$AbstractFormatter.install(JFormattedTextField.java:949) at javax.swing.text.DefaultFormatter.install(DefaultFormatter.java:124) at javax.swing.text.InternationalFormatter.install(InternationalFormatter.java:285) at javax.swing.JFormattedTextField.setFormatter(JFormattedTextField.java:465) at javax.swing.JFormattedTextField.setValue(JFormattedTextField.java:789) at javax.swing.JFormattedTextField.processFocusEvent(JFormattedTextField.java:636) at java.awt.Component.processEvent(Component.java:6261) at java.awt.Container.processEvent(Container.java:2229) at java.awt.Component.dispatchEventImpl(Component.java:4861) at java.awt.Container.dispatchEventImpl(Container.java:2287) at java.awt.Component.dispatchEvent(Component.java:4687) at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1895) at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:938) at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:570) at java.awt.Component.dispatchEventImpl(Component.java:4731) at java.awt.Container.dispatchEventImpl(Container.java:2287) at java.awt.Component.dispatchEvent(Component.java:4687) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:735) at java.awt.EventQueue.access$200(EventQueue.java:103) at java.awt.EventQueue$3.run(EventQueue.java:694) at java.awt.EventQueue$3.run(EventQueue.java:692) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76) at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:87) at java.awt.EventQueue$4.run(EventQueue.java:708) at java.awt.EventQueue$4.run(EventQueue.java:706) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76) at java.awt.EventQueue.dispatchEvent(EventQueue.java:705) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138) at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)

final JSpinner spin2 = new JSpinner();
spin2.setModel(new SpinnerNumberModel(10, 10, 100, 1));

JComponent comp = spin2.getEditor();
JFormattedTextField field = (JFormattedTextField) comp.getComponent(0);
DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
formatter.setCommitsOnValidEdit(true);


((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getDocument().addDocumentListener(new DocumentListener() {
          public void changedUpdate(DocumentEvent e) {
                warn();
              }
              public void removeUpdate(DocumentEvent e) {
                warn();
              }
              public void insertUpdate(DocumentEvent e) {
                warn();
              }

              public void warn() {
                  int stringValue = Integer.parseInt(((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getText());
                  JOptionPane.showMessageDialog(null,
                          "VALS: "+spin2.getValue(), "Error Massage",
                          JOptionPane.ERROR_MESSAGE);
                 if (stringValue<10 || stringValue >100){
                   JOptionPane.showMessageDialog(null,
                      "Error: Please enter number bigger than 0", "Error Massage",
                      JOptionPane.ERROR_MESSAGE);
                 }

              }
            });
Community
  • 1
  • 1
ManInMoon
  • 6,795
  • 15
  • 70
  • 133
  • 1
    I wouldn't use a `DocumentListener` for this task. Instead, either let the spinner do it's job or employ a `InputVerifier` or `DocumentFilter` – MadProgrammer Jan 03 '14 at 11:58
  • @MadProgrammer some mistake(s), [is/are based on my post](http://stackoverflow.com/a/20903074/714968), I would need to delete this post, but logics is the same for DocumentListener or DocumentFilter :-) – mKorbel Jan 03 '14 at 12:00
  • for better help sooner post an SSCCE short runnable, compilable, – mKorbel Jan 03 '14 at 12:01
  • @MadProgrammer [this is opposite situation](http://stackoverflow.com/questions/8282488/why-does-setselected-on-jcheckbox-lose-effect) – mKorbel Jan 03 '14 at 12:03
  • @mKorbel Sorry, my comment was a little short. I wouldn't use a `DocumentFilter` to validate the values, but actually filter them directly. But I think the spinner already has it's own or is it the formatted field...:P – MadProgrammer Jan 03 '14 at 12:04
  • @MadProgrammer not an issue is by using JOptionPane, this derivate of Top-Level Container must be wrapped into invokeLater in all cases – mKorbel Jan 03 '14 at 12:10
  • @MadProgrammer I am really stuck here - could you point me at a working example please. – ManInMoon Jan 03 '14 at 12:28

4 Answers4

4

Custom DocumentListeners and formattedTextField don't play nicely with each other, better don't mix. Instead, use a PropertyChangeListener on the text field that listens for changes of its editValid property: whenever that changes to false, you could notify the users

field.addPropertyChangeListener(new PropertyChangeListener() {

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        LOG.info("" + evt);
        if ("editValid".equals(evt.getPropertyName()) 
            &&  Boolean.FALSE.equals(evt.getNewValue())) {
          SpinnerNumberModel model = (SpinnerNumberModel) spin2.getModel();  
          JOptionPane.showMessageDialog(null,
          "Error: Number must be in range [" + model.getMinimum() + " ..." + model.getMaximum() + "]",
           "Error Massage",
          JOptionPane.ERROR_MESSAGE);

        }

    }
});

BTW, personally, I agree with Mad - such an intrusive notification tends to annoy me and maybe your users as well ..

kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Hey this is working great. However, if I type in a low value and move mouse to another field. The value of spinner textfield reverts to its previous value. I would like to catch this event and issue a warning at that point. I assume this needs to be done as textfield loses focus. Can you tell me how to do that please? – ManInMoon Jan 03 '14 at 23:23
  • @ManInMoon you get the notification once on input of an invalid value (= _type in a low value_) - if the user ignores the warning, the invalid intermediate value is reverted to a valid value to protect data integrity. Nothing you should want to do in that case - a user ignoring the first warning is likely to ignore a second (or n-th) warning as well ... – kleopatra Jan 04 '14 at 11:21
  • Yes, you are probably correct. BUT I would like to know when the "revert" happens and disable some other functions that rely on this value – ManInMoon Jan 04 '14 at 12:00
  • Yes, I see that and I use it to set background colour depending on whether value is TRUE or FALSE. However, unless I pick a value in within bounds - it reverts to previous value once I move away from the field. I would like to trap this. – ManInMoon Jan 06 '14 at 15:47
  • what do you mean by "trap"? Other than get notified of a change to the editValid property, that you are seeing in case of revertion ... – kleopatra Jan 06 '14 at 15:57
  • sorry, but that's soo waaayyyy too user-unfriendly that I refuse to help you going that far ;-) – kleopatra Jan 06 '14 at 16:08
  • Sorry I don't understand. What do you think is wrong with this approach? – ManInMoon Jan 06 '14 at 16:09
  • ntohing new to earlier comments, you warned once, the default behaviour is to guarantee data integrity by reverting and you want to blow that and at the same time trap your users in the field by an overly intrusive re-warning ... – kleopatra Jan 06 '14 at 16:16
  • no not at all! Field goes red to user amount is invalid. Then only if he chooses to leave it invalid does he get a message! If I let it revert he gets a value that he did NOT choose! How is that wrong? – ManInMoon Jan 06 '14 at 16:18
  • Perhaps I am not explaining myself. The value the user types in IS invalid, but he may not notice the spinner reverting to previous value. What I want to do is warn him his typed value has been changed. Do you see? – ManInMoon Jan 06 '14 at 18:30
  • how can s/he not notice if the field's color is changed? Anyway, I would suggest a new question with SSCCE explaining the exact steps to reproduce what you want/expect vs. what's actually happening. Make it complex enough (though still short and standalone) to demonstrate your real requirement. – kleopatra Jan 06 '14 at 18:38
  • http://stackoverflow.com/questions/20966239/how-to-notice-trap-when-user-types-invalid-value-when-jspinner-loses-focus – ManInMoon Jan 07 '14 at 07:44
2

You have a few basic choices.

  1. You could trap the exception
  2. You could check for a "empty" String

Personally, I'd like to do both...

public void warn() {
    String text = ((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getText();
    if (text != null && !text.trim().isEmpty()) {
        try {
            int stringValue = Integer.parseInt(text);
            JOptionPane.showMessageDialog(null,
                  "VALS: "+spin2.getValue(), "Error Massage",
                  JOptionPane.ERROR_MESSAGE);
            if (stringValue<10 || stringValue >100){
            JOptionPane.showMessageDialog(null,
              "Error: Please enter number bigger than 0", "Error Massage",
              JOptionPane.ERROR_MESSAGE);
            }
        } catch (NumberFormatException exp) {
            exp.printStackTrace();
        }
    }
}

Now, as a user, this is likely just to annoy me. Highlight the field, beep, change the tooltip, sure, throw a dialog in my face...hmmm...

You could take a look at Validating Input, which will allow you to validate the input when the field loses focus, which, personally, might be a better choice.

If you don't particularly need to functionality of the JSpinner (running values up and down in a sequence), you could take a look at using a DocumentFilter (for examples), which will allow you to control what goes into the field. You should know that it's not possible (or close enough to it) to add a DocumentFilter to a JSpinner... :P

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you very much. Unfortunately it never gets to warn() the error occurs in insertUpdate() or removeUpdate(). I will look at Validating Input as you suggest. BUT fxing my Spinner would be prferable. Odd I would have thought this was a basic requirement. – ManInMoon Jan 03 '14 at 13:22
  • See top EDIT 2 please – ManInMoon Jan 03 '14 at 13:50
0

The Exception says that the String you pass to Integer.parseInt(..) is an empty string. So make sure to check ((JSpinner.DefaultEditor)spin2.getEditor()).getTextField().getText() for null and empty before passing it to Integer.parseInt(..)

You may have a look at apache commons-io and the StringUtils class, they have quite a few good methods to ease the pain on checking for empty/blank strings

thuri
  • 302
  • 1
  • 2
  • 10
  • 1
    @ManInMoon, not true. This is in your stack trace of your error: com.NoviumResearch.ValueAtRisk.Sigma$7.warn(Sigma.java:626) – jzd Jan 03 '14 at 12:39
0

This proved to be a huge pain the rear. I wanted to color my text field background immediately. Ended up making an editor that overrode the editor I wanted to use, then setting it as the spinners editor accordingly. Seems to be working for me so far.

Add a java class with the following (remove the quotes around the code block, I'm having a hard time with stack overflow's editor):

`public class CustomNumberEditor extends JSpinner.NumberEditor {
    public CustomNumberEditor( JSpinner spinner ) {
        super( spinner );
        ((DefaultFormatter ) ((JFormattedTextField) getComponent( 0 )).getFormatter()).setCommitsOnValidEdit( true );
    }
    @Override public void propertyChange(PropertyChangeEvent e) {
        super.propertyChange( e );
        if( e.getPropertyName().equals( "value" ) )
            doStuff( (int) e.getNewValue() );
    }
    private void doStuff( int value )
    {
        //do stuff
    }
}`

Then when adding your spinner, do this:

_quantitySpinner.setEditor(new CustomNumberEditor(_quantitySpinner));
Evan
  • 2,441
  • 23
  • 36
  • If you wanted to get fancy, you could have register listeners to a change event here. Since I just wanted to change colors on the text field, I didn't need to go any further than this. – Evan Sep 25 '18 at 19:27
  • One more important note, this requires you to set setAllowsInvalid false on your editor. If you for some reason want to accept invalid values, I'm sure there s a second function that could be overridden in the editor, but that wasn't within the scope of what I needed to do. – Evan Sep 26 '18 at 16:56