---
category: swing
folder: GroupingListItems
title: JListのアイテムをグループ化する
title-en: Group JList items
tags: [JList, JSeparator, MatteBorder]
author: aterai
pubdate: 2025-12-22T00:43:07+09:00
description: JListのアイテムをJSeparator、またはMatteBorderを使用してグループ化して表示します。
summary-jp: JListのアイテムをJSeparator、またはMatteBorderを使用してグループ化して表示します。
summary-en: Group JList items using JSeparator or MatteBorder.
image: https://drive.google.com/uc?id=1hvXo9HYNh6KEPOif0oeVK7wDWVdYxCZG
---
* Summary [#summary]
JListのアイテムをJSeparator、またはMatteBorderを使用してグループ化して表示します。
`JList`のアイテムを`JSeparator`、または`MatteBorder`を使用してグループ化して表示します。
// #en{{Group JList items using JSeparator or MatteBorder.}}

#download(https://drive.google.com/uc?id=1hvXo9HYNh6KEPOif0oeVK7wDWVdYxCZG)

* Source Code Examples [#sourcecode]
#code(link){{
class GroupList<E> extends JList<E> {
  protected GroupList(ListModel<E> model) {
    super(model);
    initActionMpa(this);
  }

  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    ListCellRenderer<? super E> r = getCellRenderer();
    setCellRenderer((l, v, index, isSelected, cellHasFocus) ->
        v instanceof JSeparator
            ? (JSeparator) v
            : r.getListCellRendererComponent(l, v, index, isSelected, cellHasFocus)
    );
  }

  private static <E> void initActionMpa(JList<E> list) {
    ActionMap am = list.getActionMap();
    String selectPrevKey = "selectPreviousRow";
    Action prev = am.get(selectPrevKey);
    am.put(selectPrevKey, new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        prev.actionPerformed(e);
        JList<?> l = (JList<?>) e.getSource();
        int index = l.getSelectedIndex();
        Object o = l.getModel().getElementAt(index);
        if (o instanceof JSeparator) {
          prev.actionPerformed(e);
        }
      }
    });
    String selectNextKey = "selectNextRow";
    Action next = am.get(selectNextKey);
    am.put(selectNextKey, new AbstractAction() {
      @Override public void actionPerformed(ActionEvent e) {
        next.actionPerformed(e);
        JList<?> l = (JList<?>) e.getSource();
        int index = l.getSelectedIndex();
        Object o = l.getModel().getElementAt(index);
        if (o instanceof JSeparator) {
          next.actionPerformed(e);
        }
      }
    });

    InputMap im = list.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), selectPrevKey);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, 0), selectPrevKey);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), selectNextKey);
    im.put(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, 0), selectNextKey);
  }
}
}}

* Description [#description]
- `Box + Multiple JList + JSeparator`
-- グループごとに`JList`を作成し、これを`Box.createVerticalBox()`で作成した垂直配置`Box`に`JSeparator`で区切って追加
-- このサンプルでは`new JTree()`で生成されるサンプル`TreeModel`を利用してリストアイテムのグループ化をテストしている
-- フォーカスオーナーである`JList`のみ選択状態ハイライト表示するよう各`JList`のセルレンダラーを変更し、グループ内でのみ選択可能なように見せかけている
-- [[BoxLayoutでリスト状に並べる>Swing/ComponentList]]

#code{{
private static Container makeListBox() {
  Box box = Box.createVerticalBox();
  JTree tree = new JTree();
  TreeModel model = tree.getModel();
  TreeNode root = (TreeNode) model.getRoot();
  Collections.list((Enumeration<?>) root.children())
      .stream()
      .filter(TreeNode.class::isInstance)
      .map(TreeNode.class::cast)
      .forEach(node -> {
        if (!node.isLeaf()) {
          Enumeration<?> children = node.children();
          box.add(makeList(Collections.list(children).toArray()));
          box.add(new JSeparator());
        }
      });
  JPanel p = new JPanel(new BorderLayout());
  p.setBackground(UIManager.getColor("List.background"));
  p.add(box, BorderLayout.NORTH);
  return p;
}

private static JList<Object> makeList(Object... ary) {
  JList<Object> c = new JList<>(ary) {
    @Override public void updateUI() {
      setCellRenderer(null);
      super.updateUI();
      ListCellRenderer<? super Object> r = getCellRenderer();
      setCellRenderer((l, v, index, isSelected, cellHasFocus) -> {
        boolean selected = isSelected && l.isFocusOwner();
        return r.getListCellRendererComponent(l, v, index, selected, cellHasFocus);
      });
    }
  };
  int height = c.getPreferredSize().height;
  c.setMaximumSize(new Dimension(Short.MAX_VALUE, height));
  return c;
}
}}

- `JSeparator + ListModel`
-- レイアウトが`VERTICAL`の`JList`に`ListModel<Object>`モデルを適用し、セルアイテムとして`JSeparator`を追加してグループ化の区切りとする
-- デフォルトセルレンダラーの描画用コンポーネントである`JLabel`の代わりに`ListModel<Object>`に追加された`JSeparator`をそのまま使用する
-- カーソルキーによる選択状態移動で`JSeparator`をスキップするよう`selectPreviousRow`、`selectNextRow`を置き換えている
--- `selectPreviousRowExtendSelection`、`selectPreviousRowExtendSelection`アクションなどの選択行の拡張には未対応
--- マウスカーソルによる`JSeparator`アイテムの選択は制限していないが、表示上は`JSeparator`は選択状態にならないようセルレンダラーで設定
-- [[JComboBoxにJSeparatorを挿入>Swing/ComboBoxSeparator]]

- `CellRenderer + MatteBorder`
-- `new JTree()`で生成されるサンプル`TreeModel`の`2`レベルの`TreeNode`を利用してグループ化した`ListModel<TreeNode>`を作成し、セルレンダラーで現在の`TreeNode`とその次の`TreeNode`の親`TreeNode`が異なる場合は現在の`TreeNode`がグループの末尾であるとして`MatteBorder`で下線を設定している
-- `JList`のレイアウトは上記の`JSeparator + ListModel`と同様にセルが単一列で並ぶ`VERTICAL`の場合のみ想定している
-- [[JComboBoxのアイテムをBorderで修飾してグループ分け>Swing/BorderSeparator]]

#code{{
class GroupBorderList<E extends TreeNode> extends JList<E> {
  protected GroupBorderList(ListModel<E> model) {
    super(model);
  }

  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    ListCellRenderer<? super E> r = getCellRenderer();
    setCellRenderer((l, v, index, isSelected, cellHasFocus) -> {
      Component c = r.getListCellRendererComponent(l, v, index, isSelected, cellHasFocus);
      if (c instanceof JComponent) {
        Border outside = getOutsideBorder(l, v, index);
        String key = isSelected ? "List.focusCellHighlightBorder" : "List.noFocusBorder";
        Border inside = UIManager.getBorder(key);
        ((JComponent) c).setBorder(BorderFactory.createCompoundBorder(outside, inside));
      }
      return c;
    });
  }

  private static Border getOutsideBorder(JList<?> l, TreeNode v, int index) {
    int max = l.getModel().getSize();
    int next = index + 1;
    Object n = next < max ? l.getModel().getElementAt(next) : null;
    return Optional.ofNullable(n)
        .filter(TreeNode.class::isInstance)
        .map(TreeNode.class::cast)
        .map(TreeNode::getParent)
        .filter(p -> !Objects.equals(p, v.getParent()))
        .<Border>map(p -> BorderFactory.createMatteBorder(0, 0, 1, 0, Color.GRAY))
        .orElseGet(() -> BorderFactory.createMatteBorder(0, 0, 1, 0, l.getBackground()));
  }
}
}}

* Reference [#reference]
- [[BoxLayoutでリスト状に並べる>Swing/ComponentList]]
- [[JComboBoxにJSeparatorを挿入>Swing/ComboBoxSeparator]]
- [[JComboBoxのアイテムをBorderで修飾してグループ分け>Swing/BorderSeparator]]

* Comment [#comment]
#comment
#comment