---
category: swing
folder: ThreeDotsMenuButton
title: JListのセルがハイライト表示中にのみJButtonを表示する
tags: [JList, JLayer, JButton]
author: aterai
pubdate: 2024-12-09T08:19:05+09:00
description: JListのセルハイライト表示とコンテキストメニュー表示用のJButtonをJLayer上で描画します。
image: https://drive.google.com/uc?id=16fRrYb7io_62_SL3QoDEAvwI7QTD1cj4
---
* 概要 [#summary]
JListのセルハイライト表示とコンテキストメニュー表示用のJButtonをJLayer上で描画します。
`JList`のセルハイライト表示とコンテキストメニュー表示用の`JButton`を`JLayer`上で描画します。

#download(https://drive.google.com/uc?id=16fRrYb7io_62_SL3QoDEAvwI7QTD1cj4)

* サンプルコード [#sourcecode]
#code(link){{
class RolloverLayerUI extends LayerUI<JScrollPane> {
  private final JPanel renderer = new JPanel();
  private int rolloverIdx = -1;
  private final Point loc = new Point(-100, -100);
  private final JButton button = new JButton(new ThreeDotsIcon()) {
    @Override public void updateUI() {
      super.updateUI();
      setBorderPainted(false);
      setContentAreaFilled(false);
      setFocusPainted(false);
      setFocusable(false);
      setOpaque(false);
      setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));
    }
  };

  @Override public void installUI(JComponent c) {
    super.installUI(c);
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(
          AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
    }
  }

  @Override public void uninstallUI(JComponent c) {
    if (c instanceof JLayer) {
      ((JLayer<?>) c).setLayerEventMask(0);
    }
    super.uninstallUI(c);
  }

  @Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseEvent(e, l);
    Component c = e.getComponent();
    if (c instanceof JList) {
      JList<?> list = (JList<?>) c;
      int id = e.getID();
      if (id == MouseEvent.MOUSE_CLICKED && SwingUtilities.isLeftMouseButton(e)) {
        Rectangle r = list.getCellBounds(rolloverIdx, rolloverIdx);
        Dimension d = button.getPreferredSize();
        r.width = l.getView().getViewportBorderBounds().width - d.width;
        JPopupMenu popup = ((JComponent) c).getComponentPopupMenu();
        Point pt = e.getPoint();
        if (popup != null && !r.contains(pt)) {
          popup.show(c, pt.x, pt.y);
        }
      } else if (id == MouseEvent.MOUSE_EXITED) {
        list.repaint(list.getCellBounds(rolloverIdx, rolloverIdx));
        rolloverIdx = -1;
        loc.setLocation(-100, -100);
      }
    }
  }

  @Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JScrollPane> l) {
    super.processMouseMotionEvent(e, l);
    Component c = e.getComponent();
    if (e.getID() == MouseEvent.MOUSE_MOVED && c instanceof JList) {
      JList<?> list = (JList<?>) c;
      Point pt = e.getPoint();
      loc.setLocation(pt);
      rolloverIdx = list.locationToIndex(pt);
      Rectangle r = list.getCellBounds(rolloverIdx, rolloverIdx);
      r.width = l.getView().getViewportBorderBounds().width;
      r.grow(0, r.height);
      list.repaint(r);
    }
  }

  @Override public void paint(Graphics g, JComponent c) {
    super.paint(g, c);
    JList<?> list = getList(c);
    if (list != null && rolloverIdx >= 0) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) c).getView();
      Graphics2D g2 = (Graphics2D) g.create();
      Rectangle cellBounds = list.getCellBounds(rolloverIdx, rolloverIdx);
      Component rc = getRendererComponent(list, rolloverIdx);
      Dimension d = button.getPreferredSize();
      cellBounds.width = scroll.getViewportBorderBounds().width - d.width;
      boolean buttonRollover = !cellBounds.contains(loc);
      button.getModel().setRollover(buttonRollover);
      Rectangle rect = SwingUtilities.convertRectangle(list, cellBounds, c);
      SwingUtilities.paintComponent(g2, rc, renderer, rect);
      rect.x += rect.width;
      rect.width = d.width;
      g2.setPaint(rc.getBackground());
      g2.fill(rect);
      SwingUtilities.paintComponent(g2, button, renderer, rect);
      g2.dispose();
    }
  }

  private static JList<?> getList(JComponent layer) {
    JList<?> list = null;
    if (layer instanceof JLayer) {
      JScrollPane scroll = (JScrollPane) ((JLayer<?>) layer).getView();
      Component view = scroll.getViewport().getView();
      if (view instanceof JList) {
        list = (JList<?>) view;
      }
    }
    return list;
  }

  private static <E> Component getRendererComponent(JList<E> list, int idx) {
    E value = list.getModel().getElementAt(idx);
    ListCellRenderer<? super E> r = list.getCellRenderer();
    boolean isSelected = list.isSelectedIndex(idx);
    boolean cellHasFocus = list.getSelectionModel().getLeadSelectionIndex() == idx;
    Component c = r.getListCellRendererComponent(list, value, idx, isSelected, cellHasFocus);
    if (!isSelected) {
      c.setBackground(Color.GRAY);
      c.setForeground(Color.WHITE);
    }
    return c;
  }
}
}}

* 解説 [#explanation]
- `JList`の親`JScrollPane`に`JLayer`を設定
-- セルのハイライト表示を`ListCellRenderer`ではなく`LayerUI#paint(...)`をオーバーライドして、`JList#getListCellRendererComponent(...)`で取得した描画用コンポーネントの文字色、背景色を変更し`SwingUtilities.paintComponent(...)`で描画
-- 描画領域をセル全体の幅ではなくコンテキストメニュー表示用の`JButton`の幅を除外するよう縮小する
--- [[JListのセルをカーソル移動でロールオーバー>Swing/RollOverListener]]
-- コンテキストメニュー表示用の`JButton`も同様に`SwingUtilities.paintComponent(...)`でセル右端に描画
-- `SwingUtilities.paintComponent(...)`での描画で`JButton`の`MouseListener`などは反応しないため、`LayerUI#processMouseEvent(...)`中でクリックされたかを判別し`JList#getComponentPopupMenu()`で取得した`JPopupMenu`を表示
--- `Windows`環境で右クリックだけではなく、この`JButton`の左クリックで一般的?な`Web`アプリのように`JPopupMenu`が表示可能になる

* 参考リンク [#reference]
- [[JListのセルをカーソル移動でロールオーバー>Swing/RollOverListener]]
- [[JListのセル内にJButtonを配置する>Swing/ButtonsInListCell]]
- [[JListのセルに項目選択チェックボックスを追加する>Swing/ListCellItemCheckBoxes]]

* コメント [#comment]
#comment
#comment