Using a ListModel to shadow TreeSet

604 views Asked by At

My end goal is to have two JLists between which a user can move elements back and forth. I'm using a TreeSet so that elements are inserted alphabetically. Here's a visual representation:

enter image description here

Below is my code thus far, which in an SSCCE. It is mostly working, but I sometimes get an ArrayOutOfBoundsException. The problem has to do with the program thinking more elements are selected than really are. One way to recreate the problem is by selecting two elements on the left and then pressing the right button twice.

import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import javax.swing.*;

public class GUI extends JFrame {

    private GridBagLayout gridBag = new GridBagLayout();
    private static final String[] ALL_STRINGS = { "B", "A", "C" };

    private JButton leftButton = new JButton("<");
    private JButton rightButton = new JButton(">");
    private StringListModel listModel = new StringListModel(Arrays.asList(ALL_STRINGS));
    private StringListModel queueModel = new StringListModel();
    private JList<String> list = new JList<String>(listModel);
    private JList<String> queue = new JList<String>(queueModel);

    public GUI() {
        setWindowProperties();
        addComponents();
    }

    private void setWindowProperties() {
        setLayout(gridBag);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(new Dimension(300, 200));
        setTitle("JList, ListModel, and TreeSet");
        setResizable(false);
        setLocationRelativeTo(null);
    }

    private void addComponents() {
        leftButton.addActionListener(new QueueListener());
        rightButton.addActionListener(new QueueListener());

        JScrollPane listScroll = new JScrollPane(list);
        JScrollPane queueScroll = new JScrollPane(queue);

        Dimension scrollSize = new Dimension(50, 100);

        listScroll.setPreferredSize(scrollSize);
        queueScroll.setPreferredSize(scrollSize);

        add(listScroll);
        add(leftButton);
        add(rightButton);
        add(queueScroll);
    }

    private class QueueListener implements ActionListener {

        private JButton button;

        @Override
        public void actionPerformed(ActionEvent e) {
            button = (JButton) e.getSource();

            if (button.equals(leftButton)) {
                removeFromQueue();
            } else if (button.equals(rightButton)) {
                addToQueue();
            }
        }

        private void removeFromQueue() {
            List<String> Strings = queue.getSelectedValuesList();

            queueModel.removeAll(Strings);
            listModel.addAll(Strings);
        }

        private void addToQueue() {
            List<String> Strings = list.getSelectedValuesList();

            listModel.removeAll(Strings);
            queueModel.addAll(Strings);
        }
    }

    private class StringListModel extends DefaultListModel<String> {
        private TreeSet<String> model = new TreeSet<String>();

        public StringListModel() {
        }

        public StringListModel(List<String> Strings) {
            addAll(Strings);
        }

        public int getSize() {
            return model.size();
        }

        public String getElementAt(int index) {
            return (String) model.toArray()[index];
        }

        public void add(String String) {
            if (model.add(String)) {
                fireContentsChanged(this, 0, getSize());
            }
        }

        public void addAll(List<String> quets) {
            for (String String : quets) {
                model.add(String);
            }

            fireContentsChanged(this, 0, getSize());
        }

        public void clear() {
            model.clear();
            fireContentsChanged(this, 0, getSize());
        }

        public boolean contains(Object element) {
            return model.contains(element);
        }

        public String firstElement() {
            return model.first();
        }

        public Iterator iterator() {
            return model.iterator();
        }

        public String lastElement() {
            return model.last();
        }

        public void removeAll(Collection<?> elements) {
            for (Object element : elements) {
                removeElement(element);
            }

            fireContentsChanged(this, 0, getSize());
        }

        public boolean removeElement(Object element) {
            boolean removed = model.remove(element);
            if (removed) {
                fireContentsChanged(this, 0, getSize());
            }

            return removed;
        }
    }

    public static void main(String[] args) {
        new GUI().setVisible(true);
    }
}
2

There are 2 answers

4
ditkin On BEST ANSWER

If you modify your actionPerformed to clear the model's selection you will no longer have the ArrayOutOfBoundsException:

    @Override
    public void actionPerformed(ActionEvent e) {
        button = (JButton) e.getSource();

        if (button.equals(leftButton)) {
            removeFromQueue();
            **queue.getSelectionModel().clearSelection();**
        } else if (button.equals(rightButton)) {
            addToQueue();
            **list.getSelectionModel().clearSelection();**
        }
    }
0
Robin On

This implementation seems to work. Note that I changed the TreeSet to a simple List. Drawback is that the insertion/removal will be a bit slower, but at least I can fire the correct events. Seeing as it is for a UI widget, I think this is acceptable as the underlying model will never be gigantic (or the UI would become unusable).

I extended from AbstractListModel but you could also extend from DefaultListModel. In that case you would probably want to ensure that the addElement method calculates the correct index and calls super.add( calculatedIndex, element)

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import javax.swing.*;

public class GUI extends JFrame {

  private GridBagLayout gridBag = new GridBagLayout();
  private static final String[] ALL_STRINGS = {"B", "A", "C"};

  private JButton leftButton = new JButton( "<" );
  private JButton rightButton = new JButton( ">" );
  private StringListModel listModel = new StringListModel( Arrays.asList( ALL_STRINGS ) );
  private StringListModel queueModel = new StringListModel();
  private JList<String> list = new JList<String>( listModel );
  private JList<String> queue = new JList<String>( queueModel );

  public GUI() {
    setWindowProperties();
    addComponents();
  }

  private void setWindowProperties() {
    setLayout( gridBag );
    setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    setSize( new Dimension( 300, 200 ) );
    setTitle( "JList, ListModel, and TreeSet" );
    setResizable( false );
    setLocationRelativeTo( null );
  }

  private void addComponents() {
    leftButton.addActionListener( new QueueListener() );
    rightButton.addActionListener( new QueueListener() );

    JScrollPane listScroll = new JScrollPane( list );
    JScrollPane queueScroll = new JScrollPane( queue );

    Dimension scrollSize = new Dimension( 50, 100 );

    listScroll.setPreferredSize( scrollSize );
    queueScroll.setPreferredSize( scrollSize );

    add( listScroll );
    add( leftButton );
    add( rightButton );
    add( queueScroll );
  }

  private class QueueListener implements ActionListener {

    private JButton button;

    @Override
    public void actionPerformed( ActionEvent e ) {
      button = ( JButton ) e.getSource();

      if ( button.equals( leftButton ) ) {
        removeFromQueue();
      }
      else if ( button.equals( rightButton ) ) {
        addToQueue();
      }
    }

    private void removeFromQueue() {
      List<String> Strings = queue.getSelectedValuesList();

      queueModel.removeAll( Strings );
      listModel.addAll( Strings );
    }

    private void addToQueue() {
      List<String> Strings = list.getSelectedValuesList();

      listModel.removeAll( Strings );
      queueModel.addAll( Strings );
    }
  }

  private class StringListModel extends AbstractListModel<String> {
    private List<String> model = new ArrayList<>();

    public StringListModel() {
    }

    public StringListModel( List<String> strings ) {
      addAll( strings );
    }

    @Override
    public int getSize() {
      return model.size();
    }

    @Override
    public String getElementAt( int index ) {
      return model.toArray( new String[model.size()])[index];
    }

    public void addAll( Collection<String> strings ){
      for ( String string : strings ) {
        add( string );
      }
    }
    public void add( String string ){
      int index = calculateIndex( string );
      model.add( index, string );
      fireIntervalAdded( this, index, index );
    }

    public void remove( String string ){
      int index = model.indexOf( string );
      if ( index != -1 ){
        model.remove( index );
        fireIntervalRemoved( this, index, index );
      }
    }

    public void removeAll( Collection<String> strings ){
      for ( String next : strings ) {
        remove( next );
      }
    }
    private int calculateIndex( String input ){
      if ( model.size() == 0 ){
        return 0;
      }
      int index = 0;
      while ( model.get( index ).compareTo( input ) <=0 ){
        index++;
        if ( index == model.size() ){
          return index;
        }
      }
      return index;
    }
  }

  public static void main( String[] args ) {
    EventQueue.invokeLater( new Runnable() {
      @Override
      public void run() {
        new GUI().setVisible( true );
      }
    } );

  }
}