JCombobox disable item selection (make combo readonly)
Asked Answered
A

5

5

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 (-:

Assegai answered 6/5, 2014 at 16:32 Comment(6)
What is the point of doing a "readonly combobox" if you are ok with "the popup list should not open or be empty". That is a JLabel or a not editable JTextField. I thought you'd want a JComboBox where the other options are visible, but you can't change the one selected even if you click on another item of the list.Sherasherar
A JLabel has a completely different appearance then a combo. The textfield is not an option because the combo does not show the textfield. I think its strange if the user clicks on an item but nothing happens. Readonly components usually do not have a visual indicator that they are readonly. I would prefer a solution where the popup is not shown to the user in the first place.Assegai
If when you hover over an item it does not get any visual clue that is clickable (the typical background color change) then it would not be strange when nothing happens. I'd find more strange that a combobox, which is an input UI element to choose between different items does not have any item, but has "something" magically selected. Anyway there are 2 easy solutions: Override all the painting/call of the popup or just create a JComboBox with only 1 item, the one you want.Sherasherar
Is there no way to prevent the popup list to be actually opened?Assegai
On a side note, calling hidePopup() from invokeLater or rather from the EDT is not a problem, in fact the problem would be calling it from outside the EDT.Sherasherar
put there only one Item there to show, set warning or "no items are available for ...", forgot for disabling a popup, could be L&F and platform sensitive, there is possible to play with selection(moving to next), but one of them must be accesible, you can to play with JLayer(Java7) or GLassPane (doesn't protect against KeyEvents)Sofer
R
8

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.

Raskin answered 6/5, 2014 at 20:52 Comment(0)
H
1

splungebob provides the perfect solution. Here is his comments turned into code so that you can grab it and go:

private void setJComboBoxReadOnly(JComboBox jcb)
{
   JTextField jtf = (JTextField)jcb.getEditor().getEditorComponent();
   jtf.setEditable(false);

   MouseListener[] mls = jcb.getMouseListeners();
   for (MouseListener listener : mls)
      jcb.removeMouseListener(listener);

   Component[] comps = jcb.getComponents();
   for (Component c : comps)
   {
      if (c instanceof AbstractButton)
      {
         AbstractButton ab = (AbstractButton)c;
         ab.setEnabled(false);

         MouseListener[] mls2 = ab.getMouseListeners();
         for (MouseListener listener : mls2)
            ab.removeMouseListener(listener);
      }
   }
}
Hierocracy answered 2/6, 2020 at 21:12 Comment(0)
S
0

Overriding:

@Override
public void showPopup()
{
    //do nothing
}

should do the trick.

Sherasherar answered 6/5, 2014 at 17:17 Comment(0)
L
0

Whats wrong with simply disabling the JComboBox?

setEnabled(false);
Lapse answered 6/5, 2014 at 17:41 Comment(1)
because the text is colored with black and we want it to be gray textedDonnydonnybrook
D
0

I had a similar requirement. Calling setEnabled(false) gives a horrible appearance and the user can't browse the drop down. Overriding showPopup() doesn't work. Trying to listen for the menu opening and then close it via invokeLater causes the menu to flash, and again the user can't browse the menu.

In the end I did this (not saying its perfect, but it does exactly what I wanted):

import javax.swing.JComboBox;

public class ReadOnlyComboBox<E> extends JComboBox<E>
{
    private static final long serialVersionUID = 5866761337995322114L;

    public ReadOnlyComboBox()
    {
        this.setModel(new ReadOnlyComboBoxModel<E>());
    }

    public void setReadOnly(boolean readOnly)
    {
       ((ReadOnlyComboBoxModel<E>)this.getModel()).setReadOnly(readOnly);
    }
}

import javax.swing.DefaultComboBoxModel;

public class ReadOnlyComboBoxModel<E> extends DefaultComboBoxModel<E>
{
    private static final long serialVersionUID = -1923833835224513983L;
    private boolean readOnly;

    @Override
    public void setSelectedItem(Object anItem)
    {
        if(!readOnly)
            super.setSelectedItem(anItem);
    }

    public void setReadOnly(boolean readOnly)
    {
        this.readOnly = readOnly;
    }
}

You need to call setReadOnly(false) on the ReadOnlyComboBox before setting the selected item programmatically if needed, then set it back to stop the user making selections.

Do note the unchecked cast, wasn't an issue for me in my small program, but should probably override the setModel method to chuck an exception if an attempt is made to use any other kind of model.

Edit: Also note that action listeners are still called (with the unchanged selection).

Deutoplasm answered 7/2, 2016 at 17:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.