12.10 使用JTree和TreeModel创建树 12.10.1 创建树

12.10 使用JTree和TreeModel创建树

树也是图形用户界面中使用非常广泛的GUI组件,例如使用Windows资源管理器时,将看到如图12.38所示的目录树。

如图12.38所示的树,代表计算机世界里的树,它从自然界实际的树抽象而来。计算机世界里的树是由一系列具有严格父子关系的节点组成的,每个节点既可以是其上一级节点的子节点,也可以是其下一级节点的父节点,因此同一个节点既可以是父节点,也可以是子节点(类似于一个人,他既是他儿子的父亲,又是他父亲的儿子)。

节点分类

如果按节点是否包含子节点来分,节点分为如下两种

  • 普通节点:包含子节点的节点。
  • 叶子节点:没有子节点的节点,因此叶子节点不可作为父节点。

如果按节点是否具有唯一的父节点来分,节点又可分为如下两种

  • 根节点:没有父节点的节点,根节点不可作为子节点。
  • 普通节点:具有唯一父节点的节点。

棵树只能有一个根节点,如果一棵树有了多个根节点,那它就不是一棵树了,而是多棵树的集合,有时也被称为森林。图12.39显示了计算机世界里树的一些专业术语。
使用Swing里的JTreeTreeModel及其相关的辅助类可以很轻松地开发出计算机世界里的树,如图1239所示

12.10.1 创建树

Swing使用JTree对象来代表一棵树(实际上,JTree可以代表森林,因为在使用JTree创建树时可以传入多个根节点),
JTree树中节点可以使用TreePath来标识,TreePath对象封装了当前节点及其所有的父节点。必须指出,节点及其所有的父节点才能唯一地标识一个节点;也可以使用行数来标识,如图12.39所示,显示区域的每一行都标识一个节点。
当一个节点具有子节点时,该节点有两种状态。

  • 展开状态:当父节点处于展开状态时,其子节点是可见的。
  • 折叠状态:当父节点处于折叠状态时,其子节点都是不可见的。

如果某个节点是可见的,则该节点的父节点(包括直接的、间接的父节点)都必须处于展开状态,只要有任意一个父节点处于折叠状态,该节点就是不可见的。
如果希望创建一棵树,则直接使用JTree的构造器创建JTree对象即可。JTree提供了如下几个常用构造器。

方法 描述
JTree(TreeModel newModel) 使用指定的数据模型创建JTree对象,它默认显示根节点。
JTree(TreeNode root) 使用root作为根节点创建JTree对象,它默认显示根节点。
JTree(TreeNode root, boolean asksAllowsChildren) 使用root作为根节点创建JTree对象,它默认显示根节点。**asksAllowsChildren参数控制怎样的节点才算叶子节点**:
  • 如果该参数为true,则只有当程序使用setAllowsChildren(false)显式设置某个节点不允许添加子节点时(以后也不会拥有子节点),该节点才会被JTree当成叶子节点;
  • 如果该参数为false,则只要某个节点当时没有子节点(不管以后是否拥有子节点),该节点都会被JTree当成叶子节点。

上面的第一个构造器需要显式传入一个TreeModel对象,SwingTreeModel提供了一个DefaultTreeModel实现类,通常可先创建DefaultTreeModel对象,然后利用DefaultTreeModel来创建JTree,但通过DefaultTreeModelAPI文档会发现,创建DefaultTreeModel对象依然需要传入根节点,所以直接通过根节点创建JTree更加简洁。
为了利用根节点来创建JTree,程序需要创建一个TreeNode对象。TreeNode是一个接口,该接口有个MutableTreeNode子接口,Swing为该接口提供了默认的实现类:DefaultMutableTreeNode,程序可以通过DefaultMutableTreeNode来为树创建节点,并通过DefaultMutableTreeNode提供的add()方法建立各节点之间的父子关系,然后调用JTreeJTree(TreeNodeRoot构造器来创建一棵树。

图12.40显示了JTree相关类的关系,从该图可以看出DefaultTreeModelTreeModel的默认实现类,当程序通过TreeNode类创建JTree时,其状态数据实际上由DefaultTreeModel对象维护,因为创建JTree时传入的TreeNode对象,实际上传给了DefaultTreeModel对象。

DefaultTreeModel也提供了DefaultTreeModel(TreeNode root)构造器,用于接收一个TreeNode根节点来创建一个默认的TreeModel对象;当程序中通过传入一个根节点来创建JTree对象时,实际上是将该节点传入对应的DefaultTreeModel对象,并使用该DefaultTreeModel对象来创建JTree对象

程序 简单Swing数组

下面程序创建了一棵最简单的Swing树:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import javax.swing.*;
import javax.swing.tree.*;

public class SimpleJTree {
JFrame jf = new JFrame("简单树");
JTree tree;
DefaultMutableTreeNode root;
DefaultMutableTreeNode guangdong;
DefaultMutableTreeNode guangxi;
DefaultMutableTreeNode foshan;
DefaultMutableTreeNode shantou;
DefaultMutableTreeNode guilin;
DefaultMutableTreeNode nanning;

public void init() {
// 依次创建树中所有节点
root = new DefaultMutableTreeNode("中国");
guangdong = new DefaultMutableTreeNode("广东");
guangxi = new DefaultMutableTreeNode("广西");
foshan = new DefaultMutableTreeNode("佛山");
shantou = new DefaultMutableTreeNode("汕头");
guilin = new DefaultMutableTreeNode("桂林");
nanning = new DefaultMutableTreeNode("南宁");
// 通过add()方法建立树节点之间的父子关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
// 以根节点创建树
tree = new JTree(root); // ①
jf.add(new JScrollPane(tree));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}

public static void main(String[] args) {
new SimpleJTree().init();
}
}

上面程序中的粗体字代码创建了一系列的DefaultMutableNode对象,并通过add()方法为这些节点建立了相应的父子关系。程序中①号代码则以一个根节点创建了一个JTree对象。当程序把JTree对象添加到其他容器中后,JTree就会在该容器中绘制出一棵Swing树。运行上面程序,会看到如图12.41所示的窗口。

从图12.41中可以看出,Swing树的默认风格是使用一个特殊图标来表示节点的展开、折叠,而不是使用我们熟悉的“+”、“-”图标来表示节点的展开、折叠。如果希望使用“+”、“-”图标来表示节点的展开、折叠,则可以考虑使用Windows风格。

不显示节点之间的连线

从图12.41中可以看出,Swing树默认使用连接线来连接所有节点,程序可以使用如下代码来强制JTree不显示节点之间的连接线。

1
2
//没有连接线
tree.putClientProperty("JTree.lineStyle","None");

只有水平分割线

或者使用如下代码来强制节点之间只有水平分隔线。
图12.41中显示的根节点前没有绘制表示节点展开、折叠的特殊图标,如果希望根节点也绘制表示节点展开、折叠的特殊图标,则使用如下代码。

1
2
//设置是否显示根节点的“展开、折叠”图标,默认是fase
tree.setShowsRootHandles(true);

隐藏根节点

JTree甚至允许把整个根节点都隐藏起来,可以通过如下代码来隐藏根节点。

1
2
//设置根节点是否可见,默认是true
tree.setRootvisible(false);

遍历所有子节点

DefaultMutableTreeNodeJTree默认的树节点,该类提供了大量的方法来访问树中的节点,包括遍历该节点的所有子节点的两个方法。

方法 描述
Enumeration<TreeNode> breadthFirstEnumeration() 按广度优先的顺序遍历以此节点为根的子树,并返回所有节点组成的枚举对象。
Enumeration<TreeNode> preorderEnumeration() Creates and returns an enumeration that traverses the subtree rooted at this node in preorder.
Enumeration<TreeNode> depthFirstEnumeration() 按深度优先的顺序遍历以此节点为根的子树,并返回所有节点组成的枚举对象。
Enumeration<TreeNode> postorderEnumeration() Creates and returns an enumeration that traverses the subtree rooted at this node in postorder.

获取节点

除此之外,DefaultMutableTreeNode也提供了大量的方法来获取指定节点的兄弟节点、父节点节点等,常用的有如下几个方法:

方法 描述
DefaultMutableTreeNode getNextSibling() 返回此节点的下一个兄弟节点
TreeNode getParent() 返回此节点的父节点。如果此节点没有父节点,则返回null
TreeNode[] getPath() 返回从根节点到达此节点的所有节点组成的数组。
protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) 返回包含此节点的树的根节点。
DefaultMutableTreeNode getPreviousSibling() 返回此节点的上一个兄弟节点。
TreeNode getRoot() 返回包含此节点的树的根节点
TreeNode getSharedAncestor(DefaultMutableTreeNode aNode) 返回此节点和aNode最近的共同祖先
int getSiblingCount() 返回此节点的兄弟节点数
boolean isLeaf() 返回该节点是否是叶子节点。
boolean isNodeAncestor(TreeNode anotherNode) 判断anotherNode是否是当前节点的祖先节点(包括父节点)。
boolean isNodeChild(TreeNode aNode) 如果aNode是此节点的子节点,则返回true
boolean isNodeDescendant(DefaultMutableTreeNode anotherNode) 如果anotherNode是此节点的后代,包括是此节点本身、此节点的子节点或此节点的子节点的后代,都将返回tnue
boolean isNodeRelated(DefaultMutableTreeNode aNode) aNode和当前节点位于同一棵树中时返回true
boolean isNodeSibling(TreeNode anotherNode) 返回anotherNode是否是当前节点的兄弟节点。
boolean isRoot() 返回当前节点是否是根节点。
Enumeration<TreeNode> pathFromAncestorEnumeration(TreeNode ancestor) 返回从指定祖先节点到当前节点的所有节点组成的枚举对象