Search code examples
javaswingcollectionsjcombobox

Need flexible Java key/value collection class for JComboBox


I have a model class that stores keys and values:

public class KeyValue {

    private Object key;
    private String value;

    KeyValue () {
    }

    KeyValue (Object key, String value) {
        this.key=key;
        this.value=value;
    }

    public Object getKey() {
        return this.key;
    }
    public void setKey(Object key) {
        this.key=key;
    }

    public String getValue() {
        return this.value;
    }
    public void setValue(String value) {
        this.value=value;
    }

    @Override
    public String toString() {
        return this.value;
    }

}

I use this class to populate a JComboBox's Model:

for (int i = 0; i < universes.length; i++) {
    ComboBox_Universes.addItem(new KeyValue(infoObject.ID,infoObject.title));
}

I would like to refactor this logic to use a Java collection class (call it KeyValueCollection) that can support two objectives:

1) the KeyValueCollection can be used to populate the JComboBox's Model. Something like:

//get a KeyValueCollection filled with data from helper class
KeyValueCollection universeCollection = Repository.getUniverseCollection();

//use this collection as the JComboBox's model
ComboBox_Universes.setModel(universeCollection);

2) I can use the KeyValueCollection to convert a key to a value:

//ID retrieve from another control
int universeID = (int)this.Table_Values.getModel().getValueAt(row, COLUMN_ID);

//convert ID to name
String universeName = universeCollection.get(universeID).getValue();

In the .NET world, I would use the KeyedCollection class for this, but I'm not very familiar with Java.

Help is greatly appreciated.


Solution

  • You can use a custom class like this one (run main function to see its behavior) :

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.TreeMap;
    
    import javax.swing.AbstractListModel;
    import javax.swing.ComboBoxModel;
    import javax.swing.DefaultListCellRenderer;
    import javax.swing.JComboBox;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JList;
    import javax.swing.JPanel;
    
    public class KeyValueComboboxModel extends AbstractListModel implements ComboBoxModel, Map<String, String> {
    
        private TreeMap<String,String> values = new TreeMap<String,String>();
    
        private Map.Entry<String, String> selectedItem = null;
    
        public Object getSelectedItem() {
            return selectedItem;
        }
    
        public void setSelectedItem(Object anItem) {
            this.selectedItem = (java.util.Map.Entry<String, String>) anItem;
            fireContentsChanged(this, -1, -1);
        }
    
        public Object getElementAt(int index) {
            List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(values.entrySet());
            return list.get(index);
        }
    
    
    
        public int getSize() {
            return values.size();
        }
    
        public void clear() {
            values.clear();
        }
    
        public boolean containsKey(Object key) {
            return values.containsKey(key);
        }
    
        public boolean containsValue(Object value) {
            return values.containsValue(value);
        }
    
        public Set<java.util.Map.Entry<String, String>> entrySet() {
            return values.entrySet();
        }
    
        public String get(Object key) {
            return values.get(key);
        }
    
        public Set<String> keySet() {
            return values.keySet();
        }
    
        public String put(String key, String value) {
            return values.put(key, value);
        }
    
        public String remove(Object key) {
            return values.remove(key);
        }
    
        public int size() {
            return values.size();
        }
    
        public Collection<String> values() {
            return values.values();
        }
    
        public boolean isEmpty() {
            return values.isEmpty();
        }
    
        public void putAll(Map<? extends String, ? extends String> m) {
            values.putAll(m);
        }
    
    
        private static String entryToString(Map.Entry<String, String> entry) {
            String str = "" + entry.getKey() + "->" + entry.getValue();
            return str;
        }
    
        public static void main(String[] args) {
    
            Map<String,String> map= new HashMap<String,String>(){{
                put("1","blue");
                put("2","red");
                put("3","white");
                put("4","black");
            }};
    
            JFrame f = new JFrame();
            f.setContentPane(new JPanel(new BorderLayout()));
    
            KeyValueComboboxModel model = new KeyValueComboboxModel();
            model.putAll(map);
    
            final JComboBox combo = new JComboBox(model);
            combo.setRenderer(new DefaultListCellRenderer(){
    
                @Override
                public Component getListCellRendererComponent(JList list, Object value, int index,
                        boolean isSelected, boolean cellHasFocus) {
                    if(value instanceof Map.Entry){
                        Map.Entry<String,String> entry = (java.util.Map.Entry<String, String>) value;
                        String str = entryToString(entry);
                        return super.getListCellRendererComponent(list, str, index, isSelected, cellHasFocus);
                    }
                    return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                }
    
            });
    
            final JLabel lab = new JLabel("Nothing selected");
    
            combo.addActionListener(new ActionListener(){
    
                public void actionPerformed(ActionEvent e) {
                    if(combo.getSelectedItem()!=null){
                        lab.setText(entryToString((java.util.Map.Entry<String, String>) combo.getSelectedItem()));
                    } else {
                        lab.setText("");
                    }
    
                }
    
            });
    
            f.getContentPane().add(combo,BorderLayout.CENTER);
            f.getContentPane().add(lab,BorderLayout.SOUTH);
    
            f.setSize(300,80);
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
    
    
        }
    
    
    }
    

    EDIT : to handle the selected item and keys, you may add these methods:

    public void setSelectedKey(String key){
        selectedItem = values.ceilingEntry(key);
        setSelectedItem(key);
    }
    
    public void setSelectedItem(String key, String value){
        values.put(key, value);
        setSelectedKey(key);
    }
    

    By default, values are ordered following the natural order of the keys (alphabetical order of the keys, here, because these are String). If you need an other ordering, add a java.util.Comparator to the TreeMap (see TreeMap documentation).