---
category: swing
folder: RolloverTreeOnlyWhenPopupHidden
title: JTreeに設定したJPopupMenuが非表示の場合のみJTreeの行をロールオーバー状態で描画する
tags: [JTree, JPopupMenu]
author: aterai
pubdate: 2025-03-31T05:38:46+09:00
description: JTreeにマウスカーソル下の行をロールオーバー描画する機能を追加し、JPopupMenuが表示されている場合はそのロールオーバー状態を維持するよう設定します。
image: https://drive.google.com/uc?id=11qlW-Yy5eLtrsTD7qG4RTSNGM4x2E4Pe
---
* Summary [#summary]
`JTree`にマウスカーソル下の行をロールオーバー描画する機能を追加し、`JPopupMenu`が表示されている場合はそのロールオーバー状態を維持するよう設定します。

#download(https://drive.google.com/uc?id=11qlW-Yy5eLtrsTD7qG4RTSNGM4x2E4Pe)

* Source Code Examples [#sourcecode]
#code(link){{
class FileSystemViewTree extends JTree {
  private static final Color SELECTED_COLOR = new Color(0x00_78_D7);
  private static final Color ROLLOVER_COLOR = new Color(0x64_96_C8);
  protected int rollOverRowIndex = -1;
  protected transient MouseAdapter rolloverHandler;
  private transient FileSystemView fileSystemView;

  protected FileSystemViewTree() {
    super();
  }

  @Override protected void paintComponent(Graphics g) {
    int[] sr = getSelectionRows();
    if (sr == null) {
      super.paintComponent(g);
      return;
    }
    g.setColor(getBackground());
    g.fillRect(0, 0, getWidth(), getHeight());
    Graphics2D g2 = (Graphics2D) g.create();
    if (rollOverRowIndex >= 0) {
      g2.setPaint(ROLLOVER_COLOR);
      Rectangle rect = getRowBounds(rollOverRowIndex);
      g2.fillRect(0, rect.y, getWidth(), rect.height);
    }
    g2.setPaint(SELECTED_COLOR);
    Arrays.stream(sr).mapToObj(this::getRowBounds)
        .forEach(r -> g2.fillRect(0, r.y, getWidth(), r.height));
    super.paintComponent(g);
    if (hasFocus()) {
      Optional.ofNullable(getLeadSelectionPath()).ifPresent(path -> {
        Rectangle r = getRowBounds(getRowForPath(path));
        g2.setPaint(SELECTED_COLOR.darker());
        g2.drawRect(0, r.y, getWidth() - 1, r.height - 1);
      });
    }
    g2.dispose();
  }

  @Override public void updateUI() {
    setCellRenderer(null);
    removeMouseListener(rolloverHandler);
    removeMouseMotionListener(rolloverHandler);
    super.updateUI();
    setUI(new WholeRowSelectTreeUI());
    UIManager.put("Tree.repaintWholeRow", Boolean.TRUE);
    fileSystemView = FileSystemView.getFileSystemView();
    addTreeSelectionListener(new FolderSelectionListener(fileSystemView));
    DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
    setCellRenderer((tree, value, selected, expanded, leaf, row, hasFocus) -> {
    setCellRenderer((tree, value, selected, expanded, leaf, row, focus) -> {
      Component c = renderer.getTreeCellRendererComponent(
          tree, value, selected, expanded, leaf, row, false);
      boolean rollover = row == rollOverRowIndex;
      updateFgc(c, renderer.getTextSelectionColor(), rollover);
      updateBgc(c, tree.getBackground(), selected, rollover);
      updateIcon(c, value, selected);
      return c;
    });
    setOpaque(false);
    rolloverHandler = new RolloverHandler();
    addMouseListener(rolloverHandler);
    addMouseMotionListener(rolloverHandler);
    EventQueue.invokeLater(() -> setModel(makeFileTreeModel(fileSystemView)));
  }

  public void updateRolloverIndex() {
    EventQueue.invokeLater(() -> {
      Point pt = getMousePosition();
      if (pt == null) {
        clearRollover();
      } else {
        updateRolloverIndex(pt);
      }
    });
  }

  public void updateRolloverIndex(Point pt) {
    int row = getRowForLocation(pt.x, pt.y);
    boolean isPopupVisible = getComponentPopupMenu().isVisible();
    if (rollOverRowIndex != row && !isPopupVisible) {
      rollOverRowIndex = row;
      repaint();
    }
  }

  private void clearRollover() {
    rollOverRowIndex = -1;
    repaint();
  }

  protected class RolloverHandler extends MouseAdapter {
    @Override public void mouseMoved(MouseEvent e) {
      updateRolloverIndex(e.getPoint());
    }

    @Override public void mouseEntered(MouseEvent e) {
      updateRolloverIndex(e.getPoint());
    }

    @Override public void mouseExited(MouseEvent e) {
      boolean isPopupVisible = getComponentPopupMenu().isVisible();
      if (!isPopupVisible) {
        clearRollover();
      }
    }
  }
}
}}

* Explanation [#explanation]
- `JTree`に`MouseAdapter`を追加し、マウスカーソルが移動したら`JTree#getRowForLocation(x, y)`メソッドでマウスカーソル下の行番号を記憶し、その行の文字色はセルレンダラー、背景色は`JTree#paintComponent(...)`をオーバーライドして変更
-- デフォルトでは`JTree#setComponentPopupMenu(JPopupMenu)`で設定した`JPopupMenu`が表示されると`mouseExited`イベントが発生したり、その`JPopupMenu`が表示中でも`JTree`上にマウスカーソルが移動すると`mouseMoved`イベントが発生してロールオーバー状態の行番号が変化してしまう
-- このサンプルではマウスカーソル移動時に`JPopupMenu`が表示中出ない場合のみロールオーバー状態の行番号を変更するよう設定してこれを回避
-- さらに`JPopupMenu`側には`PopupMenuListener`を追加し、`JPopupMenu`が非表示になる際にマウスカーソル位置を`JTree#getMousePosition()`で取得してロールオーバー状態の行番号を更新する
--- `JTree#getMousePosition()`で取得した`Point`が`null`の場合、マウスカーソルは`JTree`の外に存在するのでロールオーバー状態はクリアする

* Reference [#reference]
- [[JTreeのノードをハイライト>Swing/RollOverTree]]
- [[JTableの行を右クリックで選択して同時にJPopupMenuを開く>Swing/RightClickRowSelectionAndPopupMenu]]

* Comment [#comment]
#comment
#comment