代码基本上是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 }

 

posted on 2016-11-07 17:13  情月  阅读(1141)  评论(0编辑  收藏  举报