同JTable类似,JTree也是一个负责的组件,其周围也有大量附属接口、类,如下图所示:
一.TreeNode(树节点)
TreeNode是JTree的核心组成部分,通过TreeNode实现了Tree的基本结构。
swing提供了三个相关的接口和类。
接口总结20-1 TreeNode |
public abstract Enumeration children() —— 枚举children
public abstract TreeNode getParent() —— 获取父节点
public abstract TreeNode getChildAt(int) —— 每个节点都有index,根据index获取节点
public abstract int getChildCount() —— children数量
public abstract int getIndex(TreeNode) —— 根据节点获取index
public abstract boolean getAllowsChildren()
public abstract boolean isLeaf()
最后两个方法用来确定一个节点是文件夹,还是树叶:
(1) isLeaf()——只要此节点没有children,就返回true;
(2) getAllowsChildren()——表示是否可以拥有children,而不是目前是否拥有children。
接口总结20-2 MutableTreeNode |
扩展:TreeNode
public abstract void insert(MutableTreeNode child, int index)
public abstract void remove(int index)
public abstract void remove(MutableTreeNode child)
public abstract void removeFromParent()
public abstract void setParent(MutableTreeNode)
public abstract void setUserObject(Object)
上面列出的第一组方法用来插入和删除子节点,子节点可以通过索引或引用来删除。removeFromParent方法用来将节点从父节点中删除,并更新父节点的子节点数目。
上面列出的最后两个方法用来设置一个节点的父节点和用户对象(UserObject)。
一般情况下,UserObject是构成“树节点”的“业务”数据,比如,该节点记录了Integer、double、或String,则userObject就分别是这三种Object。比如,DefaultMutableTreeNode就定义了两个使用UserObject来进行初始化的构造函数。
DefaultMutableTreeNode类
开发人员很少直接实现TreeNode接口,这是因为Swing在DefaultMutableTreeNode类中提供了TreeNode接口的一个常用的缺省实现。数目众多的树节点扩展了DefaultMutableTreeNode。
构造方法摘要 | |
DefaultMutableTreeNode() |
|
DefaultMutableTreeNode(Object userObject) |
|
DefaultMutableTreeNode(Object userObject, boolean allowsChildren) |
|
除了接口定义的各种方法外,该类还实现了一些其他的方法,方便使用:
void | add(MutableTreeNode newChild) |
getFirstChild() | |
getFirstLeaf() | |
getLastChild() | |
getLastLeaf() | |
getNextLeaf() | |
getNextNode | |
TreeNode[] | getPath() |
getUserObject |
JTree的TreeNode设计非常成功,比如,swing.pdf有一个例子,扩展DefaultMutableTreeNode实现文件导航器,短短几十行代码就完成了,十分的漂亮。
二.TreePath(树路径)
TreePath类表示TreeNode的路径,它的内部实现其实是一个Objects 数组,每个成员都是一个TreeNode。
一般来说,我们不直接生成TreePath,而是通过TreeNode实现类(如DefaultMutableTreeNode)获取Path。
方法摘要 | |
getLastPathComponent() (即节点自身) | |
getParentPath() (即父节点的路径) | |
Object[] | getPath() |
getPathComponent(int element) | |
int | getPathCount() |
boolean | isDescendant(TreePath aTreePath) |
pathByAddingChild(Object child) | |
boolean |
三.TreeModel(树模型)
同JTable相比,TreeModel接口显得不那么重要。这是Tree的特殊性造成的——TreeNode实现Tree的物理结构。因此,TreeModel接口中最重要的作用就是跟踪Tree的根节点了。
方法摘要 | |
getRoot() | |
boolean | |
void | addTreeModelListener(TreeModelListener l) |
getChild(Object parent, int index) | |
int | getChildCount(Object parent) |
int | getIndexOfChild(Object parent,Object child) |
void | removeTreeModelListener(TreeModelListener l) |
void | valueForPathChanged(TreePath path,Object newValue) |
Swing定义了DefaultTreeModel实现TreeModel接口,它还额外实现了许多方法:
boolean | asksAllowsChildren() |
void | setAsksAllowsChildren(boolean newValue) |
void | |
TreeNode[] | getPathToRoot(TreeNode aNode) |
protected TreeNode[] | getPathToRoot(TreeNode aNode, int depth) |
| insertNodeInto |
| removeNodeFromParent |
值得一提的是TreeModelEvent,在节点更改、插入或移除时会触发此事件。
此时,Tree节点的修改已经完成:
(1) 即如果是添加的话,新添加节点已经添加好;
(2) 删除的话,已经被删除(通过该节点的getParent()无法得到父节点了,可以通过e.getPath()得到“原父节点”)
方法摘要 | |
Object[] | getPath() |
int[] | getChildIndices() |
Object[] | getChildren() |
getTreePath() |
【注】:除了ModelStructure整个改变之外,TreeModelEvent仅支持一个节点的修改(修改、删除、添加)。
当我们选取多个节点进行操作时,其实是依次激发多个TreeModelEvent。
四.TreeSelectionModel(树选取模型)
接口TreeSelectionModel的JAVA help描述如下:
此接口表示树选择组件的当前状态。有关使用树选择模型的信息和示例,请参阅 The Java Tutorial 中的How to Use Trees 一节。 树选择的状态由 TreePath 集合描述,也可以是整数集合。从 TreePath 到整数的映射通过 RowMapper 实例的方式完成。TreeSelectionModel 不一定具有 RowMapper 才可以正确地操作,但是,没有 RowMapper 的情况下,getSelectionRows 将返回 null。 可以将 TreeSelectionModel 配置为只允许一条路径 (SINGLE_TREE_SELECTION)、多条连续路径 (CONTIGUOUS_TREE_SELECTION) 或多条不连续的路径 (DISCONTIGUOUS_TREE_SELECTION)。RowMapper 用于确定 TreePath 是否为连续。没有 RowMapper 时,CONTIGUOUS_TREE_SELECTION 和 DISCONTIGUOUS_TREE_SELECTION 的功能相同,即都允许在 TreeSelectionModel 中包含任何数量的路径。 对于 CONTIGUOUS_TREE_SELECTION 选择模型,路径在任何时候更改时(通过 setSelectionPath、addSelectionPath ...),都将重新检查 TreePath,以使它们保持连续。通过调用 resetRowSelection,也可以强制检查 TreePath。如何将一组不连续的 TreePath 映射到连续集合要通过此实例的实现者强制执行特定的策略来完成。 实现应对添加到该选择中的重复 TreePath 进行组合。例如,以下代码 TreePath[] paths = new TreePath[] { treePath, treePath }; treeSelectionModel.setSelectionPaths(paths);
会导致仅选择一个路径:treePath,而不是 treePath 的两个副本。 前导 TreePath 是添加(或设置)的最后一个路径。前导行则是对应于 TreePath 的行,由 RowMapper 确定。 |
重要的方法有:
void | addPropertyChangeListener(PropertyChangeListener listener) |
void | addTreeSelectionListener(TreeSelectionListener x) |
void | addSelectionPath(TreePath path) |
void | addSelectionPaths(TreePath[] paths) |
int | getSelectionMode() |
getSelectionPath() | |
TreePath[] | getSelectionPaths() |
void | clearSelection() |
getLeadSelectionPath() |
Swing提供了默认的接口实现——DefaultTreeSelectionModel。
五.JTree
5.1.JTree的构造
通常有两种方式:
(1) 通过构造TreeNode,然后使用TreeNode来构造JTree;
JTree() |
JTree(TreeModel newModel) |
JTree(TreeNode root, boolean asksAllowsChildren) |
比如:
使用无参数的构造函数,然后再使用setRoot()方法设置Root TreeNode;
使用TreeNode构造TreeModel,然后,使用TreeModel构造JTree;
直接使用Root TreeNode 构造Tree。
(2) 使用Object[]、Vector、Hashtable直接构造
JTree(Vector<?> value) |
5.2.JTree的方法
JTree中含有大量方法,尽管很多都属于外观模式的方法——它会委托给它的各种模型来完成真正的操作。
很多方法都很有用,如:
- isRootVisible()
- setRootVisible(boolean)
- setShowsRootHandles(boolean) 节点句柄——绘制在根节点左侧的“小钥匙”
还有些属于坐标<———>Tree转换的方法,如:
- public TreePath getPathForLocation(int x, int y)
- public int getRowForLocation(int x, int y)
- public TreePath getClosestPathForLocation(int x, int y)
- public int getClosestRowForLocation(int x, int y)
Row<———>Tree转换的方法,如:
- public TreePath getPathForRow(int row)
六.TreeCellRenderer
通过JTable一样,JTree也使用一个接口定义它的单元绘制器,该接口仅有一个方法:
getTreeCellRendererComponent(JTree tree,Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) |
Swing提供了一个缺省的绘制器——DefaultTreeCellRenderer。它继承自JLabel,提供了一系列方法供改变外观,如:
getFont() | |
getLeafIcon() | |
getOpenIcon() | |
getClosedIcon() | |
void | setClosedIcon(Icon newIcon) |
void | |
void | setLeafIcon(Icon newIcon) |
void | setOpenIcon(Icon newIcon) |
void | setTextNonSelectionColor(Color newColor) |
void | setTextSelectionColor(Color newColor) |
我们可以通过这些方法来进行个性设置,也可以扩展该类,实现自己的TreeCellRender。
我们记得,DefaultTableCellRenderer提供了一个钩子方法:setValue(),可以格式化数据。遗憾的是,DefaultTreeCellRenderer没有提供这个方法。如果想要格式化数据,我们需要extends该Renderer,在返回Renderer Component之前格式化数据——按照“标准”实现的话,业务数据应该存放在UserObject中,所以可以通过UserObject获取数据,再进行格式化。
七.TreeCellEditor
TreeCellEditor接口,扩展了CellEditor,在其上只增加了一个方法:
getTreeCellEditorComponent(JTree tree,Object value, boolean isSelected, boolean expanded, boolean leaf, int row) |
Swing还提供了一个标准的实现——DefaultCellEditor(它同时还实现了TableCellEditor接口)。它有三种构造方式,使Editor组件成为三者之一:
(1) JComboBox;
(2) JCheckBox;
(3) JTextField。
如图所示,为一个ComboBox的DefaultCellEditor实现:
上图看起来不太美观——Editor完全占据了Renderer的地盘,导致节点的图标也被遮住。
为解决这个问题,Swing又提供了一个TreeCellEditor实现,即DefaultTreeCellEditor。
DefaultTreeCellEditor是个装饰者,它装饰TreeCellEditor的一个实现(通常是DefaultCellEditor),并在内部保留了DefaultTreeCellRenderer的句柄,通过Renderer得到图标,然后,返回一个包含图标和TreeCellEditor组件的容器——这样,在editing时也保留了图标,如下图所示:
它的构造函数如下所示:
构造方法摘要 | |
|
|
|
|
第一个方法没有指定TreeCellEditor,会给它创建一个包含TestEdit的DefaultTreeCellEditor。
作为一个装饰者,它将大部分的请求都交由真正的Editor对象处理,除了isCellEditable()——它使用鼠标三击作为触发Editing的手段。
八.树事件
TreeModel激发TreeModelEvent,包括节点的修改、添加、删除;改变树结构等等;
TreeSelectionModel激发TreeSelectionEvent,包括改变选取路径、清除选取等等;
JTree激发TreeExpansionEvent,对应节点的展开和折叠;
DefaultCellEditor激发ChangeEvent——当编辑停止、取消时激发,由CellEditorListener接口进行监听;
【注】:editor激发的ChangeEvent有独特的名字,如TableEditor也是如此。
除此之外,JTree还激发鼠标事件。
8.1 TreeSelectionEvent、Listener
Event方法摘要 | |
| |
| |
| |
| |
| 通过isAddedPath()来确定是要添加还是删除。 |
| |
| |
| |
前导路径(lead selection path)——即最后添加到选取的路径。
Listener方法摘要 | ||
| |
8.2 TreeExpansionEvent、Listener
Event方法摘要 |
| ||
| |||
Listener方法摘要 |
| ||
| |
| |
| |
|
除此之外,还有TreeWillExpandListener:
方法摘要 | |
| |
| |
此时,如果抛出ExpandVetoException异常,就可以否决Expansion事件。