Question

I would like to create a JSpinner which can take every possible Double value between a specified minimum and a specified maximum.

Also, the JSpinner should be able to display a text instead of a specific value. Let's say our JSpinner can take values from -1 to 10. I would like to display a text, e.g. "Auto", instead of -1 .

How to replace Normal Jspinner : we can see "-1" by JSpinner where we can see "Auto" instead of "-1" even if Auto equals actually -1

Here is the Model I wrote, but it seems not to be enough, because it says in JSpinner there is an error because the text is not a Double.

public class SpinnerSpecialModel
        extends AbstractSpinnerModel implements SpinnerMinMaxModel {

  public static final double DEFAULT_MINIMUM = 0.0;
  public static final double DEFAULT_MAXIMUM = Double.POSITIVE_INFINITY;
  public static final double DEFAULT_STEP = 1.0;
  public static final double DEFAULT_VALUE = 1.0;
  public static final double DEFAULT_SPECIAL_NUMBER = -1.0;
  public static final String DEFAULT_SPECIAL_TEXT = "Auto";

  private double maximum;
  private double minimum;
  private double stepSize;
  private double currentNumber;
  private double specialNumber;
  private String specialText;

  private Object m_Value;

  public SpinnerSpecialModel(double max, double min, double step, double num, 
        double specialNum, String specialTxt) {
    maximum = max;
    minimum = min;
    stepSize = step;
    currentNumber = num;
    specialNumber = specialNum;
    specialText = specialTxt;
    setAccurateValue(num);
  }

  public SpinnerSpecialModel(double specialNum, String specialTxt) {
    this(DEFAULT_MAXIMUM, DEFAULT_MINIMUM,
        DEFAULT_STEP, DEFAULT_VALUE, specialNum, specialTxt);
  }

  public SpinnerSpecialModel() {
    this(DEFAULT_SPECIAL_NUMBER, DEFAULT_SPECIAL_TEXT);
  }

  @Override
  public Object getValue() {
    if (currentNumber == specialNumber) {
      m_Value = specialText;
    }
    else {
      m_Value = currentNumber;
    }
    return m_Value;
  }

  @Override
  public void setValue(Object value) {
    setAccurateValue(value);
  }

  private void setAccurateValue(Object value) {
    if (value instanceof Double) {
      double doubleValue = (Double) value;
      if (doubleValue != currentNumber) {
        if (doubleValue == specialNumber) {
          currentNumber = specialNumber;
          m_Value = specialText;
        }
        else if (doubleValue > maximum) {
          currentNumber = maximum;
          m_Value = maximum;
        }
        else if (doubleValue < minimum) {
          currentNumber = maximum;
          m_Value = minimum;
        }
        else {
          currentNumber = doubleValue;
          m_Value = doubleValue;
        }
        fireStateChanged();
      }
    }

    if (value instanceof String) {
      String stringValue = (String) value;
      if (stringValue.equals(specialText)) {
        this.currentNumber = specialNumber;
        this.m_Value = specialText;
        fireStateChanged();
      }
    }
  }

  @Override
  public Object getNextValue() {
    return getNewValue(+1);
  }

  @Override
  public Object getPreviousValue() {
    return getNewValue(-1);
  }

  /**
   * 
   * @param direction
   * @return 
   */
  private Object getNewValue(int direction) {
    double newValue = currentNumber + direction * stepSize;
    setAccurateValue(newValue);
    return m_Value;
  }

  @Override
  public double getMaximum() {
    return maximum;
  }

  @Override
  public double getMinimum() {
    return minimum;
  }

  @Override
  public double getStepSize() {
    return stepSize;
  }

  @Override
  public void setMaximum(double max) {
    maximum = max;
  }

  @Override
  public void setMinimum(double min) {
    minimum = min;
  }

  @Override
  public void setStepSize(double step) {
    stepSize = step;
  }
}
Was it helpful?

Solution

The best and proper way to do this is not as simple as just writing a model, but it is not very complicated. You actually need to write an Editor and a Formatter to have a true MVC spinner:

  • A class that extends JSpinner : SpecialValuesSpinner.
  • A class that implements SpinnerModel : SpecialValuesSpinnerModel
  • A class that extends DefaultEditor and implements DocumentListener : SpecialValuesSpinnerEditor
  • A class that extends NumberFormatter : SpecialValuesSpinnerFormatter

I am not going to show you the code for all classes, but here is basically what you have to do in each :

SpecialValuesSpinner :

public class SpecialValuesSpinner() extends SpinnerNumberModel {
    // in your constructor do this
    setModel(new SpecialValuesSpinnerModel(YOUR_SPECIAL_VALUES);
    setEditor(new SpecialValuesSpinnerEditor());
}

SpecialValuesSpinnerModel :

public class SpinnerSpecialValuesModel() extends JSpinner {
    // in this class you handle the fact that now, you have an
    // interval of values and a list of special values that are allowed.
    // here is what I did :
    @Override
    public Object getNextValue() {
        return incrValue(+1);
    }

    @Override
    public Object getPreviousValue() {
        return incrValue(-1);
    }

    private Object incrValue(int dir) {
        // NB : BigDecimal here because this is what I used,
        // but use what you want in your model
        BigDecimal result = null;
        BigDecimal numberBD = new BigDecimal(getNumber().toString());
        BigDecimal stepSizeBD = new BigDecimal(getStepSize().toString());
        BigDecimal dirBD = new BigDecimal(dir);
        BigDecimal nextValue = numberBD.add(stepSizeBD.multiply(dirBD));

        TreeSet<BigDecimal> currentAllowedValues = new TreeSet<BigDecimal>();
        currentAllowedValues.addAll(m_SpecialValues);
        if (getMaximum() != null) {
          currentAllowedValues.add((BigDecimal) getMaximum());
        }
        if (getMinimum() != null) {
          currentAllowedValues.add((BigDecimal) getMinimum());
        }
        if (isIncludedInBounds(nextValue)) {
          currentAllowedValues.add(nextValue);
        }

        if (dir > 0) {
          try {
            result = currentAllowedValues.higher(numberBD);
          }
          catch (NoSuchElementException e) {}
        }
        else if (dir < 0) {
          try {
            result = currentAllowedValues.lower(numberBD);
          }
          catch (NoSuchElementException e) {}
        }
        return result;
    }
}

In SpecialValuesSpinnerEditor, we use Document Listener to have autocompletion (easy to do, just search on SO).

public class SpecialValuesSpinnerEditor extends DefaultEditor implements DocumentListener {

    // You have to do in your contructor
    SpecialValuesSpinnerFormatter formatter = 
        new SpecialValuesSpinnerFormatter (spinner.getSpecialValues(), format);
    getTextField().setFormatterFactory(new DefaultFormatterFactory(formatter));
}

And now, the most important, the Formatter which does conversion between user input (string) and numbers, and handle the model's display :

public class SpecialValuesSpinnerFormatter extends NumberFormatter {
    // Just override the methos StringToValue and ValueToString.
    // You can check here if the value is special
    // i.e you must display its special text instead. e.g. : "Auto" instead of -1
}

OTHER TIPS

I think you can achieve that by implementing your own SpinnerModel and supplying that as argument to the JSpinner constructor.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top