JSpinner Value change Events
Asked Answered
B

7

42

How to make the update immediately when the jSpinner value was changed.

ChangeListener listener = new ChangeListener() {
  public void stateChanged(ChangeEvent e) {
    jLabel.setText(e.getSource());
  }
};

spinner1.addChangeListener(listener);

The code above doesnt change the label text automatically, it required you to click again anyplace to update.

Boutin answered 16/10, 2010 at 14:56 Comment(0)
F
52

The answer is to configure the formatter used in the JFormattedTextField which is a child of the spinner's editor:

    formatter.setCommitsOnValidEdit(true);

Unfortunately, getting one's hand on it is as long and dirty as the introductory sentence:

    final JSpinner spinner = new JSpinner();
    JComponent comp = spinner.getEditor();
    JFormattedTextField field = (JFormattedTextField) comp.getComponent(0);
    DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
    formatter.setCommitsOnValidEdit(true);
    spinner.addChangeListener(new ChangeListener() {

        @Override
        public void stateChanged(ChangeEvent e) {
            LOG.info("value changed: " + spinner.getValue());
        }
    });

A slightly (but not by much) cleaner way might be to subclass NumberEditor and expose a method which allows the config

Flagon answered 28/9, 2011 at 17:37 Comment(1)
Rather than using details of getComponent, the Oracle exmaples use this sequence to retrieve the JFormattedTextField: public JFormattedTextField getTextField(JSpinner spinner) { return ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField(); } I've removed the error checking, but that's another possibility.Wheelhouse
J
12

The code you show appears correct. For reference, here is a working example.

Addendum: While the JSpinner has focus, the left and right arrow keys move the caret. The up arrow increments and the down arrow decrements the field containing the caret. The change is (effectively) simultaneous in both the spinner and the label.

To access the JFormattedTextField of the JSpinner.DateEditor, use the parent's getTextField() method. A suitable caret listener or text input listener may then be used to update the label as desired.

Addendum: Update to use setCommitsOnValidEdit, as suggested here.

import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.Calendar;
import java.util.Date;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DateEditor;
import javax.swing.SpinnerDateModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.DefaultFormatter;

/**
 * @see https://stackoverflow.com/questions/2010819
 * @see https://stackoverflow.com/questions/3949518
 */
public class JSpinnerTest extends JPanel {

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("JSpinnerTest");
                f.add(new JSpinnerTest());
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.pack();
                f.setVisible(true);
            }
        });
    }

    public JSpinnerTest() {
        super(new GridLayout(0, 1));
        final JLabel label = new JLabel();
        final JSpinner spinner = new JSpinner();
        Calendar calendar = Calendar.getInstance();
        Date initDate = calendar.getTime();
        calendar.add(Calendar.YEAR, -5);
        Date earliestDate = calendar.getTime();
        calendar.add(Calendar.YEAR, 10);
        Date latestDate = calendar.getTime();
        spinner.setModel(new SpinnerDateModel(
            initDate, earliestDate, latestDate, Calendar.MONTH));
        DateEditor editor = new JSpinner.DateEditor(spinner, "MMM yyyy");
        spinner.setEditor(editor);
        JFormattedTextField jtf = editor.getTextField();
        DefaultFormatter formatter = (DefaultFormatter) jtf.getFormatter();
        formatter.setCommitsOnValidEdit(true);
        spinner.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                JSpinner s = (JSpinner) e.getSource();
                label.setText(s.getValue().toString());
            }
        });
        label.setText(initDate.toString());
        this.add(spinner);
        this.add(label);
    }
}
Jamarjamb answered 16/10, 2010 at 15:34 Comment(6)
This is correct but not solves the problem stated above: when editing manually, stateChanged is only called after the JSpinner losses focus or if the user press the Enter key but not with each key typedLigroin
@Andrei Vajna II: Thank you for explaining your down-vote, but I will defer to user236501. I have verified the example and updated it to remove a spurious import.Jamarjamb
I think you misunderstood his problem. The key is it required you to click again anyplace to update, meaning that you have to change focus from the spinner in order to update the label. user236501 was looking for a way to update the label whenever you change something to the spinner (e.g. insert a character).Gurule
As @Ligroin example shows, any such listener must recapitulate the editor's validation. A similar DocumentListener, which uses the editor's formatter, is shown above.Jamarjamb
if I understood the question correctly, the requirement is to update the spinner model whenever the text of the formattedTextField changes. DocumentListener serves okay (though configuring the formatter is more straightforward, biased methinks :-), but then let the field to the work by calling formattedField.commitEditFlagon
That's more like it! DocumentListener doesn't work how you'd want, because the it is called too frequently. For example, it will get called even when you just click inside the spinner without changing anything. It might be ok for this problem, but not all. And KeyListeners or CaretListeners are even worse, because they don't work for pasted text.Gurule
G
3

It might be an late answer but you may use my approach.
As spuas mentioned above the problem is that stateChanged event is fired only when focus is lost or Enter key is pressed.
Using KeyListeners is not an good idea as well.
It would be better to use DocumentListener instead. I modified spuas's example a bit and that's what I got:

JSpinner spinner= new JSpinner();
spinner.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
final JTextField jtf = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
        jtf.getDocument().addDocumentListener(new DocumentListener() {              

        private volatile int value = 0;

        @Override
        public void removeUpdate(DocumentEvent e) {
            showChangedValue(e);    
        }

        @Override
        public void insertUpdate(DocumentEvent e) {
            showChangedValue(e);                
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            showChangedValue(e);    
        }

        private void showChangedValue(DocumentEvent e){
            try {
                String text = e.getDocument().getText(0, e.getDocument().getLength());
                if (text==null || text.isEmpty()) return;
                    int val = Integer.parseInt(text).getValue();
                if (value!=val){
                   System.out.println(String.format("changed  value: %d",val));             
                   value = val;
                }       
            } catch (BadLocationException | NumberFormatException e1) {
                          //handle if you want
            }        
       }
});
Gearldinegearshift answered 15/10, 2016 at 9:26 Comment(0)
L
2

Problem here is that when you edit the JSpinner value manually by typing from the keyboard, the stateChanged event is not fired until the focus is lost by the JSpinner or until Enter key has been pressed.

If you want to upload the value, a KeyListener is needed which will perform a setValue in the JSpinner for each typed key.

I leave an example here for a JSpinner with a SpinnerNumberModel:

JSpinner spinner= new JSpinner();
spinner.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));
spinner.addChangeListener(new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        jLabel.setText(spinner.getValue());
    }
});
final JTextField jtf = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
jtf.addKeyListener(new KeyAdapter() {
    @Override
    public void keyReleased(KeyEvent e) {
        String text = jtf.getText().replace(",", "");
        int oldCaretPos = jtf.getCaretPosition();
        try {
            Integer newValue = Integer.valueOf(text);
            spinner.setValue(newValue);
            jtf.setCaretPosition(oldCaretPos);
        } catch(NumberFormatException ex) {
            //Not a number in text field -> do nothing
        }
    }
});
Ligroin answered 8/6, 2011 at 9:18 Comment(1)
hey, it's the first time today (which is at its end, as far as working is concerned): dont use keylisteners - they are not good enough, because they miss text pasted into the fieldFlagon
H
1

The last answer can be rearranged a little to make it a little more flexible. You can simply use this new MyJSpinner in place of any JSpinner. The biggest change is that you can use this new version with any underlying model of the JSpinner (int, double, byte, etc.)

    public class MyJSpinner extends JSpinner{
        boolean setvalueinprogress=false;
        public MyJSpinner()
        {
            super();
            final JTextField jtf = ((JSpinner.DefaultEditor) getEditor()).getTextField();
            jtf.getDocument().addDocumentListener(new DocumentListener() {              

                    @Override
                    public void removeUpdate(DocumentEvent e) {
                        showChangedValue(e);    
                    }

                    @Override
                    public void insertUpdate(DocumentEvent e) {
                        showChangedValue(e);                
                    }

                    @Override
                    public void changedUpdate(DocumentEvent e) {
                        showChangedValue(e);    
                    }

                    private void showChangedValue(DocumentEvent e){
                        try {
                            if (!setvalueinprogress)
                                MyJSpinner.this.commitEdit();      
                        } catch (NumberFormatException | ParseException ex) {
                                      //handle if you want
                            Exceptions.printStackTrace(ex);
                        }      
                   }
            });
        }

    @Override
    public void setValue(Object value) {
        setvalueinprogress=true;
        super.setValue(value); 
        setvalueinprogress=false;
    }

 }
Heart answered 2/2, 2017 at 1:11 Comment(1)
More boilerplate but more efficient when you have three or more spinners in your GUILadoga
S
0

I resolved a similar situation by adding the requestFocus() method after the statement, like this:

spinner.addChangeListener(new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        //Do what you need
        spinner.requestFocus();
    }
}

Hoping this would be a good help. Have nice coding.

Spoilfive answered 30/4, 2023 at 12:8 Comment(0)
O
-1

I'm new so I might be breaking some rules and I might be late. But I found some of the answers a bit confusing so I played around in NetBeans IDE and found that if you right click on the jspinner GUI component placed on your jform and go to events-> change, code will be generated for you.

Oatis answered 1/8, 2016 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.