下面这段是直接摘抄的:
1.在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。
2.在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。
既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。
现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:
1.如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。
2.如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。
注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。
在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,
这种非手动导致的选中或者非取消选中则不适用于上述规则。
按照上述规则实现的CheckBoxTreeNode源代码如下:
1 package CheckBoxTree;
2 import javax.swing.tree.DefaultMutableTreeNode;
3 import java.util.Vector;
4
5 import javax.swing.tree.*;
6 import java.util.*;
7
8
9 public class CheckBoxTreeNode extends DefaultMutableTreeNode implements Comparable
10 {
11 protected boolean isSelected;
12
13 public CheckBoxTreeNode()
14 {
15 this(null);
16 }
17
18 public CheckBoxTreeNode(Object userObject)
19 {
20 this(userObject, true,false);
21 }
22
23 public void add(MutableTreeNode childNode)
24 {
25 super.add(childNode);
26 Collections.sort(super.children);
27 }
28
29 public int compareTo(Object o)
30 {
31 return this.toString().compareTo(o.toString());
32 }
33
34 public CheckBoxTreeNode(Object userObject,boolean allowsChildren, boolean isSelected)
35 {
36 super(userObject, allowsChildren);
37 this.isSelected = isSelected;
38 }
39
40 public boolean isSelected()
41 {
42 return isSelected;
43 }
44
45 public Object[] getChildNode()
46 {
47 if(children !=null)
48 {
49 return children.toArray();
50 }
51
52 return null;
53 }
54
55 public void setSelected(boolean _isSelected)
56 {
57 this.isSelected = _isSelected;
58
59 if(_isSelected)
60 {
61 // 如果选中,则将其所有的子结点都选中
62 if(children !=null)
63 {
64 for(Object obj : children)
65 {
66 CheckBoxTreeNode node = (CheckBoxTreeNode)obj;
67 if(_isSelected != node.isSelected())
68 node.setSelected(_isSelected);
69 }
70 }
71 // 向上检查,如果父结点的所有子结点都被选中,那么将父结点也选中
72 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent;
73 // 开始检查pNode的所有子节点是否都被选中
74 if(pNode != null)
75 {
76 int index =0;
77 for(; index < pNode.children.size(); ++ index)
78 {
79 CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index);
80 if(!pChildNode.isSelected())
81 break;
82 }
83 /*
84 * 表明pNode所有子结点都已经选中,则选中父结点,
85 * 该方法是一个递归方法,因此在此不需要进行迭代,因为
86 * 当选中父结点后,父结点本身会向上检查的。
87 */
88 if(index == pNode.children.size())
89 {
90 if(pNode.isSelected() != _isSelected)
91 pNode.setSelected(_isSelected);
92 }
93 }
94 }
95 else
96 {
97 /*
98 * 如果是取消父结点导致子结点取消,那么此时所有的子结点都应该是选择上的;
99 * 否则就是子结点取消导致父结点取消,然后父结点取消导致需要取消子结点,但
100 * 是这时候是不需要取消子结点的。
101 */
102 if(children !=null)
103 {
104 int index =0;
105 for(; index < children.size(); ++ index)
106 {
107 CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index);
108 if(!childNode.isSelected())
109 break;
110 }
111 // 从上向下取消的时候
112 if(index == children.size())
113 {
114 for(int i =0; i < children.size(); ++ i)
115 {
116 CheckBoxTreeNode node = (CheckBoxTreeNode)children.get(i);
117 if(node.isSelected() != _isSelected)
118 node.setSelected(_isSelected);
119 }
120 }
121 }
122
123 // 向上取消,只要存在一个子节点不是选上的,那么父节点就不应该被选上。
124 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent;
125 if(pNode != null && pNode.isSelected() != _isSelected)
126 pNode.setSelected(_isSelected);
127 }
128 }
129 }
第一个差异通过继承DefaultMutableTreeNode定义CheckBoxTreeNode解决了,接下来需要解决第二个差异。第二个差异是外观上的差异,JTree的每个结点是通过TreeCellRenderer进行显示的。为了解决第二个差异,我们定义一个新的类CheckBoxTreeCellRenderer。
该类实现了TreeCellRenderer接口。CheckBoxTreeRenderer的源代码如下:
1 package CheckBoxTree;
2 import java.awt.Color;
3 import java.awt.Component;
4 import java.awt.Dimension;
5
6 import javax.swing.JCheckBox;
7 import javax.swing.JPanel;
8 import javax.swing.JTree;
9 import javax.swing.UIManager;
10 import javax.swing.plaf.ColorUIResource;
11 import javax.swing.tree.TreeCellRenderer;
12
13 public class CheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer
14 {
15 protected JCheckBox check;
16 protected CheckBoxTreeLabel label;
17
18 public CheckBoxTreeCellRenderer()
19 {
20 setLayout(null);
21 add(check = new JCheckBox());
22 add(label = new CheckBoxTreeLabel());
23 check.setBackground(UIManager.getColor("Tree.textBackground"));
24 label.setForeground(UIManager.getColor("Tree.textForeground"));
25 }
26
27 /**
28 * 返回的是一个<code>JPanel</code>对象,该对象中包含一个<code>JCheckBox</code>对象
29 * 和一个<code>JLabel</code>对象。并且根据每个结点是否被选中来决定<code>JCheckBox</code>
30 * 是否被选中。
31 */
32 @Override
33 public Component getTreeCellRendererComponent(JTree tree, Object value,
34 boolean selected,boolean expanded, boolean leaf,int row,
35 boolean hasFocus)
36 {
37 String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
38 setEnabled(tree.isEnabled());
39 check.setSelected(((CheckBoxTreeNode)value).isSelected());
40 label.setFont(tree.getFont());
41 label.setText(stringValue);
42 label.setSelected(selected);
43 label.setFocus(hasFocus);
44 if(leaf)
45 label.setIcon(UIManager.getIcon("Tree.leafIcon"));
46 else if(expanded)
47 label.setIcon(UIManager.getIcon("Tree.openIcon"));
48 else
49 label.setIcon(UIManager.getIcon("Tree.closedIcon"));
50
51 return this;
52 }
53
54 @Override
55 public Dimension getPreferredSize()
56 {
57 Dimension dCheck = check.getPreferredSize();
58 Dimension dLabel = label.getPreferredSize();
59 return new Dimension(dCheck.width + dLabel.width, dCheck.height < dLabel.height ? dLabel.height: dCheck.height);
60 }
61
62 @Override
63 public void doLayout()
64 {
65 Dimension dCheck = check.getPreferredSize();
66 Dimension dLabel = label.getPreferredSize();
67 int yCheck = 0;
68 int yLabel = 0;
69 if(dCheck.height < dLabel.height)
70 yCheck = (dLabel.height - dCheck.height) / 2;
71 else
72 yLabel = (dCheck.height - dLabel.height) / 2;
73 check.setLocation(0, yCheck);
74 check.setBounds(0, yCheck, dCheck.width, dCheck.height);
75 label.setLocation(dCheck.width, yLabel);
76 label.setBounds(dCheck.width, yLabel, dLabel.width, dLabel.height);
77 }
78
79 @Override
80 public void setBackground(Color color)
81 {
82 if(color instanceof ColorUIResource)
83 color = null;
84 super.setBackground(color);
85 }
86 }
在CheckBoxTreeCellRenderer的实现中,getTreeCellRendererComponent方法返回的是JPanel,而不是像DefaultTreeCellRenderer那样返回JLabel,
因此JPanel中的JLabel无法对选中做出反应,因此我们重新实现了一个JLabel的子类CheckBoxTreeLabel,它可以对选中做出反应,其源代码如下:
1 package CheckBoxTree;
2 import java.awt.Color;
3 import java.awt.Dimension;
4 import java.awt.Graphics;
5
6 import javax.swing.Icon;
7 import javax.swing.JLabel;
8 import javax.swing.UIManager;
9 import javax.swing.plaf.ColorUIResource;
10
11 public class CheckBoxTreeLabel extends JLabel
12 {
13 private boolean isSelected;
14 private boolean hasFocus;
15
16 public CheckBoxTreeLabel()
17 {
18 }
19
20 @Override
21 public void setBackground(Color color)
22 {
23 if(color instanceof ColorUIResource)
24 color = null;
25 super.setBackground(color);
26 }
27
28 @Override
29 public void paint(Graphics g)
30 {
31 String str;
32 if((str = getText()) !=null)
33 {
34 if(0 < str.length())
35 {
36 if(isSelected)
37 {
38 g.setColor(UIManager.getColor("Tree.selectionBackground")); //选中的文字颜色
39 //System.out.println("XXXXX");
40 }
41 else
42 {
43 g.setColor(UIManager.getColor("Tree.textBackground"));
44 }
45
46 Dimension d = getPreferredSize();
47 int imageOffset = 0;
48 Icon currentIcon = getIcon();
49 if(currentIcon != null)
50 imageOffset = currentIcon.getIconWidth() + Math.max(0, getIconTextGap() -1);
51 g.fillRect(imageOffset, 0, d.width -1 - imageOffset, d.height);
52 if(hasFocus)
53 {
54 g.setColor(UIManager.getColor("Tree.selectionBorderColor"));
55 g.drawRect(imageOffset, 0, d.width -1 - imageOffset, d.height - 1);
56 }
57 }
58 }
59 super.paint(g);
60 }
61
62 @Override
63 public Dimension getPreferredSize()
64 {
65 Dimension retDimension = super.getPreferredSize();
66 if(retDimension !=null)
67 retDimension = new Dimension(retDimension.width +3, retDimension.height);
68 return retDimension;
69 }
70
71 public void setSelected(boolean isSelected)
72 {
73 this.isSelected = isSelected;
74 }
75
76 public void setFocus(boolean hasFocus)
77 {
78 this.hasFocus = hasFocus;
79 }
80 }
通过定义CheckBoxTreeNode和CheckBoxTreeCellRenderer。我们解决了CheckBoxTree和JTree的两个根本差异,但是还有一个细节问题需要解决,就是CheckBoxTree可以响应用户事件决定是否选中某个结点。为此,我们为CheckBoxTree添加一个响应用户鼠标事件的监听器CheckBoxTreeNodeSelectionListener,
该类的源代码如下:
1 package CheckBoxTree;
2 import java.awt.event.MouseAdapter;
3 import java.awt.event.MouseEvent;
4
5 import javax.swing.JTree;
6 import javax.swing.tree.TreePath;
7 import javax.swing.tree.DefaultTreeModel;
8
9 public class CheckBoxTreeNodeSelectionListener extends MouseAdapter
10 {
11 @Override
12 public void mouseClicked(MouseEvent event)
13 {
14 JTree tree = (JTree)event.getSource();
15 int x = event.getX();
16 int y = event.getY();
17 int row = tree.getRowForLocation(x, y);
18 System.out.println("XXXX " + tree.getLastSelectedPathComponent().toString() + " has been selected!(mouse)");
19 TreePath path = tree.getPathForRow(row);
20 if(path != null)
21 {
22 CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent();
23 if(node != null)
24 {
25 boolean isSelected = !node.isSelected();
26 node.setSelected(isSelected);
27 ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
28 }
29 }
30 }
31 }
上述是原blog中的内容。下面是一些具体的使用心得。
经过实际的测试发现,如果使用 CheckBoxTreeNodeSelectionListener 这个类来监控鼠标的点击动作在点击父节点以后,在点击任何结点都是会产生两次时间触发的。(偶现)我一直没找到问题根源。后来在使用的过程中用下面的代码代替了这个类的效果。
1 tree.addMouseListener(new MouseAdapter()
2 {
3 public void mouseClicked(MouseEvent e)
4 {
5 treeMouseClicked(e);
6 }
7 });
1 private void treeMouseClicked(MouseEvent event)
2 {
3 JTree tree = (JTree)event.getSource();
4 int x = event.getX();
5 int y = event.getY();
6 int row = tree.getRowForLocation(x, y);
7 TreePath path = tree.getPathForRow(row);
8
9 if(null != path)
10 {
11 CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent();
12 if(null != node)
13 {
14 boolean isSelected = !node.isSelected();
15 node.setSelected(isSelected);
16 ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
17 }
18 }
19 }
其本质没有什么区别。只是原有的是由tree自带的调用。后面的是由自己指定函数调用。
我把这个几个类放到一个CheckBoxTree目录下。
下面是具体的调用过程:
1 import CheckBoxTree.*;
2 import java.awt.*;
3 import javax.swing.JPanel;
4 import javax.swing.JFrame;
5 import javax.swing.JSplitPane;
6 import javax.swing.JScrollPane;
7 import javax.swing.JTree;
8 import javax.swing.tree.DefaultTreeModel;
9 import javax.swing.tree.TreePath;
10 import javax.swing.tree.TreeSelectionModel;
11 import java.awt.event.*;
12 import javax.swing.event.*;
13
14 public class WriteForBlog extends JFrame
15 {
16 private GridBagLayout gridBagLayout = new GridBagLayout();
17 private JSplitPane splitPane = new JSplitPane();
18 private GridBagLayout gridBagLayoutForCheckBoxTree = new GridBagLayout();
19 private JPanel checkBoxTreePanel = new JPanel();
20 private JTree tree = new JTree();
21 private JScrollPane ScrollPaneForTree = new JScrollPane();
22 private CheckBoxTreeNode checkBoxTreeNode = new CheckBoxTreeNode();
23 private DefaultTreeModel defaultTreeModel = new DefaultTreeModel(checkBoxTreeNode);
24
25 public WriteForBlog()
26 {
27 try
28 {
29 jbInit();
30 }
31 catch(Exception ex)
32 {
33 System.out.println(ex.getMessage());
34 }
35 }
36
37 private void jbInit() throws Exception
38 {
39 this.setLayout(gridBagLayout);
40 this.setBounds(200, 200, 1000, 600);
41
42 checkBoxTreePanel.setLayout(gridBagLayoutForCheckBoxTree);
43 splitPane.setLastDividerLocation(-1);
44 tree.setModel(defaultTreeModel);
45 tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
46 tree.addTreeSelectionListener(new TreeSelectionListener()
47 {
48 public void valueChanged(TreeSelectionEvent e)
49 {
50 tree_valueChanged(e);
51 }
52 });
53
54 tree.addMouseListener(new MouseAdapter()
55 {
56 public void mouseClicked(MouseEvent e)
57 {
58 treeMouseClicked(e);
59 }
60 });
61
62 this.add(splitPane, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0
63 ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
64 splitPane.add(checkBoxTreePanel, JSplitPane.LEFT);
65 splitPane.setDividerLocation(150);
66 checkBoxTreePanel.add(ScrollPaneForTree, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0
67 ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
68 ScrollPaneForTree.getViewport().add(tree, null);
69
70 this.loadTree();
71
72 }
73
74 private void loadTree()
75 {
76 CheckBoxTreeNode rootNode = (CheckBoxTreeNode) ( (DefaultTreeModel) tree.getModel()).getRoot();
77 CheckBoxTreeNode node1 = new CheckBoxTreeNode("node_1");
78
79 CheckBoxTreeNode node1_1 = new CheckBoxTreeNode("node_1_1");
80 CheckBoxTreeNode node1_2 = new CheckBoxTreeNode("node_1_2");
81 CheckBoxTreeNode node1_3 = new CheckBoxTreeNode("node_1_3");
82 CheckBoxTreeNode node1_5 = new CheckBoxTreeNode("node_1_5");
83 CheckBoxTreeNode node1_6 = new CheckBoxTreeNode("node_1_6");
84
85 node1.add(node1_1);
86 node1.add(node1_2);
87 node1.add(node1_3);
88 node1.add(node1_6);
89 node1.add(node1_5);
90
91 rootNode.add(node1);
92 //rootNode.add(node2);
93
94 DefaultTreeModel model = new DefaultTreeModel(node1);
95 tree.expandPath(new TreePath(rootNode.getPath()));
96 //tree.addMouseListener(new CheckBoxTreeNodeSelectionListener());
97 tree.setModel(model);
98 tree.setCellRenderer(new CheckBoxTreeCellRenderer());
99 tree.updateUI();
100 }
101
102 public static void main(String[] args)
103 {
104 WriteForBlog test = new WriteForBlog();
105 test.setVisible(true);
106 }
107
108 private void tree_valueChanged(TreeSelectionEvent e)
109 {
110
111 }
112
113 private void treeMouseClicked(MouseEvent event)
114 {
115 JTree tree = (JTree)event.getSource();
116 int x = event.getX();
117 int y = event.getY();
118 int row = tree.getRowForLocation(x, y);
119 TreePath path = tree.getPathForRow(row);
120 if(path != null)
121 {
122 CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent();
123 if(node != null)
124 {
125 boolean isSelected = !node.isSelected();
126 node.setSelected(isSelected);
127 ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);
128 }
129 }
130 }
131 }