Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

jcombobox keyboard navigation

843807Sep 20 2010 — edited Sep 21 2010
I have a combo box of about 1400 items, so I need for the user to be able to type a letter and have the combo box jump to that place in the list. I know how to listen to these events and such, but is there any way to set the selected item in the list without actually closing the list? Everything I try to do just closes the list.

Comments

camickr
This is the default behaviour.

The KeySelectionManager provides this functionality.
843807
Thanks for responding. I'm sorry, I didn't completely clarify, I want to be able to type a word and have that selection highlighted, but not close the combo box. Currently the combo box closes when I press any key. I'm not sure if this matters, but the combo is in a table, as part of a DefaultCellEditor
843807
DefaultCellEditor is rather hostile to keyboard-oriented users. It should never be used without overriding the isCellEditable(EventObject). For JComboBox it is even worse as you have found out.

See comments in code for ComboBoxEditor for an explanation of the various "fixes".
import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.table.*;

class ComboBoxEditor extends DefaultCellEditor {
	
	ComboBoxEditor(final JComboBox comboBox) {
		super(comboBox);
		comboBox.removeActionListener(delegate);
		delegate = new EditorDelegate() {
			
			// No change from super
			@Override
			public void setValue(Object value) {
				comboBox.setSelectedItem(value);
			}

			// No change from super
			@Override
			public Object getCellEditorValue() {
				return comboBox.getSelectedItem();
			}

			// No change from super
			@Override
			public boolean shouldSelectCell(EventObject anEvent) { 
				if (anEvent instanceof MouseEvent) { 
					MouseEvent e = (MouseEvent)anEvent;
					return e.getID() != MouseEvent.MOUSE_DRAGGED;
				}
				return true;
			}
			
			// No change from super
			@Override
			public boolean stopCellEditing() {
				if (comboBox.isEditable()) {
					// Commit edited value.
					comboBox.actionPerformed(new ActionEvent(
							ComboBoxEditor.this, 0, ""));
				}
				return super.stopCellEditing();
			}

			// JComboBox sends action events when a key is pressed
			// that changes the selected item via the JList's getNextMatch
			// method. Why? Probably a bug.. workaround filters all
			// KeyEvents except when the key code is VK_ENTER
			@Override
			public void actionPerformed(ActionEvent e) {
				if (EventQueue.getCurrentEvent() instanceof KeyEvent) {
					KeyEvent ke = (KeyEvent)EventQueue.getCurrentEvent();
					if (ke.getKeyCode() != KeyEvent.VK_ENTER)
						return;
				}
				super.actionPerformed(e);
			}
			
		};
		comboBox.addActionListener(delegate);
	}

	// DefaultCellEditor returns true for any and all KeyEvents which
	// is absolutely atrocious. This method returns false for KeyEvents
	// with an undefined character (e.g F1) and when one of Alt, Ctrl,
	// or Meta is pressed.
	@Override
	public boolean isCellEditable(EventObject anEvent) {
		if (anEvent instanceof KeyEvent) {
			KeyEvent e = (KeyEvent)anEvent;
			return e.getKeyChar() != KeyEvent.CHAR_UNDEFINED &&
				(e.getModifiersEx() & (InputEvent.ALT_DOWN_MASK
					| InputEvent.CTRL_DOWN_MASK	| InputEvent.META_DOWN_MASK)) == 0;
		}
		return super.isCellEditable(anEvent);
	}
	
	// Popup doesn't become visible if the editing was triggered
	// by a KeyEvent - not sure why. It also wont be visible
	// if this editor was already active when another row is clicked.
	// The runnable within this method ensures that the popup is made visible.
	@Override
	public Component getTableCellEditorComponent(final JTable table,
			Object value, boolean isSelected, int row, int column) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				if (table.getCellEditor() != ComboBoxEditor.this)
					return;
				JComboBox combo = (JComboBox)getComponent();
				if (!combo.isPopupVisible()) {
					combo.showPopup();
					combo.requestFocusInWindow();
				}
			}
		});
		return super.getTableCellEditorComponent(table, value, isSelected, row, column);
	}
}

public class ComboBoxTableTest implements Runnable {
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new ComboBoxTableTest());
	}
	
	public void run() {
		
		String[] array = new String[100];
		for (int i=array.length; --i>=0;)
			array[i] = createString();
		Arrays.sort(array);
		
		JTable table = new JTable(new DefaultTableModel(100, 2));
		table.setRowHeight(20);
		table.getColumnModel().getColumn(0).setCellEditor(
				new ComboBoxEditor(new JComboBox(array)));
		
		JFrame frame = new JFrame(getClass().getSimpleName());
		frame.add(new JScrollPane(table));
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	Random random = new Random();
	
	private String createString() {
		char[] c = new char[random.nextInt(5)+4];
		for (int i=c.length; --i>=0;)
			c[i] = (char)('a'+random.nextInt(26));
		return new String(c);
	}

}
843807
Thanks so much for that info, the other issues you pointed out (like it editing but not popping up) were bothering me as well.
843807
Is there any way to still have keyboard navigation if I'm using an html string? For example, if you put this in your test:
	private String createString() {
		char[] c = new char[random.nextInt(5)+4];
		for (int i=c.length; --i>=0;)
			c[i] = (char)('a'+random.nextInt(26));
		return "<html>" + (new String(c)) + " (<i>test</i>)";
	}
i can no longer press keys to jump to spots in combo box

Edit: I've tried setting the KeyboardSelectionManager, where I put
v = elem.toString().toLowerCase();
v = v.substring(v.indexOf('>')+1);
in the relevant places, but it doesn't work, it seems to completely ignore that keyboard selection manager and uses it's old one.

Edited by: toucansam on Sep 21, 2010 5:28 PM

Edit 2: Ok I got that working. Looks like the default keyboard manager isn't what's normally used, because normall you can type a whole word and it will select, where the default one only goes to the first character. But i don't think that functionality will be hard to duplicate.

Edited by: toucansam on Sep 21, 2010 6:42 PM
1 - 5
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Oct 19 2010
Added on Sep 20 2010
5 comments
1,015 views