概要

JComboBoxのドロップダウンリストに配置されるJListのセルレイアウト方法をニュースペーパー・スタイルに変更します。

サンプルコード

private static JComboBox<Icon> makeComboBox1(ComboBoxModel<Icon> model, Icon proto) {
  return new JComboBox<Icon>(model) {
    @Override public Dimension getPreferredSize() {
      Insets i = getInsets();
      int w = proto.getIconWidth();
      int h = proto.getIconHeight();
      return new Dimension(w * 3 + i.left + i.right, h + i.top + i.bottom);
    }

    @Override public void updateUI() {
      super.updateUI();
      setMaximumRowCount(3);
      setPrototypeDisplayValue(proto);

      ComboPopup popup = (ComboPopup) getAccessibleContext().getAccessibleChild(0);
      JList<?> list = popup.getList();
      list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
      list.setVisibleRowCount(3);
      list.setFixedCellWidth(proto.getIconWidth());
      list.setFixedCellHeight(proto.getIconHeight());
    }
  };
}
View in GitHub: Java, Kotlin

解説

  • PreferredSize
    • JComboBox#getPreferredSize()をオーバーライドしてJComboBoxの幅をセルサイズの3倍になるよう変更
    • ComboPopup#getList()でドロップダウンリストに配置されるJListを取得してセルの配置方法をHORIZONTAL_WRAPに変更
      • JListの下側に余計な余白が生成されてしまう?
    • JComboBox#setMaximumRowCount(3)JList#setVisibleRowCount(3)3行のセルを表示するよう変更
      • 片方だけ設定するとスクロールバーが表示されたり、余計な余白ができる場合がある
    • JComboBox#setPrototypeDisplayValue(...)JList#setFixedCellWidth(...)JList#setFixedCellHeight(...)でセルサイズを変更
      • JComboBox#setPrototypeDisplayValue(...)のみ指定する場合セルの右に余分な余白が生成される?
  • PopupMenuListener
    • JComboBox#getPreferredSize()をオーバーライドしてJComboBoxの幅をセルサイズと矢印ボタンの合計になるよう変更
      • このサンプルで使用している矢印ボタンの幅は20px固定
    • PopupMenuListenerを追加してドロップダウンリストを開く直前だけJComboBoxの幅をセルサイズの3倍になるよう変更
    • JListの下側に余計な余白が生成されてしまうことを防ぐため以下のようにListCellRendererを変更
      • デフォルトセルレンダラーのBorder余白の合計分((top:1 + bottom:1) * 行:3 = 6px)だけ高くなることが原因だった
private static JComboBox<Icon> makeComboBox2(ComboBoxModel<Icon> model, Icon proto) {
  JComboBox<Icon> combo = new JComboBox<Icon>(model) {
    @Override public Dimension getPreferredSize() {
      Insets i = getInsets();
      int w = proto.getIconWidth();
      int h = proto.getIconHeight();
      return new Dimension(20 + w + i.left + i.right, h + i.top + i.bottom);
    }

    @Override public void updateUI() {
      setRenderer(null);
      super.updateUI();
      setMaximumRowCount(3);
      setPrototypeDisplayValue(proto);
      ListCellRenderer<? super Icon> r = getRenderer();
      setRenderer((list, value, index, isSelected, cellHasFocus) -> {
        Component c = r.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        if (c instanceof JLabel) {
          JLabel l = (JLabel) c;
          l.setIcon(value);
          l.setBorder(BorderFactory.createEmptyBorder());
        }
        return c;
      });

      ComboPopup popup = (ComboPopup) getAccessibleContext().getAccessibleChild(0);
      JList<?> list = popup.getList();
      list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
      list.setVisibleRowCount(3);
      list.setFixedCellWidth(proto.getIconWidth());
      list.setFixedCellHeight(proto.getIconHeight());
    }
  };
  combo.addPopupMenuListener(new PopupMenuListener() {
    private boolean adjusting;

    @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
      JComboBox<?> combo = (JComboBox<?>) e.getSource();

      Insets i = combo.getInsets();
      int popupWidth = proto.getIconWidth() * 3 + i.left + i.right;

      Dimension size = combo.getSize();
      if (size.width >= popupWidth) {
        return;
      }
      if (!adjusting) {
        adjusting = true;
        combo.setSize(popupWidth, size.height);
        combo.showPopup();
      }
      combo.setSize(size);
      adjusting = false;
    }

    @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
      /* not needed */
    }

    @Override public void popupMenuCanceled(PopupMenuEvent e) {
      /* not needed */
    }
  });
  return combo;
}

参考リンク

コメント