17.6 TreeModel接口
TreeModel接口描述了基本的JTree数据模型结构。他描述了父子聚合关系,允许任何的对象成为父节点或是子节点。树有一个根节点,而所有其他的节点都是这个节点的后代。除了返回关于不同节点的信息以外,模型要求实现类管理TreeModelListener对象列表,从而当模型中的节点发生变化时可以得到通知。其他的方法,valueForPathChanged(),用来提供修改特定位置节点内容的方法。
public interface TreeModel { // Properties public Object getRoot(); // Listeners public void addTreeModelListener(TreeModelListener l); public void removeTreeModelListener(TreeModelListener l); // Instance methods public Object getChild(Object parent, int index); public int getChildCount(Object parent); public int getIndexOfChild(Object parent, Object child); public boolean isLeaf(Object node); public void valueForPathChanged(TreePath path, Object newValue); }
17.6.1 DefaultTreeModel类
JTree自动创建一个DefaultTreeModel实例来存储其数据模型。DefaultTreeModel类提供了一个在每个节点上使用TreeNode实现的TreeModel接口的实现。
除了实现TreeModel接口的方法并且管理TreeModelListener对象的列表以外,DefaultTreeModel类还添加了一些有用的方法:
- public void insertNodeInto(MutableTreeNode child, MutableParentNode parent, index int):将子节点添加到父节点的子节点集合中的索引位置。
- public void removeNodeFromParent(MutableTreeNode node):使得节点由树中移除。
- public void nodeChanged(TreeNode node):通知模型节点已经发生变化。
- public void nodesChanged(TreeNode node, int childIndices[]):通知模型节点的子节点已经发生变化。
- public void nodeStructureChanged(TreeNode node):如果节点及子节点已经发生变化则通知模型。
- public void nodesWereInserted(TreeNode node, int childIndices[]):通知模型节点作为树节点的子节点被插入。
- public void nodesWereRemoved(TreeNode node, int childIndices[], Object removedChildren[]):通知模型子节点已经被由树中移除并且在方法调用中包含节点作为参数。
- public void reload()/public void reload(TreeNode node):通知模型节点已经发生了复杂的修改并且由根节点以下或是特定节点以下的模型应重新载入。
第一对方法用于直接由树中添加或是移除节点。其他的方法用于当树节点被修改时通知树模型。如果我们不使用前两个方法的一个由树模型中插入或是移除节点,则我们要负责调用第二个集合中的方法。
17.6.2 TreeModelListener接口与TreeModelEvent类
TreeModel使用TreeModelListener来报告模型的变化。当TreeModel发送一个TreeModelEvent,所注册的监听器就会得到通知。接口包括当节点被插入,移除或是修改时的通知方法,以及当这些操作中的一个或是全部依次完成时的捕获方法。
public interface TreeModelListener implements EventListener { public void treeNodesChanged(TreeModelEvent treeModelEvent); public void treeNodesInserted(TreeModelEvent treeModelEvent); public void treeNodesRemoved(TreeModelEvent treeModelEvent); public void treeStructureChanged(TreeModelEvent treeModelEvent); }
17.7 TreeSelectionModel接口
除了所有的树支持用于排序节点的数据模型,用于显示节点的渲染器,以及用于编辑的编辑器以外,还有一个名为TreeSelectionModel的用于树元素选取操作的数据模型。TreeSelectionModel接口包含用来描述到选定节点的选定路径集合的方法。每一个路径存储在TreePath中,其中包含由根对象到选定节点的树节点的路径。我们会在稍后探讨TreePath类。
public interface TreeSelectionModel { // Constants public final static int CONTIGUOUS_TREE_SELECTION; public final static int DISCONTIGUOUS_TREE_SELECTION; public final static int SINGLE_TREE_SELECTION; // Properties public TreePath getLeadSelectionPath(); public int getLeadSelectionRow(); public int getMaxSelectionRow(); public int getMinSelectionRow(); public RowMapper getRowMapper(); public void setRowMapper(RowMapper newMapper); public int getSelectionCount(); public boolean isSelectionEmpty(); public int getSelectionMode(); public void setSelectionMode(int mode); public TreePath getSelectionPath(); public void setSelectionPath(TreePath path); public TreePath[] getSelectionPaths(); public void setSelectionPaths(TreePath paths[]); public int[] getSelectionRows(); // Listeners public void addPropertyChangeListener(PropertyChangeListener listener); public void removePropertyChangeListener(PropertyChangeListener listener); public void addTreeSelectionListener(TreeSelectionListener listener); public void removeTreeSelectionListener(TreeSelectionListener listener); // Other methods public void addSelectionPath(TreePath path); public void addSelectionPaths(TreePath paths[]); public void clearSelection(); public boolean isPathSelected(TreePath path); public boolean isRowSelected(int row); public void removeSelectionPath(TreePath path); public void removeSelectionPaths(TreePath paths[]); public void resetRowSelection(); }
TreeSelectiomModel接口支持三种选择模式,每一种模型由一个类常量指定:CONTIGUOUS_TREE_SELECTION, DISCONTIGUOUS_TREE_SELECTION或是SINGLE_TREE_SELECTION。当选择模式是CONTIGUOUS_TREE_SELECTION时,只有彼此相连的节点才会被同时选中。DISCONTIGUOUS_TREE_SELECTION模式意味着并没有同时选中的限制。而另一种选择模式,SIGNLE_TREE_SELECTION,每次只能选中一个节点。如果我们不希望任何内容被选中,可以使用null设置。这会使用受保护的JTree.EmptySelectionModel类。
注意,用于选择多个节点的按键是特定于观感类型的。尝试使用Ctrl或中Shift键组合来选择多个节点。
除了修改选择模式以外,其他的方法允许我们监视选择牟属性。有些这些方法会使用行数,而有时这些方法会使用TreePath对象。选择模型使用RowMapper来为我们将行映射到路径。抽象的AbstractLayoutCache类提供了一个RowMapper接口的基本实现,并且由FixedHeightLayoutCache与VariableHeightLayoutCache类进行特例化。我们并不需要访问或是修改RowMapper或是其实现。要将行映射到路径(或是将路径映射到行),我们仅需要请求树即可。
17.7.1 DefaultTreeSelectionModel类
DefaultTreeSelectionModel类提供了TreeSelectionModel接口的实现,这个实现初始时使用DISCONTIGUOUS_TREE_SELECTION模式并且支持所有三种选择模式。这个类引入了一些自己的方法用来获取监听器列表;其他的方法仅是实现了所有的TreeSelectionModel接口方法,包括访问表17-6中所列的11个属性的方法。另外,DefaultTreeSelectionModel重写了Object的clone()方法,从而可以Cloneable。
使用TreeSelectionModel的主要原因在于修改模型的选择模式。例如,下面的两行代码将模式修改为单选模式:
TreeSelectionModel selectionModel = tree.getSelectionModel(); selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
如果我们对找出选定路径感兴趣,我们可以直接请求JTree。我们并不需要由模型获取选定路径。
17.7.2 TreeSelectionListener接口与TreeSelectionEvent类
当树中的选定节点集合变化时,就会生成一个TreeSelectionEvent并且TreeSelectionModel所注册的TreeSelectionListener对象会得到通知。TreeSelectionListener可以注册到JTree或是直接注册到TreeSelectionModel。该接口定义如下:
public interface TreeSelectionListener implements EventListener { public void valueChanged(TreeSelectionEvent treeSelectionEvent); }
17.7.3 TreePath类
我们要探讨的最后一个类就是TreePath。在本章前面的例子中我们已经多次使用这个类。他描述了一个由根节点到另一个节点映射路径的只读节点集合,这里的根可以是一个子树的根也可以是整棵树的根。尽管有两个构造函数可以用来创建TreePath对象,我们通常仅其作为方法的返回值进行处理。我们也可以通过使用public TreePath pathByAddingChild(Object child)方法向已存在的TreePath添加元素来创建新的路径。
TreePath可以作为是一个Object数组,其中数组的第一个元素是树的根,而最后一个元素被最后的路径组件。在这两者之间是连接他们的组件。通常情况下,数组的元素是TreeNode类型。然而,因为TreeModel支持任意类型的对象,TreePath的path属性被定义为Object节点数组。表17-7列出了四个TreePath属性。
为了更好的理解TreePath,我们重用图17-18所示的树遍历示例,如图17-20所示。
17.8 Additional Expansion Events
还有两个可以注册到JTree监听器需要探讨:一个TreeExpansionListner与一个TreeWillExapndListener。
17.8.1 TreeExpansionaListener接口与TreeExpansionEvent类
如果我们对确定一个树节点何时展开与折叠,我们可以向树注册一个TreeExpansionListner。在父节点被展开或是折叠之后所注册的监听器就会得到通知。
public interface TreeExpansionListener implements EventListener { public void treeCollapse(TreeExpansionEvent treeExpansionEvent); public void treeExpand(TreeExpansionEvent treeExpansionEvent); }
每一个方法都有一个TreeExpansionEvent作为其参数。TreeExapnsionEvent类只有一个用于获取到展开或是折叠节点路径的方法:public TreePath getPath()。
17.8.2 TreeWillExpandListener接口与ExpandVetoException类
JTree支持TreeWillExpandListener的注册,其定义如下:
public interface TreeWillExpandListener implements EventListener { public void treeWillCollapse(TreeExpansionEvent treeExpansionEvent) throws ExpandVetoException; public void treeWillExpand(TreeExpansionEvent treeExpansionEvent) throws ExpandVetoException; }
这两个方法签名类似于TreeExpansionListener,并且他们都抛出ExpandVetoException。在父节点展开或是折叠之前所注册的监听器会得到通知。如果监听器不希望展开或是折叠发生,监听器可以抛出异常拒绝请求,阻止节点打开或是关闭。
为了演示TreeWillExpandListneer,下面的代码不会允许sports节点在默认的数据模型中展开或是colors节点折叠。
TreeWillExpandListener treeWillExpandListener = new TreeWillExpandListener() { public void treeWillCollapse(TreeExpansionEvent treeExpansionEvent) throws ExpandVetoException { TreePath path = treeExpansionEvent.getPath(); DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); String data = node.getUserObject().toString(); if (data.equals("colors")) { throw new ExpandVetoException(treeExpansionEvent); } } public void treeWillExpand(TreeExpansionEvent treeExpansionEvent) throws ExpandVetoException { TreePath path = treeExpansionEvent.getPath(); DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); String data = node.getUserObject().toString(); if (data.equals("sports")) { throw new ExpandVetoException(treeExpansionEvent); } } };
不要忘记使用类似下面的代码将监听器注册到树中:
tree.addTreeWillExpandListener(treeWillExpandListener)
17.9 小结
在本章中,我们了解了与JTree组件使用相关的多个类。我们了解了使用TreeCellRenderer接口与DefaultTreeCellRenderer实现的树节点渲染。我们深入了使用TreeCellEditor接口,DefaultCellEditor与DefaultTreeCellEditor实现的树节点编辑。
在审视了如何显示与编辑树之后,我们了解了用于手动创建树对象的TreeNode接口,MutableTreeNode接口,与DefaultMutableTreeNode类。我们探讨了用于存储树数据模型的的TreeModel接口与DefaultTreeModel实现,以及用于存储树选择模型的TreeSelectionModel接口与DefaultTreeSelectionModel实现。
另外,我们了解了用于各种树类的事件相关类以及用于描述节点连接路径的TreePath。
在第18章,我们将会探讨javax.swing.table包及其用于JTable组件的多个类。