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)上操作。
备注:内容仅供参考。
