您的位置:首页 > 其它

使用JTree增加,删除,重命名节点,延迟和即时加载树结点完整实例

2013-09-12 07:30 489 查看
首先来看一下效果,如下图所示,该目录结构树,通过在右键Pop菜单实现了添加子目录,删除目录和重命名目录的功能,当然在双击目录节点的时候也可以完成重命名的操作,之后还将了关于结点的延迟加载和及时加载的实现方法。



下面就来看一下怎样具体实现吧,关于拖拽部分已经在http://blog.csdn.net/luoshenfu001/article/details/10893673上面有完整的实现了,在此就不再累述,本文只关注树节点的增,删和重命名的实现,下面使用将整个JTree放入JScrollPanel中,然后JScrollPanle又在一个大的JPanel中。

简单的说,实现增加和删除并不太难,可以使用DefaultTreeModel的两个基本方法:treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); 和treeModel.removeNodeFromParent(currentNode);  就可以了,在注意添加的时候确保UserObject (就是放在TreeNode里面的对象)为真正的用户数据对象就可以了。

对于重命名节点的实现可谓费了很多周折,实现Oralce官方文档中‘How to use a tree’中的方法,给TreeModel添加treeModel监听器来监听TreeNode值的改变,但是在里面得到的UserObject的类型竟然变为了String,丧失了原来的自定义对象类型,非常奇怪!后来终于在StackoverFlow中找到了解决方案,那就是实现自定义的TreeModel,并修改的valueForPathChanged方法的实现,就大功告成了。

http://stackoverflow.com/questions/11554583/jtree-node-rename-preserve-user-object

看来还是外国人给力啊。

另外,还值得一提的是,不论是通过双击重命名还是右键重命名都可以使用下面这一小段来实现重命名的效果!岂不是很赞?

 if (evt.getClickCount() == 2) {
TreePath path = tree.getSelectionPath();
tree.startEditingAtPath(path);
}

本文代码中依赖的几个其他类都在上面的链接中有,想要的可以去上一篇中寻找。

package test;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.log4j.Logger;

public class TreePanel extends JPanel {

private final Logger logger = Logger.getLogger(TreePanel.class);
private static final long serialVersionUID = 1975901083214104961L;
private JTree tree;
private InnerDocsMgt mainPanel;
private List<VEachDir> dirsList = new ArrayList<VEachDir>();
private DefaultTreeModel treeModel;
private DefaultMutableTreeNode rootNode;
private Toolkit toolkit = Toolkit.getDefaultToolkit();

public TreePanel(InnerDocsMgt mainPanel) {
super();
this.mainPanel = mainPanel;
init();
}

public void init() {

GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[] { 54, 0, 0, 0, 0, 0, 0, 0 };
gbl_panel.rowHeights = new int[] { 31, 0, 0 };
gbl_panel.columnWeights = new double[] { 1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, Double.MIN_VALUE };
gbl_panel.rowWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
this.setLayout(gbl_panel);

JLabel lblNewLabel = new JLabel(
"\u5185\u90E8\u8D44\u6599\u76EE\u5F55\u5217\u8868");
GridBagConstraints gbc_lblNewLabel = new GridBagConstraints();
gbc_lblNewLabel.gridwidth = 5;
gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5);
gbc_lblNewLabel.gridx = 0;
gbc_lblNewLabel.gridy = 0;
add(lblNewLabel, gbc_lblNewLabel);

JButton saveDirsOprBt = new JButton(
"\u4FDD\u5B58\u76EE\u5F55\u64CD\u4F5C");
GridBagConstraints gbc_saveDirsOprBt = new GridBagConstraints();
gbc_saveDirsOprBt.insets = new Insets(0, 0, 5, 5);
gbc_saveDirsOprBt.gridx = 5;
gbc_saveDirsOprBt.gridy = 0;
add(saveDirsOprBt, gbc_saveDirsOprBt);
saveDirsOprBt.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {

String title = "注意!";
String message = "是否确定保存对目录的修改!";
int messageType = JOptionPane.INFORMATION_MESSAGE;
int result = JOptionPane.showConfirmDialog(mainPanel, message,
title, JOptionPane.YES_NO_OPTION, messageType);

if (result == JOptionPane.YES_OPTION) {
System.out.println("Yes");
logger.debug("用户确定了对目录的修改!");
DocsMgtRmi.getINS().saveDirChanged(dirsList);
}
}
});

JScrollPane scrollPane = new JScrollPane();
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.gridwidth = 7;
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridx = 0;
gbc_scrollPane.gridy = 1;
add(scrollPane, gbc_scrollPane);

VEachDir rootDir = new VEachDir(0, "/", 0, 123);
rootNode = new DefaultMutableTreeNode(rootDir);
createNodes(rootNode);
treeModel = new DocTreeModel(rootNode);
treeModel.addTreeModelListener(new DocTreeModelListener());
tree = new JTree(treeModel);
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);

scrollPane.setViewportView(tree);
tree.setEditable(true);
tree.setDragEnabled(true);
tree.setTransferHandler(new DocsTreeTransferHanlder(dirsList));

setPopupMenu();

}

private void createNodes(DefaultMutableTreeNode top) {

List<DefaultMutableTreeNode> nodes = DocsMgtRmi.getINS()
.getFirstLevelDirs();

for (DefaultMutableTreeNode one : nodes) {
top.add(one);
}

}

public DefaultMutableTreeNode getSelectedNode() {
return (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
}

public void setPopupMenu() {
final JPopupMenu pop = new JPopupMenu();
pop.add(new AbstractAction("添加子目录") {
private static final long serialVersionUID = 1L;

public void actionPerformed(ActionEvent e) {
System.out.println("Tree Add");

VEachDir newDir = new VEachDir(0, "新的目录", 0, 123);
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
newDir);

addObject(newDir);
}
});
pop.add(new AbstractAction("删除目录") {
private static final long serialVersionUID = 1L;

public void actionPerformed(ActionEvent e) {
System.out.println("Delete");
removeCurrentNode();
}
});

pop.add(new AbstractAction("重命名") {
private static final long serialVersionUID = 1L;

public void actionPerformed(ActionEvent e) {
TreePath path = tree.getSelectionPath();
tree.startEditingAtPath(path);
}
});

tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isMetaDown()) {
pop.show(tree, e.getX(), e.getY());
}
}

public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 1) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
mainPanel.selectOneDir();
}
});
} else if (evt.getClickCount() == 2) {
TreePath path = tree.getSelectionPath();
tree.startEditingAtPath(path);
}
}
});
}

/** Remove the currently selected node. */
public void removeCurrentNode() {
TreePath currentSelection = tree.getSelectionPath();
if (currentSelection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection
.getLastPathComponent());
MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
if (parent != null) {
treeModel.removeNodeFromParent(currentNode);
return;
}
} // Either there was no selection, or the root was selected.
toolkit.beep();
}

/** Add child to the currently selected node. */
public DefaultMutableTreeNode addObject(Object child) {
DefaultMutableTreeNode parentNode = null;
TreePath parentPath = tree.getSelectionPath();
if (parentPath == null) {
parentNode = rootNode;
} else {
parentNode = (DefaultMutableTreeNode) (parentPath
.getLastPathComponent());
}
return addObject(parentNode, child, true);
}

public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
Object child) {
return addObject(parent, child, false);
}

public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
Object child, boolean shouldBeVisible) {
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
if (parent == null) {
parent = rootNode;
}

// It is key to invoke this on the TreeModel, and NOT
// DefaultMutableTreeNode
treeModel.insertNodeInto(childNode, parent, parent.getChildCount());

// Make sure the user can see the lovely new node.
if (shouldBeVisible) {
tree.scrollPathToVisible(new TreePath(childNode.getPath()));
}
return childNode;
}

class DocTreeModelListener implements TreeModelListener {

public void treeNodesChanged(TreeModelEvent e) {

TreePath tp = e.getTreePath();
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) (tp
.getLastPathComponent());
/*
* If the event lists children, then the changed node is the child
* of the node we've already gotten. Otherwise, the changed node and
* the specified node are the same.
*/

int index = e.getChildIndices()[0];
DefaultMutableTreeNode changedNode = (DefaultMutableTreeNode) (parentNode
.getChildAt(index));

VEachDir newdir = (VEachDir) changedNode.getUserObject();
dirsList.add(newdir);

}

public void treeNodesInserted(TreeModelEvent e) {
}

public void treeNodesRemoved(TreeModelEvent e) {
}

public void treeStructureChanged(TreeModelEvent e) {
System.out.println("treeStructureChanged");
}
}

private class DocTreeModel extends DefaultTreeModel {

private static final long serialVersionUID = 922481109805944053L;

public DocTreeModel(TreeNode root) {
super(root);
}

@Override
public void valueForPathChanged(TreePath path, Object newValue) {
Object obj = ((DefaultMutableTreeNode) path.getLastPathComponent())
.getUserObject();
((VEachDir) obj).setDirName(newValue.toString());
super.valueForPathChanged(path, obj);
}
}
}


 

由于一些目录名不能被删除和重命名,这些目录是默认目录,如何能实现这样的效果呢?对于删除来说比较好实现,只要将默认目录的DirID记住然后判断,如果是默认目录就提示即可。但是对于重命名,有一个难题,就是双击的时候也会导致重命名,如何避免这样的情况呢?之前想了很多种方法都不凑效,最后想起来,可以让双击重命名的时候不生效就可以了。 如何做的呢?

双击重命名生效的时候是使用的DocTreeModel的ValueforPathChanged方法,所以要想不生效,在该方法中加上条件即可,修改后的ValueforPathChanged方法如下,通过下面的方法,可以达到即使用户双击重命名了,失去焦点后这些值还是被写回原值!

public void valueForPathChanged(TreePath path, Object newValue) {

Object obj = ((DefaultMutableTreeNode) path.getLastPathComponent())
.getUserObject();
VEachDir dir = (VEachDir) obj;
if (dir.getDirId() > 6) {
dir.setDirName(newValue.toString());
super.valueForPathChanged(path, obj);
}
}


树结点的延迟加载和即时加载:

延迟加载是值先加载一些基本的节点,剩余的节点根据用户的点击事件去触发获取节点的子节点,而即时加载是指在先将整棵树的节点关系缓存好,在树展示出来的时候就显示出了结点的关系。这涉及到了树的关系结构的构建,基本上有两种选择对应着这两种方式(1)有一个VEachDir对象中含有父亲对象的ID,那么要想获取获取所有的父子关系,则遍历VEachDir List之后,在维持一个HashMap<ParentDirId, ArrayList<VEachDir>>这样,在用户点击事件触发的时候,获取DirID, 继而通过这个HashMap就得到了所有的子,就显示在界面上了
(2)在VEachDir中不仅含有父亲ID,还位置一个ArrayList放置所有的子,这样遍历所有的VEachDir List之后,只需要知道一个根Dir就可以顺藤摸瓜得到所有的Dir Tree了。

下面的代码中,第一个就是使用延迟加载子节点的例子:

tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isMetaDown()) {
pop.show(tree, e.getX(), e.getY());
}
}

public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 1) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
mainPanel.selectOneDir();
}
});
} else if (evt.getClickCount() == 2) {
lazilyLoadChildrenNodes();
}
}
});
}

private void lazilyLoadChildrenNodes() {

TreePath treePath = tree.getSelectionPath();
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (treePath
.getLastPathComponent());

VEachDir dir = (VEachDir) currentNode.getUserObject();
int dirId = dir.getDirId();
if (dirId <= 1) {
return;
}

System.out.println("lazilyLoadChildrenNodes dirId:" + dirId
+ ",dirName:" + dir.getDirName());
List<VEachDir> dirss = DirsMgtRmi.getINS().getSubDirs(dirId);
if (dirss == null) {
return;
}
for (VEachDir one : dirss) {
DefaultMutableTreeNode oneNode = new DefaultMutableTreeNode(one);
currentNode.add(oneNode);
}
tree.expandPath(treePath);
}


第二个是及时加载的实现, 注意如上说述VEachDir含有子和父亲的信息,在实现的时候使用了递归的方式架构了所有的整棵树的信息:

private DefaultMutableTreeNode initNodes(VEachDir rootDir) {

DirsMgtRmi.getINS().addDefaultDirs(rootDir);
DirsMgtRmi.getINS().getSharedDirs();
DirsMgtRmi.getINS().getPrivateDirs(0);
return addDirNodes(rootDir);
}

private DefaultMutableTreeNode addDirNodes(VEachDir one) {

DefaultMutableTreeNode oneNode = new DefaultMutableTreeNode(one);

if (one.getChildDirs() != null) {
for (VEachDir two : one.getChildDirs()) {
oneNode.add(addDirNodes(two));
}
}
return oneNode;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐