目录
6.4 使用DefaultTreeCellRenderer改变结点外观
6.5 扩展DefaultTreeCellRenderer改变结点外观
6.6 实现TreeCellRenderer接口改变结点外观
6 JTree、TreeModel实现树
树也是图形用户界面使用十分广泛的GUI组件,例如使用Windows资源管理器时,将看到如下图的目录树:
如上图所示的树,代表计算机世界里的树,它从自然界中的树抽象而来。计算机世界里的树是由一系列具有严格父子关系的节点组成的,每个节点既可以是其上一级节点的子节点,也可以是下一级节点的父节点,因此同一个节点既可以是父节点,也可以是子节点。
按照节点是否包含子节点,可把节点分为两类:
普通节点:包含子节点的节点;
叶子节点:没有子节点的节点;
按照节点是否具有唯一的父节点,可把节点分为两类:
根节点:没有父节点的节点,计算机中一棵树只能有一个根节点;
普通节点:具有唯一父节点的节点;
6.1 创建树
Swing使用JTree对象代表一棵树,JTree树中节点可以使用TreePath来识别,该对象封装了当前节点及其所有的父节点。
JTree常用构造方法:
JTree(TreeModel newModel);使用指定的数据模型来创建JTree对象,它默认显示根节点
JTree(TreeNode root);使用root作为根节点创建JTree对象,它默认显示根节点
JTree(TreeNode root,boolean askAllowsChildren);使用root作为根节点创建JTree对象,它默认显示根节点。askAllowsChildren参数控制怎样的节点才算叶子节点,若参数为true,则只有当程序使用
setAllowsChildren(false);显式设置某个节点不允许添加子节点时(以后也不会有子节点),该节点才会被JTree当成叶子节点,如果该参数为false,则只要某个节点当时没有子节点(不管以后有没有子节点),该节点都会被JTree当成叶子节点
在构建目录树时,可以先创建很多DefaultMutableTreeNode对象,并调用他们的add方法构建好父级结构,最后根据根节点构建一个JTree即可。
案例:
使用JTree和TreeNode完成下图效果:
public class SimpleJTree {
JFrame jf = new JFrame("简单树");
public void init(){
//创建DefaultMutableTreeNode对象代表结点
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放入到窗口中进行展示
jf.add(tree);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new SimpleJTree().init();
}
}
6.2 编辑树结点
JTree生成的树默认是不可编辑的,不可以添加、删除结点,也不可以改变结点数据,如果想让某个JTree对象变成可编辑状态,则可以调用JTree的setEditable(boolean b)方法,传入true即可把这棵树变成可编辑的树。
编辑树结点的步骤:
1.获取当前被选中的结点:
获取当前被选中的结点,会有两种方式;
一:通过JTree对象的某些方法,例如TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根结点到当前结点路径上的所有结点;
调用TreePath对象的Object getLastPathComponent()方法,得到当前选中结点
二:调用JTree对象的Object getLastSelectedPathComponent()方法获取当前被选中的结点
2.调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑,方法执行完后,重绘JTree
案例:
使用JTree及DEfaultTreeModel、DefaultMutableTreeNode、TreePath完成下图效果:
public class EditTree {
JFrame jf = new 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("编辑当前结点");
public void init() {
//通过add()方法建立父子层级关系
guangdong.add(foshan);
guangdong.add(shantou);
guangxi.add(guilin);
guangxi.add(nanning);
root.add(guangdong);
root.add(guangxi);
JTree tree = new JTree(root);
//完成树的结点编辑的代码
tree.setEditable(true);
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
//处理添加
addSiblingBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//添加兄弟结点逻辑
//1.获取当前选中的结点
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode == null) {
return;
}
//2.获取当前结点的父结点
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) selectedNode.getParent();
if (parentNode == null) {
return;
}
//3.创建新结点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");
//4.把新结点通过父结点进行添加
int index = parentNode.getIndex(selectedNode);
model.insertNodeInto(newNode, parentNode, index);
//5.显示新结点
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
TreePath treePath = new TreePath(pathToRoot);
tree.scrollPathToVisible(treePath);
//6.重绘tree
tree.updateUI();
}
});
addChildBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//为选中结点添加子节点
//1.获取选中结点
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode == null) {
return;
}
//2.创建新结点
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("新结点");
//3.把新结点添加到当前结点中
selectedNode.add(newNode);
//4.显示新结点
TreeNode[] pathToRoot = model.getPathToRoot(newNode);
TreePath treePath = new TreePath(pathToRoot);
tree.scrollPathToVisible(treePath);
//5.重绘UI
tree.updateUI();
}
});
//处理删除
deleteBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (selectedNode != null && selectedNode.getParent() != null) {
model.removeNodeFromParent(selectedNode);
}
}
});
//处理编辑
editBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//获取当前选中结点的路径
TreePath selectionPath = tree.getSelectionPath();
//判断如果路径不为空,则设置该路径的最后一个结点可编辑
if (selectionPath != null) {
tree.startEditingAtPath(selectionPath);
}
}
});
jf.add(new JScrollPane(tree));
JPanel panel = new JPanel();
panel.add(addSiblingBtn);
panel.add(addChildBtn);
panel.add(deleteBtn);
panel.add(editBtn);
jf.add(panel, BorderLayout.SOUTH);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}
public static void main(String[] args) {
new EditTree().init();
}
}
6.3 监听结点事件
为JTree添加监听器:
1.addTreeExpansionListener(TreeExpansionListener tel);添加树节点展开/折叠事件的监听器
2.addTreeSelectionListener(TreeSelectionListener tsl);添加树节点选择事件的监听器
修改JTree的选择模式:
JTree专门提供了一个TreeSelectionModel对象来保存该JTree选中状态的信息。也就是说,JTree组件背后隐藏了两个model对象,其中TreeModel用于保存该JTree的所有结点数据,而TreeSelectionModel用于保存该JTree的所有选中状态的信息。
程序可以改变JTree的选中模式,但必须先获取该JTree对应的TreeSelectionModel对象,再调用该对象的setSelectionModel(int model);方法来设置该JTree的选择模式,其中model有如下3种取值:
1.TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;可以连续选中多个TreePath
2.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;该选项对于选择没有任何限制
3.TreeSelectionModel.SINGLE_TREE_SELECTION;每次只能选一个TreePath
案例:
实现下图效果:
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);
//TODO 设置选择模式
TreeSelectionModel selectionModel = tree.getSelectionModel();
selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
//TODO 设置监听器
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
//把当前选中结点的路径显示到文本域中
TreePath newLeadSelectionPath = e.getNewLeadSelectionPath();
eventTxt.append(newLeadSelectionPath.toString()+"\n");
}
});
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();
}
}
6.4 使用DefaultTreeCellRenderer改变结点外观
JTree默认的外观比较单一,它提供了如下几种可以改变结点外观的方式:
1.使用DefaultTreeCellRenderer直接改变结点的外观,这种方式可以改变整棵树所有结点的字体、颜色和图标。
2.为JTree指定DefaultTreeCellRenderer的扩展类对象作为JTree的结点绘制器,该绘制器负责为不同结点使用不同的字体,颜色和图标。通常使用这种方式来改变结点外观。
3.为JTree指定一个实现TreeCellRenderer接口的结点绘制器,该绘制器可以为不同的结点自由绘制任意内容,这是最复杂但最灵活的结点绘制器。
DefaultTreeCellRenderer提供了如下几个方法来修改结点的外观:
setBackgroundNonSelectionColor(Color newColor);设置用于非选定结点的背景颜色
setBackgroundSelectionColor(Color newColor);设置结点再选中状态下的背景颜色
setBorderSelectionColor(Color newColor);设置选中状态下结点的边框颜色
setClosedIcon(Icon newIcon);设置处于折叠状态下非叶子结点的图标
setFont(Font font);设置结点文本的字体
setLeafIcon(Icon newIcon);设置叶子结点的图标
setOpenIcon(Icon newIcon);设置处于展开状态下非叶子结点的图标
setTextNonSelectionColor(Color newColor);设置绘制非选中状态下结点文本的颜色
setTextSelectionColor(Color newColor);设置绘制选中状态下结点文本的颜色
案例:
使用DefaultTreeCellRenderer完成下图效果:
public class ChangeAllCellRender {
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("南宁");
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);
DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
//设置非选定结点的背景颜色
renderer.setBackgroundNonSelectionColor(new Color(220,220,220));
//设置选中结点的背景色
renderer.setBackgroundSelectionColor(new Color(140,140,140));
//设置选中状态下结点的边框颜色
renderer.setBorderSelectionColor(Color.BLACK);
//设置处于折叠状态下非叶子结点的图标
renderer.setClosedIcon(new ImageIcon("E:\\GUISwing\\img\\tree\\close.gif"));
//设置结点文本的字体
renderer.setFont(new Font("StSong",Font.BOLD,16));
//设置叶子结点图标
renderer.setLeafIcon(new ImageIcon("E:\\GUISwing\\img\\tree\\leaf.png"));
//设置处于展开状态下非叶子结点图标
renderer.setOpenIcon(new ImageIcon("E:\\GUISwing\\img\\tree\\open.gif"));
//设置绘制非选中状态下结点文本颜色
renderer.setTextNonSelectionColor(new Color(255,0,0));
//设置选中状态下结点的文本颜色
renderer.setTextSelectionColor(new Color(0,0,255));
//把结点绘制器设置给树对象
tree.setCellRenderer(renderer);
jf.add(new JScrollPane(tree));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void main(String[] args) {
new ChangeAllCellRender().init();
}
}
6.5 扩展DefaultTreeCellRenderer改变结点外观
DefaultTreeCellRenderer实现类实现了TreeCellRenderer接口,该接口里只有一个用于绘制结点内容的方法,getTreeCellRendererComponent(),该方法负责绘制JTree结点。学习JList的时候,如果要绘制JList的列表项外观的内容,需要实现ListCellRenderer接口,通过重写Component getTreeCellRendererComponent()方法返回一个Component对象,该对象就是JTree的结点组件。
DefaultTreeCellRenderer类继承了JLabel,实现getTreeCellRendererComponent()方法返回this,即返回一个特殊的JLabel对象。如果需要根据结点内容来改变结点的外观,则可以再次扩展DefaultTreeCellRenderer类,并再次重写与它提供的getTreeCellRendererComponent()方法。
案例:
自定义类继承DefaultTreeCellRenderer,重写getTreeCellRendererComponent()方法,实现下图效果:
public class ExtendsDefaultCellTreeRenderer {
JFrame jf = new JFrame("根据结点类型定义图标");
JTree tree;
//初始化5个图标
ImageIcon rootIcon = new ImageIcon("E:\\GUISwing\\img\\tree\\root.gif");
ImageIcon databaseIcon = new ImageIcon("E:\\GUISwing\\img\\tree\\database.gif");
ImageIcon tableIcon = new ImageIcon("E:\\GUISwing\\img\\tree\\table.gif");
ImageIcon columnIcon = new ImageIcon("E:\\GUISwing\\img\\tree\\column.gif");
ImageIcon indexIcon = new ImageIcon("E:\\GUISwing\\img\\tree\\index.gif");
//定义一个NodeData类,用于封装结点数据
class NodeData{
public ImageIcon icon;
public String name;
public NodeData(ImageIcon icon, String name) {
this.icon = icon;
this.name = name;
}
}
//定义几个初始结点
DefaultMutableTreeNode root = new DefaultMutableTreeNode(new NodeData(rootIcon,"数据库导航"));
DefaultMutableTreeNode salaryDb = new DefaultMutableTreeNode(new NodeData(databaseIcon,"公司工资数据库"));
DefaultMutableTreeNode customerDb = new DefaultMutableTreeNode(new NodeData(databaseIcon,"公司客户数据库"));
DefaultMutableTreeNode employee = new DefaultMutableTreeNode(new NodeData(tableIcon,"员工表"));
DefaultMutableTreeNode attend = new DefaultMutableTreeNode(new NodeData(tableIcon,"考勤表"));
DefaultMutableTreeNode concat = new DefaultMutableTreeNode(new NodeData(tableIcon,"联系方式表"));
DefaultMutableTreeNode id = new DefaultMutableTreeNode(new NodeData(indexIcon,"员工ID"));
DefaultMutableTreeNode name = new DefaultMutableTreeNode(new NodeData(columnIcon,"姓名"));
DefaultMutableTreeNode gender = new DefaultMutableTreeNode(new NodeData(columnIcon,"性别"));
public void init(){
//通过结点的add方法,建立结点的父子关系
root.add(salaryDb);
root.add(customerDb);
salaryDb.add(employee);
salaryDb.add(attend);
customerDb.add(concat);
concat.add(id);
concat.add(name);
concat.add(gender);
//创建树
tree = new JTree(root);
//TODO 通过扩展DefaultTreeCellRenderer修改外观
tree.setCellRenderer(new MyRenderer());
jf.add(new JScrollPane(tree));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
//自定义类,继承DefaultTreeCellRenderer,完成结点的绘制
private class MyRenderer extends DefaultTreeCellRenderer{
//重写方法
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
//当前类间接的继承了JLabel这个组件类,展示一张图片和一些配套的文字
//Object value这个参数,代表的就是即将要绘制的结点
//获取当前结点
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
//获取到当前即将绘制的结点的名称和图标
NodeData nodeData = (NodeData) node.getUserObject();
//通过setText方法和setIcon方法完成设置
this.setText(nodeData.name);
this.setIcon(nodeData.icon);
return this;
}
}
public static void main(String[] args) {
new ExtendsDefaultCellTreeRenderer().init();
}
}
6.6 实现TreeCellRenderer接口改变结点外观
这种改变结点外观的方法最灵活,程序实现TreeCellRenderer接口时同样需要实现
getTreeCellRendererComponent()方法,该方法可以返回任意类型的组件,该组件将作为JTree的结点。通过这种方式可以最大程度的改变结点的外观。
案例:
自定义类,继承JPanel类,实现TreeCellRenderer接口,实现下图效果:
public class CustomerTreeNode {
JFrame jf = new JFrame("定制树的结点");
JTree tree;
//定义几个初始结点
DefaultMutableTreeNode friends = new DefaultMutableTreeNode("我的好友");
DefaultMutableTreeNode qingzhao = new DefaultMutableTreeNode("李清照");
DefaultMutableTreeNode suge = new DefaultMutableTreeNode("苏格拉底");
DefaultMutableTreeNode libai = new DefaultMutableTreeNode("李白");
DefaultMutableTreeNode nongyu = new DefaultMutableTreeNode("弄玉");
DefaultMutableTreeNode hutou = new DefaultMutableTreeNode("虎头");
public void init() {
//组装视图
friends.add(qingzhao);
friends.add(suge);
friends.add(libai);
friends.add(nongyu);
friends.add(hutou);
tree = new JTree(friends);
//TODO 设置结点绘制器
MyRenderer renderer = new MyRenderer();
tree.setCellRenderer(renderer);
jf.add(new JScrollPane(tree));
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
//自定义类,实现TreeCellRenderer接口,绘制组件
private class MyRenderer extends JPanel implements TreeCellRenderer{
private ImageIcon icon;
private String name;
private Color background;
private Color foreground;
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
//给成员变量设置值
this.icon = new ImageIcon("E:\\GUISwing\\img\\tree\\"+value.toString()+".gif");
this.name = value.toString();
this.background = hasFocus? new Color(144,200,225) : new Color(255,255,255);
this.foreground = hasFocus? new Color(255,255,3) : new Color(0,0,0);
return this;
}
//通过重写getPreferenceSize方法,指定当前Jpanel组件的大小
@Override
public Dimension getPreferredSize() {
return new Dimension(80,80);
}
@Override
public void paint(Graphics g) {
//绘制组件内容
int iconWidth = this.icon.getIconWidth();
int iconHeight = this.icon.getIconHeight();
//填充背景
g.setColor(background);
g.fillRect(0,0,getWidth(),getHeight());
//绘制头像
g.drawImage(this.icon.getImage(),getWidth()/2 - iconWidth/2,10,null);
//绘制昵称
g.setColor(foreground);
g.setFont(new Font("StSong",Font.BOLD,18));
g.drawString(this.name,getWidth()/2-this.name.length()*20/2,iconHeight+30);
}
}
public static void main(String[] args) {
new CustomerTreeNode().init();
}
}