JComboBox防止弹出窗口关闭

4

我需要在下拉框中提供一些禁用的选项。所有功能都正常,除了点击禁用项后防止下拉框关闭。

这是我的代码:

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;
        }
    }
}

我的问题是,我能够获得mouseReleased事件,但无法获得mouseClicked事件。唯一的方法是使用Toolkit类为鼠标事件注册AWTEventListener来获取mouseClicked事件,但这种方法很丑陋。在此处使用setPopupVisible(true)重新显示弹出窗口的方法也很困难,因为弹出窗口中可能会有滚动窗格(真正的组合框可能有大约30个条目,因此我需要保存滚动条值以便将下拉列表恢复到相同位置)。请问有人能给我建议,如何防止组合框弹出窗口关闭?
2个回答

5
  • 这是我的建议:
    • 覆盖 JComboBox#setPopupVisible(boolean) 方法,而不是使用 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;
  }
}

太好了!甚至不再需要自定义模型。 - Sergiy Medvynskyy
1
方法 setSelectedObject 必须被重写。当用户在禁用的项目上按 Enter 键时,它被 UI 使用。我已经纠正了你的例子。再次感谢! - Sergiy Medvynskyy

1

我找到了一些简单的解决方案,可以始终保持弹出窗口打开,直到用户在弹出窗口外面点击。这对于某些自定义的JComboBox非常有用,比如我在我的项目中使用的那个,但是有点hacky。

public class MyComboBox extends JComboBox
{
    boolean select_action_performed = false; //check when user select item

    public MyComboBox(){
        setRenderer(new MyComboBoxRenderer()); //our spesial render

        addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
            //Do stuff when user select item
            select_action_performed = true;  //set the flag
            }
        });
    }

    class MyComboBoxRenderer extends BasicComboBoxRenderer {

        public Component getListCellRendererComponent(JList list, Object value, 
            int index, boolean isSelected, boolean cellHasFocus) {

            if (index == -1){ //if popup hidden
                if (select_action_performed) {
                    showPopup(); //show it again
                    select_action_performed = false; //and remove the flag
                }
                return r;
            }
        }
    }
}

当组合框尝试在弹出列表之外呈现所选元素时,index = -1。你的解决方案可能在某些情况下有效,但是之前的解决方案要好得多。 - Sergiy Medvynskyy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接