Nick
Nick

Reputation: 177

Using Multiple JComboBoxes with a Common Data Source

I want to be able to use multiple JComboBoxes with a single common data source. I want the combo boxes to be able to display either an item from that list or a blank item, and most importantly I want them to not display an item if it is currently selected by another combo box (but show it if it become unselected).

I have been trying to go about this by calling a removeDuplicates() method which should add all the currently selected items to a list, remove that from the master list, and then set that as the list for the combo boxes.

This has been giving me some interesting problems. In my program none of the options from the master list show up, even initially, though they do if I get rid of my code to remove duplicates (the initialization is tested working).

Also even though they are editable combo boxes I cannot write a selected item to them, it gets deleted as soon as I press the enter button.

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class Main extends JFrame implements ActionListener
{
    ArrayList<String> commonItemList = new ArrayList<String>(Arrays.asList("Dog", "Cat", "Fish", "Bear", "Lion"));
    ArrayList<JComboBox<Object>> comboBoxes = new ArrayList<>();

    public Main()
    {
        this.setSize(new Dimension(500, 150));
        this.setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        JComboBox<Object> comboBox1 = new JComboBox<Object>(commonItemList.toArray());
        comboBox1.setEditable(true);
        comboBox1.addActionListener(this);

        JComboBox<Object> comboBox2 = new JComboBox<Object>(commonItemList.toArray());
        comboBox2.setEditable(true);
        comboBox2.addActionListener(this);

        JComboBox<Object> comboBox3 = new JComboBox<Object>(commonItemList.toArray());
        comboBox3.setEditable(true);
        comboBox3.addActionListener(this);

        this.add(comboBox1);
        comboBoxes.add(comboBox1);
        this.add(comboBox2);
        comboBoxes.add(comboBox2);
        this.add(comboBox3);
        comboBoxes.add(comboBox3);
    }

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

    @Override
    public void actionPerformed(ActionEvent arg0)
    {
        removeDuplicates();
    }

    private void removeDuplicates()
    {
        ArrayList<String> currentlyUsedItems = new ArrayList<>();
        ArrayList<String> availableItems = commonItemList;

        // Add all currently selected items to usedItems list
        for (JComboBox<Object> comboBox : comboBoxes)
        {
            currentlyUsedItems.add((String) comboBox.getSelectedItem());
        }

        // For every string in currentlyUsedItems remove it from availableItems
        for (String string : currentlyUsedItems)
        {
            availableItems.remove(string);
        }

        // Remove all items from combobox, then add back all available Items, while disabling actionListener
        for (JComboBox<Object> comboBox : comboBoxes)
        {
            comboBox.removeActionListener(this);

            comboBox.removeAllItems();

            for (String string : availableItems)
            {
                comboBox.addItem(string);
            }

            comboBox.addActionListener(this);
        }
    }
}

Above I have posted a SSCCE, which does not have exactly the same issues as mine but it still bugged regardless, and uses the same method I used to attempt to solve my issue. Is there something I am missing or is this just a poor way to go about a solution?

Upvotes: 3

Views: 810

Answers (2)

splungebob
splungebob

Reputation: 5415

One approach would be to simply "disable" items in the combo's drop-down list (i.e., make them unselectable) if they are already selected in another combobox.

Here's one way of disabling items in a JComboBox (using Metal):

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.plaf.metal.*;

public class JComboBoxDisabledItemsDemo implements ItemListener, Runnable
{
  private static final String ITEMS[] =
    { "Black", "Blue", "Green", "Orange", "Purple", "Red", "White", "Yellow"};

  private JComboBox jCombo;
  private JCheckBox[] checkBoxes = new JCheckBox[ITEMS.length];
  private boolean[] enabledFlags = new boolean[ITEMS.length];

  public static void main(String args[])
  {
    SwingUtilities.invokeLater(new JComboBoxDisabledItemsDemo()); 
  }

  @SuppressWarnings("unchecked")
  public void run()
  {
    JPanel pnlEnablers = new JPanel(new GridLayout(0,1));
    pnlEnablers.setBorder(BorderFactory.createTitledBorder("Enabled Items"));

    for (int i = 0; i < ITEMS.length; i++)
    {
      checkBoxes[i] = new JCheckBox(ITEMS[i]);
      checkBoxes[i].setSelected(true);
      checkBoxes[i].addItemListener(this);
      enabledFlags[i] = true;
      pnlEnablers.add(checkBoxes[i]);
    }

    jCombo = new JComboBox(ITEMS);

    jCombo.setUI(new MetalComboBoxUI()
    {
      @Override
      protected ComboPopup createPopup()
      {
        ComboPopup cp = new BasicComboPopup( comboBox )
        {
          @Override
          protected void configureList()
          {
            super.configureList();
            list.setSelectionModel(new DisabledItemSelectionModel());
          }
        };
        return cp;
      }
    });
    jCombo.setRenderer(new DisabledItemListCellRenderer());

    JFrame f = new JFrame("Colors");
    Container contentPane = f.getContentPane();
    contentPane.setLayout(new BorderLayout());
    contentPane.add(pnlEnablers, BorderLayout.CENTER);
    contentPane.add(jCombo, BorderLayout.SOUTH);

    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(240, 280);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }

  public void itemStateChanged(ItemEvent event)
  {
    JCheckBox checkBox = (JCheckBox) event.getSource();
    int index = -1;
    int selectedComboIndex = jCombo.getSelectedIndex();

    for (int i = 0; i < ITEMS.length; i++)
    {
      if (ITEMS[i].equals(checkBox.getText()))
      {
        index = i;
        break;
      }
    }

    if (index != -1)
    {
      enabledFlags[index] = checkBox.isSelected();
      jCombo.repaint();

      if (index == selectedComboIndex)
      {
        jCombo.setSelectedIndex(-1);
      }
    }
  }

  public class DisabledItemListCellRenderer extends DefaultListCellRenderer
  {
    @Override
    public Component getListCellRendererComponent(JList list, Object value,
                           int index, boolean isSelected, boolean cellHasFocus)
    {
      if (index < 0 || enabledFlags[index])
      {
        return super.getListCellRendererComponent(list, value, index,
                                                  isSelected, cellHasFocus);
      }

      Component comp = super.getListCellRendererComponent(list, value, index,
                                                          false, false);
      comp.setEnabled(false);
      return comp;
    }
  }

  public class DisabledItemSelectionModel extends DefaultListSelectionModel
  {
    /**
     * No need to override addSelectionInterval(int index0, int index1)
     * since we're using SINGLE_SELECTION mode for this demo.
     */

    @Override
    public void setSelectionInterval(int index0, int index1)
    {
      if (enabledFlags[index0])
      {
        super.setSelectionInterval(index0, index0);
      }
      else
      {
        /*
         * The previously selected index is before this one,
         * so walk forward to find the next selectable item.
         */
        if (getAnchorSelectionIndex() < index0)
        {
          for (int i = index0; i < enabledFlags.length; i++)
          {
            if (enabledFlags[i])
            {
              super.setSelectionInterval(i, i);
              return;
            }
          }
        }
        /*
         * Otherwise, walk backward to find the next selectable item.
         */
        else
        {
          for (int i = index0; i >= 0; i--)
          {
            if (enabledFlags[i])
            {
              super.setSelectionInterval(i, i);
              return;
            }
          }
        }
      }
    }
  }
}

Upvotes: 2

trashgod
trashgod

Reputation: 205785

As shown here, operate on the models rather than the views. In this case, the disparate models can share access to a common data source that knows about the models.

Upvotes: 3

Related Questions