0

I have a JSpinner with a SpinnerListModel . What I want to do is, once the list is loaded in the model and the JSpiner shows in a frame, it should be possible to remove elements from the spinner. This will be done just by cliking on a button on the same frame. The handler from the click action will remove the element selected currently on the spinner. The problem, with my current implementation, is that when this handler returns, there is an IndexOutOfBoundsExeception from the spinner (that is when I remove the last element from the list), which shows that the JSpinner is not well updated.

I created a new class ExtendedSpinner which extends JSpinner, only to use fireStateChanged. This is to update the JSpinner when an element is removed. It works fine for removing an element on the middle of the list, but not for the last one.

What am I doing wrong? This is the code:

package image;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerListModel;
import javax.swing.SpinnerModel;

public class ImageDealer2 {

protected JFrame selectCoverFrame;
protected JExtendedSpinner spinnerCovers;
protected JButton deleteCoverButton;
protected SpinnerListModel spinnerCoversM;
protected ArrayList<Object> stringList = new ArrayList<Object>();

public ImageDealer2() {
selectFrameInit();
}


public void selectFrameInit(){

selectCoverFrame = new JFrame("Select");
selectCoverFrame.setSize(new Dimension(500,100));
selectCoverFrame.getContentPane().setLayout(new    BoxLayout(selectCoverFrame.getContentPane(),BoxLayout.Y_AXIS));


stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("d");

spinnerCoversM = new SpinnerListModel(stringList);
spinnerCovers = new JExtendedSpinner(spinnerCoversM);

deleteCoverButton = new JButton("Delete current element");
DeleteCurrentCoverHandler deleteCurrentCoverHandler = new DeleteCurrentCoverHandler();
deleteCoverButton.addActionListener(deleteCurrentCoverHandler);

selectCoverFrame.getContentPane().add(spinnerCovers);
selectCoverFrame.getContentPane().add(deleteCoverButton);
selectCoverFrame.setVisible(true);
}



public class JExtendedSpinner extends JSpinner{

/**
 * 
 */
private static final long serialVersionUID = 6109392800971431371L;

public JExtendedSpinner() {
    super();
    // TODO Auto-generated constructor stub
}

public JExtendedSpinner(SpinnerModel model) {
    super(model);
    // TODO Auto-generated constructor stub
}

public void fireUpdate(){
    this.fireStateChanged();
}
}    

private class DeleteCurrentCoverHandler implements ActionListener {

public void actionPerformed(ActionEvent e) {
    if (stringList.size()>1){
        stringList.remove(spinnerCovers.getValue());
        spinnerCoversM.setList(stringList);
        spinnerCovers.setModel(spinnerCoversM);
        spinnerCovers.fireUpdate();
    } else{
        stringList.clear();
        selectCoverFrame.dispose();
    }

}
}
}
Asciiom
  • 9,867
  • 7
  • 38
  • 57
  • 2
    The `SpinnerListModel` just keeps a reference to the list passed in the constructor, so it is sufficient to just remove the element from the list and fire the event. No need to call `setList` and `setModel` again. This might even solve your issue, but I am not sure of that. Note that I am not really keen on having a list which backs the model, but which you cannot alter without having to fire events. I prefer a model that can take care of itself, and fire the events itself – Robin Oct 01 '12 at 14:26
  • Thanks. I have tried first removing that 2 lines, but it didn't work. Then I tried extending the SpinnerListModel, but with the same results. I'm missing something else. – José Luis Villar Bardanca Oct 01 '12 at 21:29

2 Answers2

0

I think you are overcomplicating this, you should not need to extend the JSpinner.

When you start your program make sure you add the values before you create the JSpinner:

    //create the list   
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");
    stringList.add("d");

    //make sure you set the model to the list
    jSpinner.setModel(new javax.swing.SpinnerListModel(stringList));

You should create the event like this (Do this before anything else):

    jSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
        public void stateChanged(javax.swing.event.ChangeEvent evt) {
            stateChanged(evt);
        }
    });

The stateChanged(evt) method should look like this:

    //Get selected item/object
    Object selected = jSpinner.getValue();
    //Remove selected item/object
    stringList.remove(selected);
sorifiend
  • 5,927
  • 1
  • 28
  • 45
  • The element should be removed by an event on the Button, not on the Spinner. This way, the element would be removed every time you click on the spinner. I tried also this way with a boolean activated from the event from the button handler, but is complicated because you need to fire the spinner event some way. Still if you click on the spinner after clickin the button you will have also the same problem as I had in the first version of the code. – José Luis Villar Bardanca Oct 02 '12 at 09:11
0

You are close.
You need indeed to fire an event when adding and removing elements from the list, but this is the responsibility of the model, not the spinner. So you have to implement your own model.
The other problem is when the list gets emptied, then there is nothing for the spinner to display and that's probably why you are getting the exception.
So you have to decide what to do when the spinner gets emptied.

Let's say for example you want the spinner to get disabled when it is empty and to get back to enabled when it is not empty.
As I told you earlier, you have to implement your SpinnerListModel so as to fire manually the events for adding and removing. But now you also want the spinner to get disabled and enabled, according to the model. So you can implement the model to get the reference of the spinner...
Follows sample code:

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerListModel;

public class ImageDealer2 {
    public static class RemoveSpinnerListModel extends SpinnerListModel {
        private static final String VALUE_OF_EMPTY_LIST = ""; //This value indicates the spinner is empty.

        protected JSpinner spin; //The spinner which this model referes to.

        private boolean empty; //indicates if the list spinner is empty.
        //The spinner is empty if it contains only the VALUE_OF_EMPTY_LIST value.

        public RemoveSpinnerListModel() {
            super(new ArrayList(Arrays.asList(VALUE_OF_EMPTY_LIST))); //We initialize to an "empty" read-write list.
            empty = true; //The spinner does not contain any valid value.
        }

        public void setSpinner(final JSpinner spin) {
            this.spin = Objects.requireNonNull(spin); //Just a null check plus assignment.

            //We ensure the list has at least one element in it (the VALUE_OF_EMPTY_LIST):
            if (getList().isEmpty())
                ((List)getList()).add(VALUE_OF_EMPTY_LIST);

            //The spinner is empty if and only if the list contains only the VALUE_OF_EMPTY_LIST.
            empty = getList().size() == 1 && getList().get(0).equals(VALUE_OF_EMPTY_LIST);

            //We enable/disable the spinner accordingly:
            spin.setEnabled(!empty);

            if (empty) //If the spinner is empty of valid values:
                spin.setValue(VALUE_OF_EMPTY_LIST); //then we add the VALUE_OF_EMPTY_LIST.
        }

        public void add(final Object value) {
            if (empty) //If the spinner contains only the VALUE_OF_EMPTY_LIST, then:
                getList().clear(); //Remove the VALUE_OF_EMPTY_LIST string.
            ((List)getList()).add(value); //Add the requested value.
            if (spin != null) {
                spin.setEnabled(true); //Enable the spinner for sure, because now it has at least one valid value.
                spin.setValue(value);
            }
            empty = false; //The spinner is surely not empty.
            fireStateChanged(); //Important step: updates the spinner via firing an event in the model.
        }

        public void remove(final Object value) {
            if (!getList().isEmpty()) { //If there is something to remove.
                getList().remove(value); //Remove the requested value.
                if (getList().isEmpty()) //If now the list is empty, then we mark the spinner as empty:
                    markTheSpinnerAsEmpty();
                else //Else the list still contains valid elements, so we mark the spinner as not-empty:
                    empty = false;
            }
            else //Else, the list is empty, so nothing to remove and the spinner must be marked as empty:
                markTheSpinnerAsEmpty();
            if (spin != null)
                spin.setEnabled(!empty);
            fireStateChanged(); //Important step: updates the spinner via firing an event in the model.
        }

        public void clear() {
            getList().clear(); //Remove everything for sure.
            markTheSpinnerAsEmpty();
        }

        private void markTheSpinnerAsEmpty() {
            ((List)getList()).add(VALUE_OF_EMPTY_LIST);
            if (spin != null) {
                spin.setValue(VALUE_OF_EMPTY_LIST); //Let's show the user that nothing is here.
                spin.setEnabled(false);
            }
            empty = true;
        }
    }

    protected JFrame selectCoverFrame;
    protected JSpinner spinnerCovers;
    protected JButton deleteCoverButton;
    protected RemoveSpinnerListModel spinnerCoversM;

    public ImageDealer2() {
        selectFrameInit();
    }

    private void selectFrameInit() {

        selectCoverFrame = new JFrame("Select");
        selectCoverFrame.setSize(new Dimension(500, 100));
        selectCoverFrame.getContentPane().setLayout(new BoxLayout(selectCoverFrame.getContentPane(), BoxLayout.Y_AXIS));

        spinnerCoversM = new RemoveSpinnerListModel();
        spinnerCoversM.add("a");
        spinnerCoversM.add("b");
        spinnerCoversM.add("c");
        spinnerCoversM.add("d");

        spinnerCovers = new JSpinner(spinnerCoversM);
        spinnerCoversM.setSpinner(spinnerCovers); //DO NOT forget this step!

        deleteCoverButton = new JButton("Delete current element");
        DeleteCurrentCoverHandler deleteCurrentCoverHandler = new DeleteCurrentCoverHandler();
        deleteCoverButton.addActionListener(deleteCurrentCoverHandler);

        final JButton addButton = new JButton("Add an element");
        AddANewCoverHandler addANewCoverHandler = new AddANewCoverHandler();
        addButton.addActionListener(addANewCoverHandler);

        final JPanel buttonPanel = new JPanel(); //FlowLayout.
        buttonPanel.add(deleteCoverButton);
        buttonPanel.add(addButton);

        selectCoverFrame.getContentPane().add(spinnerCovers);
        selectCoverFrame.getContentPane().add(buttonPanel);
        selectCoverFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //With this call, when you close the frame, then the application gets terminated.
        selectCoverFrame.setLocationRelativeTo(null); //With this call, the frame gets to the center of the screen.
        selectCoverFrame.setVisible(true);
    }

    private class DeleteCurrentCoverHandler implements ActionListener {
        @Override
        public void actionPerformed(final ActionEvent e) {
            spinnerCoversM.remove(spinnerCovers.getValue());
        }
    }

    private class AddANewCoverHandler implements ActionListener {
        @Override
        public void actionPerformed(final ActionEvent e) {
            spinnerCoversM.add(JOptionPane.showInputDialog("Enter the new element string:"));
        }
    }

    public static void main(final String[] args) {
        new ImageDealer2();
    }
}

If you want to add or remove any objects from the spinner, then you have to do it via this model.
For any other "read-only" action though (such as isEmpty() and size()) you can call the getList() of the model.
Maybe this is a bigger than expected solution, but it's reusable.

gthanop
  • 3,035
  • 2
  • 10
  • 27