I'd like to create a readonly combobox. The user should not be able to select another item from the popup list. That means that the popup list should not open or should be empty.
I see the following solutions:
Set a ComboBox model with only one item (the current selected item) so when the user clicks on the arrow button, an empty list is presented.
Add a PopupMenuListener
and in the popupMenuWillBecomeVisible
hide the menu. This is problematic: We have to call combo.hidePopup();
from within a SwingUtilities.invokeLater()
The empty model approach seems a little bit clunky. The second approach shows the popup list for a fraction of a second, short enough to be noticed. This is very ugly.
Is there a third solution?
EDIT: Implemented solution:
I implemented the suggested method from splungebob and here is my code for future reference:
private void makeComboReadonly() {
Component editorComponent = box.getEditor().getEditorComponent();
if (editorComponent instanceof JTextField) {
((JTextField) editorComponent).setEditable(false);
}
for (Component childComponent : box.getComponents()) {
if (childComponent instanceof AbstractButton) {
childComponent.setEnabled(false);
final MouseListener[] listeners = childComponent.getListeners(MouseListener.class);
for (MouseListener listener : listeners) {
childComponent.removeMouseListener(listener);
}
}
}
final MouseListener[] mouseListeners = box.getListeners(MouseListener.class);
for (MouseListener listener : mouseListeners) {
box.removeMouseListener(listener);
}
final KeyListener[] keyListeners = box.getListeners(KeyListener.class);
for (KeyListener keyListener : keyListeners) {
box.removeKeyListener(keyListener);
}
box.setFocusable(false);
//box.getActionMap().clear(); //no effect
//box.getInputMap().clear();
}
The only problem is the Key-Event Alt-Down which oppens the popup menu even if I remove all the key listeners and clear the action map. I circumvent this problem by making the combo non focusable. Not ideal but good enough (-:
This is actually a good question about one of Swing's limitations (and has bugged me for a long time).
One would need a read-only combobox when... (wait for it)... the form is currently in read-only mode. Note that input from the user elswhere may flip the form into edit mode at a moment's notice, so switching JComponents (using a JLabel for instance) would not be visually desirable, IMO. Also note that a disabled combo does not convey the same information to a user as a read-only combo would:
setEnabled(false)
-> entirely grayed out; the component cannot be interacted with; whatever data may be shown is not relevant and cannot be selected for Copy/Paste.
setReadOnly(true)
-> text component of combo is not grayed out (but the arrow is); the component cannot be interacted with; whatever data may be shown is relevant and can be selected.
Justification for this is that Swing did implement this for JTextComponents in the form of setEditable(boolean)
. Thanks guys for that, but I also need it for JComboBox, JCheckbox, JRadioButton, etc. We had to roll our own versions for this missing API.
Another Swing gaffe (IHMO) is the inconsistent API. JTextComponent.setEditable(boolean)
enforces a read-only behavior, whereas JComboBox.setEditable(boolean)
does not.
Arrrgh!!!
So, to the problem. You gotta roll up your sleeves a bit. For an editable combo:
Get the combo's editor component via combo.getEditor().getEditorComponent()
. It's a JTextField. Cast it, and call setEditable(false)
. This gives you both the functionality and appearance you want for the text portion of the combo.
Get the combo's arrow component by iterating over getComponents() of the combo. It's the only AbstractButton you'll find. Call setEnabled(false)
. This is for appearance only.
Find all of the default mouse listeners that came with the combo (which should be all of them if you didn't add any yourself) and remove them from both the combo and the arrow button.
Keep a reference to these listeners and the arrow button in case you want to switch it back to when read-only = false.
Or something like that. Your mileage may vary.
Cue kleopatra with an endorsement for SwingX, which probably has this functionality already built-in (I don't know that for sure, I'm just guessing).
Good luck.