Search code examples
javaswingjcombobox

Nulls In JComboBox Stopping Arrow Key Use


The code below has a bug. Upon loading the JFrame, press tab to focus on the JComboBox, and try pressing the down key. It doesn't do anything.

Inserting Null at position 0 causes this. However, I would still like to be able to select Null. I don't want to force the user to pick an option.

package kobalt.test.colin;

import java.awt.*;
import javax.swing.*;

public class ColinTest extends JFrame {

    private JTextField mTextField;
    private JComboBox  mComboBox;

    public ColinTest(){
      setLayout(new BorderLayout());

      mTextField = new JTextField("Something");
      mComboBox = new JComboBox(new String[]{"One", "Two"});
      mComboBox.insertItemAt(null, 0); //this line causes the bug

      add(mTextField, "North");
      add(mComboBox, "South");

      pack();
      setVisible(true);
    }


    public static void main(String[] argv) {
      new ColinTest();
    }
}

Is there something I can override in JComboBox to fix this behaviour?

I don't really fancy inserting an empty String at position 0 as I'll have to deal with that everywhere.

Using a Wrapping object may be an option, but I'd rather extend and then override something in JComboBox.


Solution

  • Null objects do not play nicely in a JComboBox. For example, the combo box's getSelectedIndex method, which is fired when you select an item, will return -1 if the object is null. There may also be other methods which perform null checks and may return incorrect results.

    But you can try overriding the getSelectedIndex so that it returns 0 instead of -1 if the object is null. Also override selectedItemChanged so that it doesn't check for nulls. The following seems to work, but there may be other methods which need to be overridden too:

    JComboBox mComboBox = new JComboBox(new String[]{"One", "Two"}){
        @Override
        public int getSelectedIndex() {
            Object sObject = dataModel.getSelectedItem();
            int i,c;
            Object obj;
    
            if(sObject==null){
                return 0;
            }
            for ( i=0,c=dataModel.getSize();i<c;i++ ) {
                obj = dataModel.getElementAt(i);
    
                 if ( obj != null && obj.equals(sObject) )
                    return i;
            }
            return -1;
        }
    
        @Override
        protected void selectedItemChanged() {
            fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
                    selectedItemReminder,
                    ItemEvent.DESELECTED));
            selectedItemReminder = dataModel.getSelectedItem();
    
            fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED,
                    selectedItemReminder,
                    ItemEvent.SELECTED));
        }
    };
    

    However, instead of doing the above, I would recommend using a wrapper object. For example:

    class StringWrapper{
        final String s;
        public StringWrapper(String s){
            this.s=s;
        }
        @Override
        public String toString() {
            return s;
        }
    }
    
    JComboBox cb = new JComboBox(new StringWrapper[]{ 
                new StringWrapper("one"),
                new StringWrapper("two"),
                new StringWrapper("three")});
    cb.insertItemAt(new StringWrapper(null), 0);