概要

JListのセルハイライト表示とコンテキストメニュー表示用のJButtonJLayer上で描画します。

サンプルコード

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;
  }
}
View in GitHub: Java, Kotlin

解説

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

参考リンク

コメント