JSpinner динамически обновляется, как удалить элементы?

У меня есть JSpinner с SpinnerListModel. Что я хочу сделать, так это то, что после загрузки списка в модель и отображения JSpiner во фрейме должна быть возможность удалить элементы из счетчика. Это можно сделать, просто нажав кнопку в том же кадре. Обработчик действия щелчка удалит элемент, выбранный в данный момент на счетчике. Проблема с моей текущей реализацией заключается в том, что когда этот обработчик возвращается, возникает IndexOutOfBoundsExeception от счетчика (то есть когда я удаляю последний элемент из списка), который показывает, что JSpinner не хорошо обновился.

Я создал новый класс ExtendedSpinner, который расширяет JSpinner только для использования fireStateChanged. Это необходимо для обновления JSpinner при удалении элемента. Он отлично работает для удаления элемента в середине списка, но не для последнего.

Что я делаю не так? Это код:

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();
    }

}
}
}

person José Luis Villar Bardanca    schedule 01.10.2012    source источник
comment
SpinnerListModel просто хранит ссылку на список, переданный в конструкторе, поэтому достаточно просто удалить элемент из списка и запустить событие. Нет необходимости снова вызывать setList и setModel. Это может даже решить вашу проблему, но я не уверен в этом. Обратите внимание, что мне не очень нравится иметь список, который поддерживает модель, но который вы не можете изменить, не запуская события. Я предпочитаю модель, которая может сама о себе позаботиться и сама запускать события.   -  person Robin    schedule 01.10.2012
comment
Спасибо. Я попытался сначала удалить эти 2 строки, но это не сработало. Затем я попытался расширить SpinnerListModel, но с теми же результатами. Я упускаю что-то еще.   -  person José Luis Villar Bardanca    schedule 02.10.2012


Ответы (2)


Я думаю, вы слишком усложняете это, вам не нужно расширять JSpinner.

Когда вы запускаете свою программу, убедитесь, что вы добавили значения перед созданием 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));

Вы должны создать такое событие (сделайте это перед чем-либо еще):

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

Метод stateChanged(evt) должен выглядеть так:

    //Get selected item/object
    Object selected = jSpinner.getValue();
    //Remove selected item/object
    stringList.remove(selected);
person sorifiend    schedule 02.10.2012
comment
Элемент должен быть удален событием на кнопке, а не на счетчике. Таким образом, элемент будет удаляться каждый раз, когда вы нажимаете на счетчик. Я также пробовал этот способ с логическим значением, активируемым из события из обработчика кнопки, но это сложно, потому что вам нужно каким-то образом запустить событие счетчика. Тем не менее, если вы нажмете на счетчик после нажатия кнопки, у вас будет такая же проблема, как и у меня в первой версии кода. - person José Luis Villar Bardanca; 02.10.2012

Вы близки.
Вам действительно нужно запускать событие при добавлении и удалении элементов из списка, но за это отвечает модель, а не счетчик. Поэтому вам нужно реализовать свою собственную модель.
Другая проблема заключается в том, что когда список опустошается, счетчику нечего отображать, и, вероятно, поэтому вы получаете исключение.
Итак, вам нужно решить, что делать, когда спиннер опустеет.

Скажем, например, вы хотите, чтобы счетчик отключался, когда он пуст, и снова включался, когда он не пуст.
Как я уже говорил вам ранее, вы должны реализовать свой SpinnerListModel, чтобы вручную запускать события для добавление и удаление. Но теперь вы также хотите, чтобы счетчик отключался и включался в соответствии с моделью. Таким образом, вы можете реализовать модель для получения ссылки на счетчик...
Ниже приведен пример кода:

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();
    }
}

Если вы хотите добавить или удалить какие-либо объекты из счетчика, вы должны сделать это с помощью этой модели.
Однако для любого другого действия «только для чтения» (например, isEmpty() и size()) вы можете вызвать getList() из модель.
Возможно, это решение больше, чем ожидалось, но его можно использовать повторно.

person gthanop    schedule 04.04.2018