代码基本上是copy的。只是在使用上有一些自己的想法。
先上code吧! 虽然别的地方也有。但是还是转一份给自己。
出处:http://blog.csdn.net/joy_125/article/details/20397869
下面这段是直接摘抄的:
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 }