Summary

JMenuJPopupMenuJMenuItemではなくスクロールや選択が可能なJListを使用します。

Source Code Examples

Font[] fonts = GraphicsEnvironment
    .getLocalGraphicsEnvironment()
    .getAllFonts();
DefaultListModel<String> model = new DefaultListModel<>();
Stream.of(fonts)
    .map(Font::getFontName)
    .sorted()
    .forEach(model::addElement);
JList<String> list = new PopupList<>(model);
JScrollPane scroll = new JScrollPane(list);
scroll.setBorder(BorderFactory.createEmptyBorder());
scroll.setViewportBorder(BorderFactory.createEmptyBorder());
JMenu subMenu = new JMenu("Font list");
subMenu.add(scroll);
// ...
class PopupList<E> extends JList<E> {
  private transient PopupListMouseListener listener;

  protected PopupList(ListModel<E> model) {
    super(model);
  }

  @Override public void updateUI() {
    removeMouseListener(listener);
    removeMouseMotionListener(listener);
    super.updateUI();
    setFixedCellHeight(20);
    setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    listener = new PopupListMouseListener();
    addMouseListener(listener);
    addMouseMotionListener(listener);
    Color selectedBg = new Color(0x91_C9_F7);
    ListCellRenderer<? super E> renderer = getCellRenderer();
    setCellRenderer((list, value, index, isSelected, cellHasFocus) -> {
      Component c = renderer.getListCellRendererComponent(
          list, value, index, isSelected, cellHasFocus);
      if (c instanceof JComponent && listener.isRolloverIndex(index)) {
        c.setBackground(selectedBg);
        c.setForeground(Color.WHITE);
      }
      return c;
    });
  }
}
View in GitHub: Java, Kotlin

Explanation

  • JMenu#add(...)JMenuが使用するJPopupMenuの末尾にJMenuItemではなくJScrollPaneでスクロール可能にしたJListを配置
  • JPopupMenuに配置されたJListはスクロール可能だが、マウスカーソルでのロールオーバーやマウスクリックでの選択が不可になるので、以下のようなMouseAdapterを追加して対応
    • mouseMoved(...)が実行されたらロールオーバー表示するリストアイテムのインデックスを記憶し、JListに設定したListCellRendererで背景色などを変更
    • mouseClicked(...)が実行されたらMenuSelectionManager.defaultManager().clearSelectedPath();を実行することですべてのメニューを閉じる
class PopupListMouseListener extends MouseAdapter {
  private int index = -1;

  public boolean isRolloverIndex(int i) {
    return this.index == i;
  }

  private void setRollover(MouseEvent e) {
    Point pt = e.getPoint();
    Component c = e.getComponent();
    if (c instanceof JList) {
      index = ((JList<?>) c).locationToIndex(pt);
      c.repaint();
    }
  }

  @Override public void mouseMoved(MouseEvent e) {
    setRollover(e);
  }

  @Override public void mouseDragged(MouseEvent e) {
    setRollover(e);
  }

  @Override public void mouseClicked(MouseEvent e) {
    MenuSelectionManager.defaultManager().clearSelectedPath();
  }

  @Override public void mouseExited(MouseEvent e) {
    index = -1;
    e.getComponent().repaint();
  }
}

Reference

Comment