概要

JListのセルに配置されているコンポーネントをマウスの座標から検索し、それに設定されたカーソルをJListに適用します。

サンプルコード

class LinkCellList<E> extends JList<E> {
  private int prevIndex = -1;
  protected LinkCellList(ListModel<E> model) {
    super(model);
  }

  @Override public void updateUI() {
    setForeground(null);
    setBackground(null);
    setSelectionForeground(null);
    setSelectionBackground(null);
    super.updateUI();
    setFixedCellHeight(32);
    setCellRenderer(new LinkCellRenderer<>());
    // TEST: putClientProperty("List.isFileList", Boolean.TRUE);
  }

  @Override protected void processMouseMotionEvent(MouseEvent e) {
    Point pt = e.getPoint();
    int i = locationToIndex(pt);
    E s = getModel().getElementAt(i);
    Component c = getCellRenderer().getListCellRendererComponent(
        this, s, i, false, false);
    Rectangle r = getCellBounds(i, i);
    c.setBounds(r);
    if (prevIndex != i) {
      c.doLayout();
    }
    prevIndex = i;
    pt.translate(-r.x, -r.y);
    setCursor(
      Optional.ofNullable(SwingUtilities.getDeepestComponentAt(c, pt.x, pt.y))
        .map(Component::getCursor)
        .orElse(Cursor.getDefaultCursor()));
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、以下の手順でイベントを取得しないセルレンダラー中のコンポーネントに応じたカーソルの変更を行っています。

  • JList上をマウスカーソルを移動した時にカーソルのある行を取得
  • セルレンダラーに行番号や文字列などの値を渡して描画用のコンポーネントを取得
  • 描画用コンポーネントの位置とサイズをJList#getCellBounds()で取得したセル領域に変更
    • Component#setBounds(...)で描画用コンポーネントの位置とサイズを変更してもその子コンポーネントのレイアウトは更新されない
  • Component#doLayout()でレイアウトを更新
    • このサンプルで使用しているFlowLayoutではJLabelに設定する文字列の長さで後続のコンポーネントの位置が変化するのでComponent#doLayout()を実行してレイアウトの更新を行う必要がある
    • ただし、同じ行の場合は描画用コンポーネントも前回と同じはずなのでレイアウトを更新する必要はない
    • JListのセル内にJButtonを配置するのように、すべての行のレイアウトが同じ(内容に応じて変化しない)場合もレイアウトを更新する必要はない
  • JList基準のカーソル位置座標を描画用コンポーネント基準に変更
  • SwingUtilities.getDeepestComponentAt(...)メソッドで描画用コンポーネントから変更した座標の直下にある子コンポーネントを取得
  • 取得した子コンポーネントのカーソルをJListのカーソルとして設定

参考リンク

JListのセルレンダラーとして設定したJEditorPaneからHyperlinkEventを取得する

コメント