Question

Greetings to all of this gr8 StackOverflow community. This is my first question after trying to solve it myself and thus is kinda long... :-)

I'm having problem with getting the minimum specified value of JSpinner using the down arrow key. It accepts the value if typed in the text field. Everything else works fine. The strange part of this is, that this occurs in only some combination of Spinner models, specifically new SpinnerNumberModel(29.6, 29.6, 118.1, 0.1) and new SpinnerNumberModel(2.00, 2.00, 6.00, 0.01). Below see the code of 2 JSpinners that have this problem. I'm not sure why these models create the problems and if there are more models that have this problem.

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

public class JSpinnerBug {

    private JFrame frame;
    private JSpinner spinner;
    private JSpinner spinner_1;
    private JButton btnChangeValue_1;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JSpinnerBug window = new JSpinnerBug();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public JSpinnerBug() {
        initialize();
    }

    /**
     * Initialize the contents of the frame.
     */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GridBagLayout gridBagLayout = new GridBagLayout();
        gridBagLayout.columnWidths = new int[]{119, 88, 105, 0};
        gridBagLayout.rowHeights = new int[]{25, 0, 0};
        gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
        gridBagLayout.rowWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
        frame.getContentPane().setLayout(gridBagLayout);

        JButton btnChangeValue = new JButton("Change Value");
        btnChangeValue.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                spinner.setValue(2.00);
            }
        });

        spinner = new JSpinner();
        spinner.setModel(new SpinnerNumberModel(2.01, 2.00, 6.00, 0.01));
        GridBagConstraints gbc_spinner = new GridBagConstraints();
        gbc_spinner.fill = GridBagConstraints.HORIZONTAL;
        gbc_spinner.insets = new Insets(0, 0, 5, 5);
        gbc_spinner.gridx = 1;
        gbc_spinner.gridy = 0;
        frame.getContentPane().add(spinner, gbc_spinner);
        GridBagConstraints gbc_btnChangeValue = new GridBagConstraints();
        gbc_btnChangeValue.insets = new Insets(0, 0, 5, 0);
        gbc_btnChangeValue.anchor = GridBagConstraints.NORTHWEST;
        gbc_btnChangeValue.gridx = 2;
        gbc_btnChangeValue.gridy = 0;
        frame.getContentPane().add(btnChangeValue, gbc_btnChangeValue);

        spinner_1 = new JSpinner();
        spinner_1.setModel(new SpinnerNumberModel(29.7, 29.6, 118.1, 0.1));
        GridBagConstraints gbc_spinner_1 = new GridBagConstraints();
        gbc_spinner_1.fill = GridBagConstraints.HORIZONTAL;
        gbc_spinner_1.insets = new Insets(0, 0, 0, 5);
        gbc_spinner_1.gridx = 1;
        gbc_spinner_1.gridy = 1;
        frame.getContentPane().add(spinner_1, gbc_spinner_1);

        btnChangeValue_1 = new JButton("Change Value");
        btnChangeValue_1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                spinner_1.setValue(29.6);
            }
        });
        GridBagConstraints gbc_btnChangeValue_1 = new GridBagConstraints();
        gbc_btnChangeValue_1.gridx = 2;
        gbc_btnChangeValue_1.gridy = 1;
        frame.getContentPane().add(btnChangeValue_1, gbc_btnChangeValue_1);
    }

/**
 * Custom Spinner to detect button click on 
 * JSpinner. This class fixes a bug for 
 * JSpinner like for example for minimum value = 2.00 
 * and step size 0.01
 * 
 * @author dARKpRINCE
 *
 */
class CustomSpinner extends JSpinner{

        @Override
        public Object getPreviousValue() {
            // Circumvent JSpinner bug
            NumberEditor spinnerEditor = (NumberEditor) this.getEditor();
            SpinnerNumberModel model = spinnerEditor.getModel();
            if(model.getMinimum().equals(2.00) && model.getStepSize().equals(0.01)){
                if ((Double) getValue() == 2.01) {
                    return 2.00;
                }
                else {
                    return super.getPreviousValue();
                }
            }
            else if(model.getMinimum().equals(29.6) && model.getStepSize().equals(0.1)){
                if ((Double) getValue() == 29.7) {
                    return 29.6;
                }
                else {
                    return super.getPreviousValue();
                }
            }
            else {
                return super.getPreviousValue();
            }
        }
    }    

}

Feel free to try with other models like new SpinnerNumberModel(55.3, 55.3, 118.1, 0.1). With that it works.

My solution was to extend the JSpinner (given at the end of the code example) and attend to each model that creates this problem, which is not optimal. Thus each JSpinner was replaced by CustomSpinner.

I also checked the source code of JSpinner but was not able to determine/fix the problem. Can anyone tell me if this is a bug and how to optimally fix it? Using a third-party library for JSpinner would be my last resort.

Was it helpful?

Solution

BigDecimal#compareTo(...) in Double#compareTo(...) out:

import java.awt.*;
import java.math.*;
import javax.swing.*;
public class JSpinnerBug2 {
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        try {
          JFrame frame = new JFrame();
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.getContentPane().add(new JSpinnerBug2().makeUI());
          frame.setSize(320, 240);
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }
  public JComponent makeUI() {
    Box box = Box.createVerticalBox();
    box.add(new JSpinner(new SpinnerNumberModel(2.01, 2.00, 6.00, 0.01)));
    box.add(Box.createVerticalStrut(10));
    box.add(new JSpinner(new SpinnerNumberModel(29.7f, 29.6f, 111.8f, 0.1f)));
    box.add(Box.createVerticalStrut(10));
    box.add(new JSeparator());

    //TEST:
    double d = 29.7 - 29.6 - 0.1;
    System.out.format("(%f-%f-%f>=0): %b%n", 29.7, 29.6, 0.1, d>=0);
    System.out.format("(abs(%f-%f-%f)<1.0e-14): %b%n", 29.7, 29.6, 0.1, Math.abs(d)<1.0e-14);
    System.out.format("(abs(%f-%f-%f)<1.0e-15): %b%n", 29.7, 29.6, 0.1, Math.abs(d)<1.0e-15);

    box.add(new JSpinner(new SpinnerNumberModel(2.01f, 2.00f, 6.00f, 0.01f)));
    box.add(Box.createVerticalStrut(10));
    box.add(new JSpinner(new SpinnerNumberModel(29.7, 29.6, 111.8, 0.1) {
      @Override public Object getPreviousValue() {
        Number v = getNumber();
        BigDecimal value    = new BigDecimal(v.toString());
        BigDecimal stepSize = new BigDecimal(getStepSize().toString());
        BigDecimal maximum  = new BigDecimal(getMaximum().toString());
        BigDecimal minimum  = new BigDecimal(getMinimum().toString());
        BigDecimal newValue;
        if (v instanceof Double) {
          newValue = value.subtract(stepSize);
        } else {
          return super.getPreviousValue();
        }
        if ((maximum != null) && (maximum.compareTo(newValue) < 0)) {
          return null;
        }
        if ((minimum != null) && (minimum.compareTo(newValue) > 0)) {
          return null;
        } else {
          return newValue;
        }
      }
    }));
    box.add(Box.createVerticalGlue());
    box.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
    return box;
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top