在总结了数人的博客和自己的探索之下,终于实现,其中主要就是TreeCellRenderer这个接口的实现,
下面代码
用的Jcreator,需要在项目文件了加入image文件夹和图片文件。
Test.java
package myprojects.test;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
public class Test extends JFrame implements TreeSelectionListener{
JButton addB, deleteB,addBS,addT;
JTree tree;
DefaultTreeModel treeModel;
DefaultMutableTreeNode leadSelection;
ImageIcon test;
JLabel label;
public Test() {
super("Tree Event Demo");
setSize(600,400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
treeModel = new DefaultTreeModel(root);
tree = new JTree(treeModel);
tree.setExpandsSelectedPaths(true);
tree.setEditable(true);
getContentPane( ).add(new JScrollPane(tree), BorderLayout.CENTER);
tree.addTreeSelectionListener(this);
MyTreeCellRenderer myCellRenderer = new MyTreeCellRenderer();
//设置叶子节点的图标
tree.setCellRenderer(myCellRenderer);
//按钮
addB = new JButton("Add a Child node");
addBS=new JButton("Add a Sibling node");
deleteB = new JButton("Delete a node");
addT=new JButton("Test");
JPanel buttonP = new JPanel( );
buttonP.add(addB);
buttonP.add(addBS);
buttonP.add(deleteB);
buttonP.add(addT);
getContentPane( ).add(buttonP, BorderLayout.SOUTH);
addB.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
String nodeName = JOptionPane.showInputDialog("New node name:");
if (leadSelection != null&&nodeName!=null) {
leadSelection.add(new DefaultMutableTreeNode(nodeName));
((DefaultTreeModel)tree.getModel( )).reload(leadSelection);
}
else {
JOptionPane.showMessageDialog(Test.this, "Error! Please select a node and input the nodename");
}
}
});
addBS.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae) {
String nodeName = JOptionPane.showInputDialog("New node name:");
if (leadSelection != null&&leadSelection.getParent()!=null&&nodeName!=null) {
((DefaultMutableTreeNode)leadSelection.getParent()).add(new DefaultMutableTreeNode(nodeName));//该步骤改为先寻找parent然后加node
((DefaultTreeModel)tree.getModel( )).reload(leadSelection.getParent());
}
else {
JOptionPane.showMessageDialog(Test.this, "Error! Please select a node, input the nodename and insure the node is not the root");
}
}
});
deleteB.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
if (leadSelection != null) {
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode) leadSelection.getParent( );
if (parent == null) {
JOptionPane.showMessageDialog(Test.this, "Can't delete root");
}
else {
parent.remove(leadSelection);
leadSelection = null;
((DefaultTreeModel)tree.getModel( )).reload(parent);
}
}
else {
JOptionPane.showMessageDialog(Test.this, "No Selection...");
}
}
});
addT.addActionListener(
new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
String nodeName = JOptionPane.showInputDialog("New node name:");
test=new ImageIcon("../image/class.PNG");
label=new JLabel();
label.setIcon(test);
label.setText(nodeName);
if (leadSelection != null&&nodeName!=null) {
leadSelection.add(new DefaultMutableTreeNode(label));
((DefaultTreeModel)tree.getModel( )).reload(leadSelection);
}
else {
JOptionPane.showMessageDialog(Test.this, "Error! Please select a node and input the nodename");
}
}
});
}
public void valueChanged(TreeSelectionEvent e) {
TreePath leadPath = e.getNewLeadSelectionPath( );
if (leadPath != null) {
leadSelection = (DefaultMutableTreeNode)leadPath.getLastPathComponent( );
}
}
public static void main(String args[]) {
//System.out.println("Starting Test...");
//Test mainFrame = new Test();
//mainFrame.setSize(400, 400);
//mainFrame.setTitle("Test");
//mainFrame.setVisible(true);
Test te = new Test( );
te.setVisible(true);
}
}
Ok,另外MyTreeCellRenderer类的代码如下MyTreeCellRenderer.java
package myprojects.test;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
class MyTreeCellRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value,boolean selected, boolean expanded,boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
if(obj instanceof JLabel) {
JLabel label = (JLabel)obj;
DefaultTreeCellRenderer tempCellRenderer = new DefaultTreeCellRenderer();
tempCellRenderer.setLeafIcon(label.getIcon());
return tempCellRenderer.getTreeCellRendererComponent(tree,label.getText(),selected,expanded,true,row,hasFocus);
}
return super.getTreeCellRendererComponent(tree,value,
selected,expanded,leaf,row,hasFocus);
}
}
整个类是gool实现的,我写了个Test用了一下而已,不过作为一个整体,理解起来比较容易。
http://blog.sina.com.cn/s/blog_47e3d38d01000716.html
JTree及JTable的一些“剖析”
1.JTree的显示
最近因为要使用JTree及JTable做一些图片的显示及操作,因为以前都是肤浅的用过,所以并为深入。现在遇到的问题如在JTree里面如何单独显示每个cell的图片,比如在JTable里面如何画出别的控件等!
在网寻求帮助,但效果并不是很好,所以自行研究了一翻,虽然并未象那些资深人士一般做出精辟的真正的剖析,但我相信我的“一些剖析”还是能帮助到一些正在,或者正要使用这两大swing控件的人。
首先来说下JTree,单就显示父节点,子节点的图片是比较简单的,我们用DefaultTreeCellRenderer这个类就能实现。调用setOpenIcon(), setCloseIcon(), setLeafIcon()三个方法,分别实现父节点打开的图片,父节点关闭的图片,以及叶节点的显示图片。然后在相应需要显示的JTree里面调用setCellRenderer(TreeCellRenderer x)即可。至于别的字体,背景色等都在DefaultTreeCellRenderer里面相应的set方面里面,大家可通过JDK自行寻找。
这上面说的方法是极为简单的实现图片的方法,一般也就足够了,但是如果你想在节点里面实现自己的控件或者每个cell都需要拥有自己图片如何操作呢?使用DefaultTreeCellRenderer是不能满足我们的要求的,至少我是没找到简便的方法!因为使用JTree的setCellRenderer(TreeCellRenderer x) 方法后,是使所有cell都使用实现了TreeCellRenderer接口的类,这里我们用的是DefaultTreeCellRenderer来进行显示的.
那么我们先来看看DefaultTreeCellRenderer是如何实现TreeCellRenderer这个接口的:
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel,
boolean expanded,
boolean leaf, int row,
boolean hasFocus) {
String stringValue = tree.convertValueToText(value, sel,
expanded, leaf, row, hasFocus);
this.tree = tree;
this.hasFocus = hasFocus;
setText(stringValue);
if(sel)
setForeground(getTextSelectionColor());
else
setForeground(getTextNonSelectionColor());
// There needs to be a way to specify disabled icons.
if (!tree.isEnabled()) {
setEnabled(false);
if (leaf) {
setDisabledIcon(getLeafIcon());
} else if (expanded) {
setDisabledIcon(getOpenIcon());
} else {
setDisabledIcon(getClosedIcon());
}
}
else {
setEnabled(true);
if (leaf) {
setIcon(getLeafIcon());
} else if (expanded) {
setIcon(getOpenIcon());
} else {
setIcon(getClosedIcon());
}
}
setComponentOrientation(tree.getComponentOrientation());
selected = sel;
return this;
}
以上的橙色部分就是DefaultTreeCellRenderer为节点设置图片的地方,这个类本就是继承JLabel并且实现的TreeCellRenderer接口,所以以上蓝色部分是把传来的value加到Label的文字部分,并且return的是this,也就是标签了。
至于getLeafIcon(),getClosedIcon(),getOpenIcon(),对应得到的值就是我们调用对应的set方法给的值.
了解到这些你是否有想法了?对了,那就是我们自己实现TreeCellRenderer接口。那么我们就能显示自己需要的图片及控件了。但是前提还有个问题需要我们了解,那就是我们的节点—DefaultMutableTreeNode;构造一个节点时可以传任意的一个类,因为构造函数是用Object类来接收的:public DefaultMutableTreeNode(Object userObject) 。所以我们可以直接传入需要的东西,比如我们需要图片加文字,就直接传入Label即可,然后让我们自己实现了TreeCellRenderer的类实现就行了。而不必担心象DefaultTreeCellRenderer一样,标签的Text来源于Node构造时传入的对象的.toString(),而图片来源于事先set的ImageIcon. 这样,我们的JTree就可以多样化了,想要什么东西就传入什么东西,而不必担心他显示不出来。
而我们即将实现的接口public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) ;
着色的形参value是个节点对象,那么这里就需要用到getUserObject()方法来获取我们构造DefaultMutableTreeNode时传入的对象,运用RTTI来识别类型,如果是我们希望的类型就做出处理,否则就象DefaultTreeCellRenderer一样,用默认的ImageIcon加上value.toString来显示.。哦,对了!节点的toString()是重写过的,return getUserObject().toString;这里解释一下一面误会!
一切准备工作搞定,以下就是实现接口的细节了,代码如下:
class MyTreeCellRenderer implements TreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value,boolean selected, boolean expanded,boolean leaf, int row,boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
if(obj instanceof JLabel) {
return (JLabel)obj;
}
if(obj instanceof JCheckBox) {
return (JCheckBox)obj;
}
if(obj instanceof JRadioButton) {
return (JRadioButton)obj;
}
label.setText(value.toString());
if (leaf)
label.setIcon(leafIcon);
else if (expanded)
label.setIcon(openIcon);
else
label.setIcon(closeIcon);
return label;
}
public void setOpenIcon (ImageIcon image) {
openIcon = image;
}
public ImageIcon getOpenIcon () {
return openIcon;
}
public void setClosedIcon(ImageIcon image) {
closeIcon = image;
}
public ImageIcon getClosedIcon() {
return closeIcon;
}
public void setLeafIcon(ImageIcon image) {
leafIcon = image;
}
public ImageIcon getLeafIcon() {
return leafIcon;
}
private JLabel label = new JLabel();
private boolean flag = true;
private ImageIcon openIcon = new ImageIcon(".\\src\\image\\open.png");
private ImageIcon closeIcon = new ImageIcon(".\\src\\image\\close.png");
private ImageIcon leafIcon = new ImageIcon(".\\src\\image\\buddy.gif");
}
这个类把TreeCellRenderer接口实现了,注意上面着色的部分,我们把自己需要用到的组件直接返回,这样就可以在给构造node的时候直接传过去了。至于一般的图文结合或者是不能识别的类型处理方式和DefaultTreeCellRenderer一样。但是最终实现是实现了,问题也有不少,比如我们选中node后颜色不会变化,让我们无法区分是否选择等等一系列问题,最重要的是如果添加的组件,如何进行组件的操作等!以下给出了解决方案!
其实实现UI细节比较烦琐,因为你传入的控件都有自己的paint()方法,这就说明你如果要实现细节那么必然要重写这些paint()方法来配合JTree显示,既然要重写这些方法那么你所传入的控件至少是从实现了paint()重写方法的类实例化而来,这就是烦琐之处。如果大家有兴趣可以参考DefaultTreeCellRenderer类来自己写这些方法!
我想也可以通过传来的控件一个个set属性也能达到目的,但是这效率就不是很高了。唯一方便的就不用从某个控件派生而来,把共有属性都写到一堆,也不唯是个好办法。
当然DefaultTreeCellRenderer类是继承的JLabel,而我当下要用的无非就是实现每个cell有自己图片而已,所以偷了一个懒,以下是我实现部分的源码。
class MyTreeCellRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value,boolean selected, boolean expanded,boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
if(obj instanceof JLabel) {
JLabel label = (JLabel)obj;
DefaultTreeCellRenderer tempCellRenderer = new DefaultTreeCellRenderer();
tempCellRenderer.setLeafIcon(label.getIcon());
return tempCellRenderer.getTreeCellRendererComponent(tree,label.getText(),
selected,expanded,true,row,hasFocus);
}
return super.getTreeCellRendererComponent(tree,value,
selected,expanded,leaf,row,hasFocus);
}
}
目的只有一个,让实现了UI细节部分的DefaultTreeCellRenderer重用而已。这样,能实现都实现了,不能实现的也实现了,标签需要什么属性直接用tempCellRenderer的set方法即可!!虽然只有JLabel而已!呵呵。
至于如何操作控件我会放到JTable操作里面一起讲,因为他们都是实现了CellEditor接口,虽然一个是从TreeCellEditor派生,一个是从TableCellEditor派生!