---
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, 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();
}
}
}
}
}}
* Description [#explanation]
* Description [#description]
- `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