12.10.2 拖动 编辑树节点
JTree默认不可编辑
JTree
生成的树默认是不可编辑的,不可以添加、删除节点,也不可以改变节点数据;
如果想让某个JTree
对象变成可编辑状态,则可以调用JTree
的setEditable(boolean b)
方法,传入true
即可把这棵树变成可编辑的树(可以添加、删除节点,也可以改变节点数据)。
方法 |
描述 |
void setEditable(boolean flag) |
Determines whether the tree is editable. |
一旦将JTree
对象设置成可编辑状态后,程序就可以为指定节点添加子节点、兄弟节点,也可以修改、删除指定节点。
前面简单提到过,JTree
处理节点有两种方式:
- 一种是根据
TreePath
;
- 另一种是根据节点的行号.
所有JTree
显示的节点都有一个唯一的行号(从0开始)。只有那些被显示出来的节点才有行号,这就带来一个潜在的问题:
如果该节点之前的节点被展开、折叠或增加、删除后,那么该节点的行号就会发生变化,因此通过行号来识别节点可能有一些不确定的地方;相反,使用TreePath
来识别节点则会更加稳定。
可以使用文件系统来类比JTree
,从图12.38中可以看出,实际上所有的文件系统都采用树状结构,其中
Windows
的文件系统是森林,因为Windows
包含C、D等多个根路径,
UNIX
、Linux
的文件系统是一棵树,只有一个根路径。
如果直接给岀abc
文件夹(类似于JTree
中的节点),系统不能准确地定位该路径;
如果给出D:\xyz\abc
,系统就可以准确地定位到该路径,这个D:\xyz\abc
实际上由三个文件夹组成:D:
、xyz
、abc
,其中D:
是该路径的根路径。类似地,TreePath
也采用这种方式来唯地标识节点。
TreePath
保持着从根节点到指定节点的所有节点,**TreePath
由一系列节点组成**,而不是单独的一个节点。
获取被选中的节点
JTree
的很多方法都用于返回一个TreePath
对象,当程序得到一个TreePath
后,可以调用TreePath
的getLastPathComponent
方法获取最后一个节点
例如:需要获得JTree
中被选定的节点,则可以通过如下两行代码来实现。
1 2 3 4
| TreePath path = tree.getselectionPath();
TreeNode target = (TreeNode) path.getLastPathcomponent();
|
TreePath 方法 |
描述 |
Object getLastPathComponent() |
Returns the last element of this path. |
获取选中的节点
又因为JTree
经常需要查询被选中的节点,所以JTree
提供了一个getLastSelectedPathComponent
方法来获取选中的节点。
比如采用下面代码也可以获取选中的节点。
JTree 方法 |
描述 |
Object getLastSelectedPathComponent() |
Returns the last path component of the selected path. |
1 2
| TreeNode target =(TreeNode) tree.getLastselectedPathComponent();
|
可能有读者对上面这行代码感到奇怪,getLastSelectedPathComponent()
方法返回的不是TreeNode
吗?getLastSelectedPathComponent
方法返回的不一定是TreeNode
,该方法的返回值是Object
。因为Swing
把JTree
设计得非常复杂,JTree
把所有的状态数据都交给TreeModel
管理,而JTree
本身并没有与TreeNode
发生关联(从图12.40可以看出这一点),只是因为DefaultTreeModel
需要TreeNode
而已,如果开发者自己提供一个TreeModel
实现类,这个TreeModel
实现类完全可以与TreeNode
没有任何关系。当然,对于大部分Swing
开发者而言,无须理会JTree
的这些过于复杂的设计。
TreeNode转成TreePath
如果已经有了从根节点到当前节点的一系列节点所组成的节点数组,也可以通过TreePath
提供的构造器将这些节点转换成TreePath
对象,如下代码所示。
1 2
| TreePath tp = new TreePath(nodes);
|
获取了选中的节点之后,即可通过DefaultTreeModel
(它是Swing
为TreeModel
提供的唯一一个实现类)提供的系列方法来插入、删除节点。DefaultTreeModel
类有一个非常优秀的设计,当使用DefaultTreeModel
插入、删除节点后,该DefaultTreeModel
会自动通知对应的JTree
重绘所有节点,用户可以立即看到程序所做的修改
也可以直接通过TreeNode
提供的方法来添加、删除和修改节点。
但通过TreeNode
改变节点时,程序必须显式调用JTree
的updateUI()
通知JTree
重绘所有节点,让用户看到程序所做的修改。
程序 增加 修改 删除节点 拖动节点
下面程序实现了增加、修改和删除节点的功能,并允许用户通过拖动将一个节点变成另一个节点的子节点。
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
| import java.awt.BorderLayout; import java.awt.event.*; import javax.swing.*; import javax.swing.tree.*;
public class EditJTree { JFrame jf; JTree tree; DefaultTreeModel model; DefaultMutableTreeNode root = new DefaultMutableTreeNode("中国"); DefaultMutableTreeNode guangdong = new DefaultMutableTreeNode("广东"); DefaultMutableTreeNode guangxi = new DefaultMutableTreeNode("广西"); DefaultMutableTreeNode foshan = new DefaultMutableTreeNode("佛山"); DefaultMutableTreeNode shantou = new DefaultMutableTreeNode("汕头"); DefaultMutableTreeNode guilin = new DefaultMutableTreeNode("桂林"); DefaultMutableTreeNode nanning = new DefaultMutableTreeNode("南宁"); TreePath movePath; JButton addSiblingButton = new JButton("添加兄弟节点"); JButton addChildButton = new JButton("添加子节点"); JButton deleteButton = new JButton("删除节点"); JButton editButton = new JButton("编辑当前节点");
public void init() { guangdong.add(foshan); guangdong.add(shantou); guangxi.add(guilin); guangxi.add(nanning); root.add(guangdong); root.add(guangxi); jf = new JFrame("可编辑节点的树"); tree = new JTree(root); model = (DefaultTreeModel) tree.getModel(); tree.setEditable(true); MouseListener ml = new MouseAdapter() { public void mousePressed(MouseEvent e) { TreePath tp = tree.getPathForLocation(e.getX(), e.getY()); if (tp != null) { movePath = tp; } }
public void mouseReleased(MouseEvent e) { TreePath tp = tree.getPathForLocation(e.getX(), e.getY()); if (tp != null && movePath != null) { if (movePath.isDescendant(tp) && movePath != tp) { JOptionPane.showMessageDialog(jf, "目标节点是被移动节点的子节点,无法移动!", "非法操作", JOptionPane.ERROR_MESSAGE); return; } else if (movePath != tp) { ((DefaultMutableTreeNode) tp.getLastPathComponent()) .add((DefaultMutableTreeNode) movePath.getLastPathComponent()); movePath = null; tree.updateUI(); } } } }; tree.addMouseListener(ml); JPanel panel = new JPanel(); addSiblingButton.addActionListener(event -> { DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (selectedNode == null) return; DefaultMutableTreeNode parent = (DefaultMutableTreeNode) selectedNode.getParent(); if (parent == null) return; DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新节点"); int selectedIndex = parent.getIndex(selectedNode); model.insertNodeInto(newNode, parent, selectedIndex + 1); TreeNode[] nodes = model.getPathToRoot(newNode); TreePath path = new TreePath(nodes); tree.scrollPathToVisible(path); }); panel.add(addSiblingButton); addChildButton.addActionListener(event -> { DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (selectedNode == null) return; DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新节点"); selectedNode.add(newNode); TreeNode[] nodes = model.getPathToRoot(newNode); TreePath path = new TreePath(nodes); tree.scrollPathToVisible(path); tree.updateUI(); }); panel.add(addChildButton); deleteButton.addActionListener(event -> { DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (selectedNode != null && selectedNode.getParent() != null) { model.removeNodeFromParent(selectedNode); } }); panel.add(deleteButton); editButton.addActionListener(event -> { TreePath selectedPath = tree.getSelectionPath(); if (selectedPath != null) { tree.startEditingAtPath(selectedPath); } }); panel.add(editButton); jf.add(new JScrollPane(tree)); jf.add(panel, BorderLayout.SOUTH); jf.pack(); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true); }
public static void main(String[] args) { new EditJTree().init(); } }
|
上面程序中实现拖动节点也比较容易:
当用户按下鼠标时获取鼠标事件发生位置的树节点,并把该节点赋给movePath
变量;
当用户松开鼠标时获取鼠标事件发生位置的树节点,作为目标节点需要拖到的父节点,把movePath
从原来的节点中删除,添加到新的父节点中即可(TreeNode
的add()
方法可以同时完成这两个操作)。
运行上面程序,会看到如图12.42所示的效果。
选中图12.42中的某个节点并双击,或者单击“编辑当前节点”按钮,就可以进入该节点的编辑状态,系统启动默认的单元格编辑器来编辑该节点,JTree
的单元格编辑器与JTable
的单元格编辑器都实现了相同的CellEditor
接口。