如何使用 Jtree
(1)创建树
(2)对节点的选择做出响应
(3)自定义树的外观表现
(4)动态改变一棵树
(5)创建树的数据模型
(6)懒加载孩子
(7)如何写expansion linstener
(8)如何写tree-will-expand listener
利用 JTree 类,你可以显示等级体系的数据。一个 JTree 对象并没有包含实际的数据;它只是提供了数据的一个视图。像其他非平凡的( nontrivial ) Swing 组件一样,这种 Jtree 通过查询她的数据模型获得数据。这是一个 Jtree :
如上面的图片所显示, Jtree 垂直显示它的数据。树中显示的每一行包含一项数据,称之为节点( node )。每颗树有一个根节点( root node ),其他所有节点是它的子孙。默认情况下,树只显示根节点,但是你可以设置改变默认显示方式。一个节点可以拥有孩子也可以不拥有任何子孙。我们称那些可以拥有孩子(不管当前是否有孩子)的节点为“分支节点”( branch nodes ),而不能拥有孩子的节点为“叶子节点”( leaf nodes )。
分支节点可以有任意多个孩子。通常,用户可以通过点击实现展开或者折叠分支节点,使得他们的孩子可见或者不可见。默认情况下,除了根节点以外的所有分支节点默认呈现折叠状态。程序中,通过监听 tree expansion 或者 tree-will-expand 事件可以检测分支节点的展开状态。监听事件在下面两节内容中描述 How to Write a Tree Expansion Listener and How to Write a Tree-Will-Expand Listener .
(1)创建一棵 Tree
http://download.oracle.com/javase/tutorial/uiswing/examples/components/TreeDemoProject/src/components/TreeDemo.java 获得,创建了一个JTree 对象,并将之放到一个scroll pane 上
- //Where
instance variables are declared: -
private JTree tree; -
... -
public TreeDemo() { -
... -
DefaultMutableTreeNode top = -
new DefaultMutableTreeNode("The Java Series"); -
createNodes(top); -
tree = new JTree(top); -
... -
JScrollPane treeView = new JScrollPane(tree); -
... -
}
//Where instance variables are declared: private JTree tree; ... public TreeDemo() { ... DefaultMutableTreeNode top = new DefaultMutableTreeNode("The Java Series"); createNodes(top); tree = new JTree(top); ... JScrollPane treeView = new JScrollPane(tree); ... }
- private
void createNodes(DefaultMutableTreeNode top) { -
DefaultMutableTreeNode category = null; -
DefaultMutableTreeNode book = null; -
-
category = new DefaultMutableTreeNode("Books for Java Programmers"); -
top.add(category); -
-
//original Tutorial -
book = new DefaultMutableTreeNode(new BookInfo -
("The Java Tutorial: A Short Course on the Basics", -
"tutorial.html")); -
category.add(book); -
-
//Tutorial Continued -
book = new DefaultMutableTreeNode(new BookInfo -
("The Java Tutorial Continued: The Rest of the JDK", -
"tutorialcont.html")); -
category.add(book); -
-
//JFC Swing Tutorial -
book = new DefaultMutableTreeNode(new BookInfo -
("The JFC Swing Tutorial: A Guide to Constructing GUIs", -
"swingtutorial.html")); -
category.add(book); -
-
//...add more books for programmers... -
-
category = new DefaultMutableTreeNode("Books for Java Implementers"); -
top.add(category); -
-
//VM -
book = new DefaultMutableTreeNode(new BookInfo -
("The Java Virtual Machine Specification", -
"vm.html")); -
category.add(book); -
-
//Language Spec -
book = new DefaultMutableTreeNode(new BookInfo -
("The Java Language Specification", -
"jls.html")); -
category.add(book); - }
private void createNodes(DefaultMutableTreeNode top) { DefaultMutableTreeNode category = null; DefaultMutableTreeNode book = null; category = new DefaultMutableTreeNode("Books for Java Programmers"); top.add(category); //original Tutorial book = new DefaultMutableTreeNode(new BookInfo ("The Java Tutorial: A Short Course on the Basics", "tutorial.html")); category.add(book); //Tutorial Continued book = new DefaultMutableTreeNode(new BookInfo ("The Java Tutorial Continued: The Rest of the JDK", "tutorialcont.html")); category.add(book); //JFC Swing Tutorial book = new DefaultMutableTreeNode(new BookInfo ("The JFC Swing Tutorial: A Guide to Constructing GUIs", "swingtutorial.html")); category.add(book); //...add more books for programmers... category = new DefaultMutableTreeNode("Books for Java Implementers"); top.add(category); //VM book = new DefaultMutableTreeNode(new BookInfo ("The Java Virtual Machine Specification", "vm.html")); category.add(book); //Language Spec book = new DefaultMutableTreeNode(new BookInfo ("The Java Language Specification", "jls.html")); category.add(book); }
(2)对节点的选择作出响应
对于树节点的选择做出响应是简单的。你可以实现一个树节点选择监听器,并且注册在这棵树上。接下来的代码显示了 TreeDemo.java 中有关选择的代码:
- //Where
the tree is initialized: -
tree.getSelectionModel().setSelectionMode -
(TreeSelectionModel.SINGLE_TREE_SELECTION); -
-
//Listen for when the selection changes. -
tree.addTreeSelectionListener (this); -
... -
public void valueChanged(TreeSelectionEvent e) { -
//Returns the last path element of the selection. -
//This method is useful only when the selection model allows a single selection. -
DefaultMutableTreeNode node = (DefaultMutableTreeNode) -
tree.getLastSelectedPathCompo nent(); -
-
if (node == null) -
//Nothing is selected. -
return; -
-
Object nodeInfo = node.getUserObject(); -
if (node.isLeaf()) { -
BookInfo book = (BookInfo)nodeInfo; -
displayURL(book.bookURL); -
} else { -
displayURL(helpURL); -
} - }
//Where the tree is initialized: tree.getSelectionModel().setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION); //Listen for when the selection changes. tree.addTreeSelectionListener(this); ... public void valueChanged(TreeSelectionEvent e) { //Returns the last path element of the selection. //This method is useful only when the selection model allows a single selection. DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathCompo nent(); if (node == null) //Nothing is selected. return; Object nodeInfo = node.getUserObject(); if (node.isLeaf()) { BookInfo book = (BookInfo)nodeInfo; displayURL(book.bookURL); } else { displayURL(helpURL); } }
上面的代码执行了一下任务:
这里给出一些树节点的图片,分别通过 Java 、 Windows 和 MacOS 样式绘得。
(依次为 java look 、 windows look 和 MacOS look )
像之前图片显示一样,一棵树按照惯例,对于每个基点显示了一个图标和一些文字。像我们简短的展示一样,你可以指定这些样式。
tree.putClientProperty(“Jtree.lineStyle”, “Horizontal”);
指定 JAVA 样式在节点间不显示任何行线,则使用以下代码:
tree.putClientProperty(“Jtree.lineStyle”, “None”);
(3)自定义树的外观表现
不管你使用那种样式( java 、 windows 、 mac ) , 默认情况下,节点显示的图标决定于节点是否为叶子节点和是否可展开。例如,在 windwos 样式中,每个叶子节点的默认图标是一个点;在 JAVA 样式中,叶子节点默认图标是一个类似白纸的符号。在所有样式中,分支节点被一个文件夹符号所标识。不同样式对于可展开分支和对应的可折叠分支,可能有不同的图标。
一定你创建了这些图标,使用树的 setCellRender 方法去指定这个 DefaultTreeCellRender 来绘制它的节点。这里有一个来自 TreeIconDemo 的例子
- ImageIcon
leafIcon = createImageIcon("images/middle.gif"); - if
(leafIcon != null) { -
DefaultTreeCellRenderer renderer = -
new DefaultTreeCellRenderer(); -
renderer.setLeafIcon(leafIcon); -
tree.setCellRenderer(renderer); - }
ImageIcon leafIcon = createImageIcon("images/middle.gif"); if (leafIcon != null) { DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); renderer.setLeafIcon(leafIcon); tree.setCellRenderer(renderer); }
这是一个截图:
如果你想更精巧的控制节点图标,或者你想提供一些工具,你可以创建 DefaultTreeCellRender 的子类,然后覆盖他的getTreeCellRendererCompo
- //...where
the tree is initialized: -
//Enable tool tips. -
ToolTipManager.sharedInstance().registerComponent(tree); -
-
ImageIcon tutorialIcon = createImageIcon("images/middle.gif"); -
if (tutorialIcon != null) { -
tree.setCellRenderer(new MyRenderer(tutorialIcon)); -
} - ...
- class
MyRenderer extends DefaultTreeCellRenderer { -
Icon tutorialIcon; -
-
public MyRenderer(Icon icon) { -
tutorialIcon = icon; -
} -
-
public Component getTreeCellRendererCompo nent( -
JTree tree, -
Object value, -
boolean sel, -
boolean expanded, -
boolean leaf, -
int row, -
boolean hasFocus) { -
-
super.getTreeCellRendererCompo nent( -
tree, value, sel, -
expanded, leaf, row, -
hasFocus); -
if (leaf && isTutorialBook(value)) { -
setIcon(tutorialIcon); -
setToolTipText("This book is in the Tutorial series."); -
} else { -
setToolTipText(null); //no tool tip -
} -
-
return this; -
} -
-
protected boolean isTutorialBook(Object value) { -
DefaultMutableTreeNode node = -
(DefaultMutableTreeNode)value; -
BookInfo nodeInfo = -
(BookInfo)(node.getUserObject()); -
String title = nodeInfo.bookName; -
if (title.indexOf("Tutorial") >= 0) { -
return true; -
} -
-
return false; -
} - }
//...where the tree is initialized: //Enable tool tips. ToolTipManager.sharedInstance().registerComponent(tree); ImageIcon tutorialIcon = createImageIcon("images/middle.gif"); if (tutorialIcon != null) { tree.setCellRenderer(new MyRenderer(tutorialIcon)); } ... class MyRenderer extends DefaultTreeCellRenderer { Icon tutorialIcon; public MyRenderer(Icon icon) { tutorialIcon = icon; } public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererCompo nent( tree, value, sel, expanded, leaf, row, hasFocus); if (leaf && isTutorialBook(value)) { setIcon(tutorialIcon); setToolTipText("This book is in the Tutorial series."); } else { setToolTipText(null); //no tool tip } return this; } protected boolean isTutorialBook(Object value) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; BookInfo nodeInfo = (BookInfo)(node.getUserObject()); String title = nodeInfo.bookName; if (title.indexOf("Tutorial") >= 0) { return true; } return false; } }
你可能会疑惑单元绘制器( cell renderer )是如何工作的。当一个 tree 在话每个节点的时候,不管是 Jtree 或是他的样式表现都包含了绘制节点的代码。 Tree 可以使用 cell renderer 的绘图代码代替前者去绘制节点。例如,画一个包含字符串“ The Java Programming Language ”的叶子节点, tree 会要求 cell renderer 返回一个组件,该组件能够绘制一个包含该字符串的叶子节点。如果这个 cell renderer 是一个 DefaultTreeCellRender ,它就返回一个 label ( DefaultTreeCellRender 继承于 Jlabel ),它绘制默认的叶子节点图标,紧随一段字符串。
(4)动态地改变一棵 Tree
接下来的图片展示了一个叫 DynamicTreeDemo 的应用程序,它允许你从一颗可视 tree 中增加或者移除节点。你也可以编辑每个节点的文本。
这里给出了树初始化的代码:
- rootNode
= new DefaultMutableTreeNode("Root Node"); - treeModel
= new DefaultTreeModel(rootNode); - treeModel.addTreeModelListener(new
MyTreeModelListener()); -
- tree
= new JTree(treeModel); - tree.setEditable(true);
- tree.getSelectionModel().setSelectionMode
-
(TreeSelectionModel.SINGLE_TREE_SELECTION); - tree.setShowsRootHandles(true);
rootNode = new DefaultMutableTreeNode("Root Node"); treeModel = new DefaultTreeModel(rootNode); treeModel.addTreeModelListener(new MyTreeModelListener()); tree = new JTree(treeModel); tree.setEditable(true); tree.getSelectionModel().setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true);
- class
MyTreeModelListener implements TreeModelListener { -
public void treeNodesChanged(TreeModelEvent e) { -
DefaultMutableTreeNode node; -
node = (DefaultMutableTreeNode) -
(e.getTreePath().getLastPathComponent()); -
-
-
try { -
int index = e.getChildIndices()[0]; -
node = (DefaultMutableTreeNode) -
(node.getChildAt(index)); -
} catch (NullPointerException exc) {} -
-
System.out.println("The user has finished editing the node."); -
System.out.println("New value: " + node.getUserObject()); -
} -
public void treeNodesInserted(TreeModelEvent e) { -
} -
public void treeNodesRemoved(TreeModelEvent e) { -
} -
public void treeStructureChanged(TreeModelEvent e) { -
} - }
class MyTreeModelListener implements TreeModelListener { public void treeNodesChanged(TreeModelEvent e) { DefaultMutableTreeNode node; node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); try { int index = e.getChildIndices()[0]; node = (DefaultMutableTreeNode) (node.getChildAt(index)); } catch (NullPointerException exc) {} System.out.println("The user has finished editing the node."); System.out.println("New value: " + node.getUserObject()); } public void treeNodesInserted(TreeModelEvent e) { } public void treeNodesRemoved(TreeModelEvent e) { } public void treeStructureChanged(TreeModelEvent e) { } }
这里是一些增加按钮事件处理器(用于增加节点)的代码:
- treePanel.addObject("New
Node " + newNodeSuffix++); - ...
- public
DefaultMutableTreeNode addObject(Object child) { -
DefaultMutableTreeNode parentNode = null; -
TreePath parentPath = tree.getSelectionPath(); -
-
if (parentPath == null) { -
//There is no selection. Default to the root node. -
parentNode = rootNode; -
} else { -
parentNode = (DefaultMutableTreeNode) -
(parentPath.getLastPathComponent()); -
} -
-
return addObject(parentNode, child, true); - }
- ...
- public
DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, -
Object child, -
boolean shouldBeVisible) { -
DefaultMutableTreeNode childNode = -
new DefaultMutableTreeNode(child); -
... -
treeModel.insertNodeInto(childNode, parent, -
parent.getChildCount()); -
-
//Make sure the user can see the lovely new node. -
if (shouldBeVisible) { -
tree.scrollPathToVisible(new TreePath(childNode.getPath())); -
} -
return childNode; - }
treePanel.addObject("New Node " + newNodeSuffix++); ... public DefaultMutableTreeNode addObject(Object child) { DefaultMutableTreeNode parentNode = null; TreePath parentPath = tree.getSelectionPath(); if (parentPath == null) { //There is no selection. Default to the root node. parentNode = rootNode; } else { parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent()); } return addObject(parentNode, child, true); } ... public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) { DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child); ... treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); //Make sure the user can see the lovely new node. if (shouldBeVisible) { tree.scrollPathToVisible(new TreePath(childNode.getPath())); } return childNode; }
(5)创建一个数据模型
在 GenealogyModel.java
中,你可以找到这个自定义的 tree model 的实现。因为这个 model 通过一个 DefaultTreeModel 的子类实现,他必须实现 TreeModel 接口。这就需要实现获得节点信息的一系列方法,例如,哪个是根节点、某个节点的子孙是哪些节点。在 GenealogyModel 的例子中,每个节点表现为一个 Person 类型的对象,这是一个未实现 TreeNode 接口的自定义类。
(这里涉及的四个 java 文件都挺值得读,里面的编程思想跟技巧很值得学习)
(6)“懒加载”孩子
- class
DemoArea extends JScrollPane -
-
implements TreeWillExpandListener { -
....... -
....... -
-
private TreeNode createNodes() { -
DefaultMutableTreeNode root; -
DefaultMutableTreeNode grandparent; -
DefaultMutableTreeNode parent; -
-
root = new DefaultMutableTreeNode("San Francisco"); -
grandparent = new DefaultMutableTreeNode("Potrero Hill"); -
root.add(grandparent); -
-
parent = new DefaultMutableTreeNode("Restaurants"); -
grandparent.add(parent); -
-
dummyParent = parent; -
return root; -
-
}
class DemoArea extends JScrollPane implements TreeWillExpandListener { ....... ....... private TreeNode createNodes() { DefaultMutableTreeNode root; DefaultMutableTreeNode grandparent; DefaultMutableTreeNode parent; root = new DefaultMutableTreeNode("San Francisco"); grandparent = new DefaultMutableTreeNode("Potrero Hill"); root.add(grandparent); parent = new DefaultMutableTreeNode("Restaurants"); grandparent.add(parent); dummyParent = parent; return root; }
- TreeNode
rootNode = createNodes(); - tree
= new JTree(rootNode); -
tree.addTreeExpansionListener (this); - tree.addTreeWillExpandListene
r(this); -
....... -
....... -
setViewportView(tree);
TreeNode rootNode = createNodes(); tree = new JTree(rootNode); tree.addTreeExpansionListener(this); tree.addTreeWillExpandListene r(this); ....... ....... setViewportView(tree);
- private
void LoadLazyChildren(){ -
DefaultMutableTreeNode child; -
child = new DefaultMutableTreeNode("Thai Barbeque"); -
dummyParent.add(child); -
child = new DefaultMutableTreeNode("Goat Hill Pizza"); -
dummyParent.add(child); -
textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily"); -
} -
-
....... -
....... -
- public
void treeWillExpand(TreeExpansionEvent e) -
throws ExpandVetoException { -
saySomething("You are about to expand node ", e); -
int n = JOptionPane.showOptionDialog( -
this, willExpandText, willExpandTitle, -
JOptionPane.YES_NO_OPTION, -
JOptionPane.QUESTION_MESSAGE, -
null, -
willExpandOptions, -
willExpandOptions[1]); -
-
LoadLazyChildren(); -
}
private void LoadLazyChildren(){ DefaultMutableTreeNode child; child = new DefaultMutableTreeNode("Thai Barbeque"); dummyParent.add(child); child = new DefaultMutableTreeNode("Goat Hill Pizza"); dummyParent.add(child); textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily"); } ....... ....... public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException { saySomething("You are about to expand node ", e); int n = JOptionPane.showOptionDialog( this, willExpandText, willExpandTitle, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, willExpandOptions, willExpandOptions[1]); LoadLazyChildren(); }
(7)如何写 Tree Expansion Listener (监听器)
- private
void LoadLazyChildren(){ -
DefaultMutableTreeNode child; -
child = new DefaultMutableTreeNode("Thai Barbeque"); -
dummyParent.add(child); -
child = new DefaultMutableTreeNode("Goat Hill Pizza"); -
dummyParent.add(child); -
textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily"); -
} -
-
....... -
....... -
- public
void treeWillExpand(TreeExpansionEvent e) -
throws ExpandVetoException { -
saySomething("You are about to expand node ", e); -
int n = JOptionPane.showOptionDialog( -
this, willExpandText, willExpandTitle, -
JOptionPane.YES_NO_OPTION, -
JOptionPane.QUESTION_MESSAGE, -
null, -
willExpandOptions, -
willExpandOptions[1]); -
-
LoadLazyChildren(); -
}
private void LoadLazyChildren(){ DefaultMutableTreeNode child; child = new DefaultMutableTreeNode("Thai Barbeque"); dummyParent.add(child); child = new DefaultMutableTreeNode("Goat Hill Pizza"); dummyParent.add(child); textArea.append(" Thai Barbeque and Goat Hill Pizza are loaded lazily"); } ....... ....... public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException { saySomething("You are about to expand node ", e); int n = JOptionPane.showOptionDialog( this, willExpandText, willExpandTitle, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, willExpandOptions, willExpandOptions[1]); LoadLazyChildren(); }
(8)如何写 Tree-Will-Expand Listener