Sergiy Medvynskyy Sergiy Medvynskyy - 1 month ago 11
Java Question

JComboBox preventing of popup closing

I need to provide some disabled items in a combobox. All works fine except preventing of combobox from closing after click on a disabled item.

Here is my code:

import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.plaf.basic.ComboPopup;


public class DisabledCombo {

public static void main(String[] args) {
final DisabledSupportComboModel model = new DisabledSupportComboModel();
model.addElement(new Item("First element"));
model.addElement(new Item("Second element"));
model.addElement(new Item("Disabled", false));
model.addElement(new Item("Fourth element"));
final JComboBox<Item> itemCombo = new JComboBox<DisabledCombo.Item>(model);
itemCombo.setRenderer(new DisabledSupportComboRenderer());
final ComboPopup popup = (ComboPopup) itemCombo.getUI().getAccessibleChild(itemCombo, 0);
final JList<?> l = popup.getList();
final MouseListener[] listeners = l.getMouseListeners();
for (final MouseListener ml : listeners) {
l.removeMouseListener(ml);
System.out.println("remove listener: " + ml);
}
System.out.println("Number of listeners: " + l.getMouseListeners().length);
l.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("Release");
final int idx = l.locationToIndex(e.getPoint());
if (idx >= 0 && l.getModel().getElementAt(idx) instanceof Item) {
final Item itm = (Item) l.getModel().getElementAt(idx);
if (!itm.isEnabled()) {
e.consume();
}
}
}
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Click");
final int idx = l.locationToIndex(e.getPoint());
if (idx >= 0 && l.getModel().getElementAt(idx) instanceof Item) {
final Item itm = (Item) l.getModel().getElementAt(idx);
if (!itm.isEnabled()) {
e.consume();
}
}
}
});
for (final MouseListener ml : listeners) {
l.addMouseListener(ml);
}
final JFrame frm = new JFrame("Combo test");
frm.add(itemCombo);
frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frm.pack();
frm.setVisible(true);
}

private static class Item {
private final Object value;
private final boolean enabled;

public Item(Object aValue) {
value = aValue;
enabled = true;
}

public Item(Object aValue, boolean isEnabled) {
value = aValue;
enabled = isEnabled;
}

public Object getValue() {
return value;
}

public boolean isEnabled() {
return enabled;
}

/**
* {@inheritDoc}
*/
@Override
public String toString() {
return null == value? null : value.toString();
}
}

private static class DisabledSupportComboModel extends DefaultComboBoxModel<Item> {
/**
* {@inheritDoc}
*/
@Override
public void setSelectedItem(Object anObject) {
if (anObject instanceof Item) {
if (((Item) anObject).isEnabled()) {
super.setSelectedItem(anObject);
}
} else {
super.setSelectedItem(anObject);
}
}
}

private static class DisabledSupportComboRenderer extends BasicComboBoxRenderer {
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof Item) {
if (((Item) value).isEnabled()) {
setForeground(isSelected? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected? list.getSelectionBackground() : list.getBackground());
} else {
setForeground(UIManager.getColor("Label.disabledForeground"));
setBackground(list.getBackground());
}
} else {
setForeground(isSelected? list.getSelectionForeground() : list.getForeground());
setBackground(isSelected? list.getSelectionBackground() : list.getBackground());
}
return this;
}
}
}


My problem is, that I get
mouseReleased
event, but no
mouseClicked
event. The only way to get
mouseClicked
event is to register
AWTEventListener
for mouse events using the
Toolkit
class. But it's realy ugly here. The approach to show the popup again using the
setPopupVisible(true)
is also difficult here due to eventually scroll pane in popup (the real combobox can have about 30 entries, so I need to save the scroll bar value to restore the drop down list at the same position). Can somebody advise me, how can I prevent the combo popup from closing?

Answer
  • Here's my attempt:
    • Override JComboBox#setPopupVisible(boolean) instead of using JList#addMouseListener(...)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class DisabledCombo2 {
  public static JComponent makeUI() {
    DisabledSupportComboModel model = new DisabledSupportComboModel();
    model.addElement(new Item("First element"));
    model.addElement(new Item("Second element"));
    model.addElement(new Item("Disabled", false));
    model.addElement(new Item("Fourth element"));

    JComboBox<Item> itemCombo = new JComboBox<Item>(model) {
      //@see http://java-swing-tips.blogspot.jp/2010/03/non-selectable-jcombobox-items.html
      private boolean isDisableIndex;
      @Override public void setPopupVisible(boolean v) {
        if (!v && isDisableIndex) {
          //Do nothing(prevent the combo popup from closing)
          isDisableIndex = false;
        } else {
          super.setPopupVisible(v);
        }
      }
      @Override public void setSelectedObject(Object o) {
        if (o instanceof Item && !((Item) o).isEnabled()) {
          isDisableIndex = true;
        } else {
          super.setSelectedObject(o);
        }
      }
      @Override public void setSelectedIndex(int index) {
        Object o = getItemAt(index);
        if (o instanceof Item && !((Item) o).isEnabled()) {
          isDisableIndex = true;
        } else {
          super.setSelectedIndex(index);
        }
      }
    };
    itemCombo.setRenderer(new DisabledSupportComboRenderer());
    return itemCombo;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame("Combo test2");
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(makeUI());
    f.pack();
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class Item {
  private final Object value;
  private final boolean enabled;

  public Item(Object aValue) {
    value = aValue;
    enabled = true;
  }
  public Item(Object aValue, boolean isEnabled) {
    value = aValue;
    enabled = isEnabled;
  }
  public Object getValue() {
    return value;
  }
  public boolean isEnabled() {
    return enabled;
  }
  @Override public String toString() {
    return null == value ? null : value.toString();
  }
}

class DisabledSupportComboModel extends DefaultComboBoxModel<Item> {
  @Override public void setSelectedItem(Object anObject) {
    if (anObject instanceof Item) {
      if (((Item) anObject).isEnabled()) {
        super.setSelectedItem(anObject);
      }
    } else {
      super.setSelectedItem(anObject);
    }
  }
}

class DisabledSupportComboRenderer extends DefaultListCellRenderer {
  @Override public Component getListCellRendererComponent(
      JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
    super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
    if (value instanceof Item) {
      if (((Item) value).isEnabled()) {
        setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
        setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
      } else {
        setForeground(UIManager.getColor("Label.disabledForeground"));
        setBackground(list.getBackground());
      }
    } else {
      setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
      setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
    }
    return this;
  }
}
Comments