目录
树结点
- javaSwing中使用JTree、TreeModel 来对创建树世界
- 树组件需要依赖顶层容器才能显示,意思是树组件,可以直接放到顶层容器中,Jtree 、TreeModel 这两个节点来开发计算机中的树
- Swing 使用 JTree 对象来代表一棵树,JTree树中结点可以使用 TreePath 来标识,该对象封装了当前结点及其所有的父结点。
- treepath里面包含了当前节点和其所有父节点
- treeNode【也是一个叶子节点】是一个接口,DefaultMutableTreeNode,是对接口的实现,我们使用这个,可以更好的使用其api
- jtree生成的树,不可编辑的,不可以添加、删除结点,不可改变结点数据,
JTree 的setEditable(true);
即可把这棵树变成可编辑的树(可以添加、删除结点,也可以改变结点数据) 。
- 获取当前选中节点方法有两种,通常用后者
1,通过JTree对象的某些方法,
例如 TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根结点到当前结点路径上的所有结点;
调用TreePath对象的 Object getLastPathComponent()方法,得到当前选中结点
2,调用JTree对象的 Object getLastSelectedPathComponent() 方法获取当前被选中的结点
- JTree 专门提供了 一个 TreeSelectionModel 对象来保存该 JTree 选中状态的信息 。
- JTree组件背后隐藏了两个 model 对象 , TreeModel、TreeSelectionModel,
其中TreeModel 用于保存该 JTree 的所有节点数据 , 而TreeSelectionModel 用于保存该 JTree的所有选中状态的信息 。
程序可以改变 JTree 的选择模式 , 但必须先获取该 JTree 对应的 TreeSelectionMode1 对象 , 再调用该对象的 setSelectionMode(int mode),方法来设置该JTree的选择模式
JTree上每一个节点就代表一个TreeNode 对象,TreeNode本身是一个接口,里面定义了7个有关节点的方法,其中包括判断是否为叶子节点,有几个子节点(getChileCount())、父节点是什么(getparent()等,)在实际 开发中,一般不会直接实现此接口,而是采用Java所提供的DefaultMutableTreeModel类,此类用于实现MutableTreeNode接口,并提供其他许多实用的方法,MutableTreeNode本身也是一个接口,并且继承了TreeNode接口,此类主要是定义了一些节点的处理方式,比如添加节点(insert)、删除节点(remove())、设置节点(setUserObject())等。
案例:简单树
使用JTree和TreeNode完成下图效果:
package tree;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;
public class SimpleJTree extends JFrame
{
public void init(){
Container contentPane = getContentPane();
//四个步骤
//创建对象代表节点
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("南宁");
//组装节点之间的关系
root.add(guangDong);
root.add(guangXi);
guangDong.add(foShan);
guangDong.add(shanTou);
guangXi.add(guiLin);
guangXi.add(nanNing);
//创建JTree对象
JTree tree = new JTree(root);
//把JTree放入到窗口中进行展示
contentPane.add(tree);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) {
new SimpleJTree().init();
}
}
JTree的其他外观设置方法
tree.putClientProperty( "JTree.lineStyle", "None"):设置结点之间没有连接线
tree.putClientProperty("JTree.lineStyle" , "Horizontal"):设置结点之间只有水平分割线
tree.setShowsRootHandles(true):设置根结点有"展开、折叠"图标
tree.setRootVisible(false):隐藏根结点
DefaultMutableTreeNode其他成员方法
Enumeration breadthFirstEnumerationO/preorderEnumeration(): 按广度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象 。
Enumeration depthFirstEnumerationO/postorderEnumeration(): 按深度优先的顺序遍历以此结点为根的子树,并返回所有结点组成的枚举对象 。
DefaultMutableTreeNode getNextSibling(): 返回此结点的下一个兄弟结点 。
TreeNode getParent(): 返回此结点的父结点 。 如果此结点没有父结点,则返回null 。
TreeNode[] getPath(): 返回从根结点到达此结点的所有结点组成的数组。
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 是此结点的后代,包括是此结点本身、此结点的子结点或此结点的子结点的后代,都将返回true 。
boolean isNodeRelated(DefaultMutableTreeNode aNode) : 当aNode和当前结点位于同一棵树中时返回 true 。
boolean isNodeSibling(TreeNode anotherNode): 返回anotherNode是否是当前结点的兄弟结点 。
boolean isRoot(): 返回当前结点是否是根结点 。
Enumeration pathFromAncestorEnumeration(TreeNode ancestor): 返回从指定祖先结点到当前结点的所有结点组成的枚举对象 。
案例:DefaultMutableTreeNode的成员方法练习
package Tree;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;
/**
* Created with IntelliJ IDEA.
*
* @Author: 从南到北
* @Date: 11/22/2021/17:32
* @Description:
*/
public class SimpleJTree extends JFrame
{
public void init(){
setTitle("函数的练习");
Container contentPane = getContentPane();
//四个步骤
//创建对象代表节点
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("南宁");
//组装节点之间的关系
root.add(guangdong);
root.add(guangxi);
guangdong.add(foShan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanNing);
//创建JTree对象
JTree tree = new JTree(root);
//把JTree放入到窗口中进行展示
/**
* 方法展示:
*/
System.out.println( "返回广东的父结点:"+ guangdong.getParent()); //广东的父节点是中国
System.out.println( "广东的下一个兄弟节点是广西:"+ guangdong.getNextSibling()); //广西的下一个兄弟节点是广西
System.out.println( "广东没有上一个兄弟节点:" +guangdong.getPreviousSibling());//广东没有上一个兄弟节点
System.out.println(" //返回该节点所在的根节点:" + guangdong.getRoot()); //返回该节点所在的根节点
System.out.println("返回两个节点的共同祖先:"+ guangdong.getSharedAncestor(guilin)); //
System.out.println("该节点的兄弟数量:"+ guangdong.getSiblingCount());
System.out.println("该节点是不是叶子节点:"+ guangdong.isLeaf());
System.out.println("该节点是不是当前节点的祖先节点:"+ guangdong.isNodeAncestor(root));
System.out.println("该节点是不是当前节点的儿子节点:"+ guangdong.isNodeChild(shantou));
System.out.println("获取选中节点的索引getIndex(),root节点的guangxi的索引:"+root.getIndex(guangxi));
System.out.println("getDepth()函数,获取root(中国)节点的深度为:" + root.getDepth());
contentPane.add(new JScrollPane(tree));
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) {
new SimpleJTree().init();
}
}
Object getLastSelectedPathComponent() 方法
获取当前被选中的结点
jtree的增删改查,需要用到Model对象,这个是JTree关联的模型对象,用于完成对象的增删改查,
添加兄弟结点,
添加子结点:直接调用当前结点的add方法把一个新结点添加到内部即可
删除结点:model.remove NodeFromParent(),这个方法
- 调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑,方法执行完后,会自动重绘JTree
案例:树节点增删改【重点】
使用JTree以及DefaultTreeModel、DefaultMutableTreeNode、TreePath完成下图效果:
package Tree;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Created with IntelliJ IDEA.
*
* @Author: 从南到北
* @Date: 11/22/2021/16:32
* @Description:
*
* 树节点的编辑,增删改查
*
*/
public class EditTree extends JFrame {
//定义几个初始结点
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("南宁");
//定义按钮,完成操作
JButton addSiblingBtn = new JButton("添加兄弟结点");
JButton addChildBtn = new JButton("添加子结点");
JButton deleteBtn = new JButton("删除结点");
JButton editBtn = new JButton("编辑当前结点");
JPanel panel = new JPanel();
public EditTree(){
setTitle("编辑树结点");
//通过add()方法建立父子层级关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
JTree tree = new JTree(root);
//设置JTree可编辑
tree.setEditable(true);
//获取JTree关联的数据模型TreeModel对象,里面有两个东西,所有jtree节点数据,jtree节点的选中状态信息
// 如果要修改树的内容,就要调用这个对象
//JTree关联的数据模型对象,treemodel是一个接口,得到一个实现类DefaultTreeModel
// TreeModel 是一个接口,DefaultTreeModel这个是对其的实现,
DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); //强转为这个defaultTreeModel。是方便使用api
/**
* 添加兄弟结点
*/
addSiblingBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//添加兄弟结点的逻辑
// 1, 获取当前选中节点
// DefaultMutableTreeNode这个是一个实现类
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
//如果结点为空,则直接返回
if (selectedNode == null){
return;
}
// 2,获取该选中结点的父结点
DefaultMutableTreeNode parent = (DefaultMutableTreeNode)selectedNode.getParent();
//如果父结点为空,则直接返回
if (parent==null){
return;
}
// 3,创建一个新结点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");
// 4,把新结点通过父结点进行添加
//获取选中结点的索引
int selectedIndex = parent.getIndex(selectedNode);
//在选中位置插入新结点
model.insertNodeInto(newNode,parent,selectedIndex);
// 5,显示新结点
//获取从根结点到新结点的所有结点,这个函数是返回从根节点到newNode新节点的路径
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
//使用指定的结点数组创建TreePath
TreePath treePath = new TreePath(pathToRoot);
//显示指定的treePath
tree.scrollPathToVisible(treePath); //将视野移动到这个新节点
//6,重绘tree
tree.updateUI();
}
});
/**
* 添加子节点
*/
addChildBtn.addActionListener(e -> { //兰姆达表达式写法,会更方便点
//为选中节点添加子节点
//1,获取选中结点
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode==null){
return ;
}
//2,创建新结点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");
//3,把新节点添加到当前节点中
//model.insertNodeInto(newNode,selectedNode,selectedNode.getChildCount());使用TreeModel的方法添加,不需要手动刷新UI
selectedNode.add(newNode);//使用TreeNode的方法添加,需要手动刷新UI
//4,显示新节点
//显示新结点
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
TreePath treePath = new TreePath(pathToRoot);
tree.scrollPathToVisible(treePath);
//5,重绘ui
//手动刷新UI
tree.updateUI();
});
/**
* 删除节点
*/
deleteBtn.addActionListener(e -> {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode!=null && selectedNode.getParent()!=null){
//删的时候要找到其父节点才能删,所以说,最后的那个根节点,不能删
model.removeNodeFromParent(selectedNode);
}
});
/**
* 编辑节点
*/
//实现编辑结点的监听器
editBtn.addActionListener(e -> {
//获取当前选中节点的路径
TreePath selectionPath = tree.getSelectionPath();
//判断路径不为空,则设置最后一个节点可以编辑
if (selectionPath!=null){
//编辑选中结点
tree.startEditingAtPath(selectionPath);
}
});
panel.add(addSiblingBtn);
panel.add(addChildBtn);
panel.add(deleteBtn);
panel.add(editBtn);
//add(tree); //直接传tree不好,因为树不支持滚动条,所以会不显示之后的内容
add(new JScrollPane(tree));
add(panel, BorderLayout.SOUTH); //上下
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) {
new EditTree();
}
}
监听结点事件
为JTree添加监听器:
- addTreeExpansionListener(TreeExpansionListener tel) : 添加树节点展开/折叠事件的监听器。
- addTreeSelectionListener(TreeSelectionListener tsl) : 添加树节点选择事件的监听器。
案例:打印选中结点路径
package Tree;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
public class SelectJTree {
JFrame jf = new JFrame("监听树的选择事件");
JTree tree;
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("南宁");
JTextArea eventTxt = new JTextArea(5, 20);
public void init() {
//通过add()方法建立父子层级关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
tree = new JTree(root);
//添加树节点,选择事件监听器
// tree.addTreeSelectionListener(e -> {
// if (e.getOldLeadSelectionPath()!=null){
// eventTxt.append("原选中结点的路径:"+e.getOldLeadSelectionPath().toString()+"\n");
// }
// eventTxt.append("新选中结点的路径:"+e.getNewLeadSelectionPath().toString()+"\n");
// });
//
//
//
//设置选择模式,如果要选中节点,必须要有这个
TreeSelectionModel selectionModel = tree.getSelectionModel();
selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); //单选模式
//设置监听器
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
//把当前选中节点的路径,显示到文本域中
//e里面有事件源发生的所有信息
TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
//将treePath转换成字符串,显示到文本域中
eventTxt.append(newLeadSelectionPath.toString()+"\n");
}
});
// 设置节点选中监听器【重点】
/** 上面那几句,其实可以用e.getpath()来代替的,结果是一样的,所以,建议用这种
* tree.addTreeSelectionListener(new TreeSelectionListener() {
* @Override
* public void valueChanged(TreeSelectionEvent e) {
* System.out.println("当前被选中的节点: " + e.getPath());
* }
* });
*/
// tree.setShowsRootHandles(true);
// tree.setRootVisible(true);
Box box = Box.createHorizontalBox();
box.add(new JScrollPane(tree));
box.add(new JScrollPane(eventTxt));
jf.add(box);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void main(String[] args) {
new SelectJTree().init();
}
}
参考链接:
树结点的增删改:https://www.bilibili.com/video/BV1wh411d7it?p=85
Lambda表达式:https://www.bilibili.com/video/BV1h7411v7Mq?p=75
节点监听事件(打印节点路径):https://www.bilibili.com/video/BV1wh411d7it?p=86&spm_id_from=pageDriver