PAYATHON 2026

Java 8 Swing:如何让 JTree 选中节点时只垂直滚动?

数据小陈

结论

要让 JTree 只沿垂直方向滚动,可以在 scrollPathToVisible() 中展开目标路径,再调整外层 JViewport 的垂直位置,同时保留当前水平位置。这样选中节点后,视口只会上下移动。

实现方式

getPathBounds(path) 返回目标节点在 JTree 坐标系中的边界。JViewport.getViewPosition() 给出当前视口左上角在树坐标系中的位置。比较节点边界与视口的垂直可见范围,只在节点超出范围时更新 Y 坐标;X 坐标保持不变。

import java.awt.Point;
import java.awt.Rectangle;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

private static class TreeEx extends JTree {
    public TreeEx(TreeNode root) {
        super(root);
    }

    @Override
    public void setSelectionPath(TreePath path) {
        super.setSelectionPath(path);
        scrollPathToVisible(path);
    }

    @Override
    public void scrollPathToVisible(TreePath path) {
        if (path == null) {
            return;
        }

        // 确保目标路径已展开,再获取节点边界。
        makeVisible(path);
        Rectangle bounds = getPathBounds(path);
        if (bounds == null) {
            return;
        }

        JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(
                JViewport.class, this);
        if (viewport == null) {
            return;
        }

        Point position = viewport.getViewPosition();
        int y = position.y;
        int extentHeight = viewport.getExtentSize().height;

        if (bounds.height > extentHeight) {
            // 节点本身比视口高时,将节点顶部对齐。
            y = bounds.y;
        } else if (bounds.y < position.y) {
            y = bounds.y;
        } else if (bounds.y + bounds.height > position.y + extentHeight) {
            y = bounds.y + bounds.height - extentHeight;
        }

        int maxY = Math.max(0, viewport.getViewSize().height - extentHeight);
        y = Math.max(0, Math.min(y, maxY));

        // 保留当前水平位置,只更新垂直位置。
        viewport.setViewPosition(new Point(position.x, y));
    }
}

scrollRectToVisible() 会根据传入矩形的边界尝试让矩形可见,因此可能同时改变视口的 X 和 Y。直接设置 JViewport 位置,可以明确只更新 Y 坐标。

注意事项

  • makeVisible(path) 会展开通往目标节点的路径。之后再调用 getPathBounds(path),才能取得展开后的节点边界。
  • 这段实现假设 JTree 位于一个 JViewport 中,例如放在 JScrollPane 里。如果树不在视口内,方法会直接返回。
  • 如果目标节点比视口高,无法让节点整体可见;示例会将节点顶部对齐。
  • Swing 组件应在事件派发线程(EDT)上操作。

备注:内容仅供参考。