在使用Java Swing开发UI程序时,很有可能会遇到使用带复选框的树的需求,但是Java Swing并没有提供这个组件,因此如果你有这个需求,你就得自己动手实现带复选框的树。
1.在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。
2.在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。
既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。
现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:
1.如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。
2.如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。
注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,这种非手动导致的选中或者非取消选中则不适用于上述规则。
按照上述规则实现的CheckBoxTreeNode源代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
package demo; import javax.swing.tree.DefaultMutableTreeNode; public class CheckBoxTreeNode extends DefaultMutableTreeNode { protected boolean isSelected; public CheckBoxTreeNode() { this ( null ); } public CheckBoxTreeNode(Object userObject) { this (userObject, true , false ); } public CheckBoxTreeNode(Object userObject, boolean allowsChildren, boolean isSelected) { super (userObject, allowsChildren); this .isSelected = isSelected; } public boolean isSelected() { return isSelected; } public void setSelected( boolean _isSelected) { this .isSelected = _isSelected; if (_isSelected) { // 如果选中,则将其所有的子结点都选中 if (children != null ) { for (Object obj : children) { CheckBoxTreeNode node = (CheckBoxTreeNode)obj; if (_isSelected != node.isSelected()) node.setSelected(_isSelected); } } // 向上检查,如果父结点的所有子结点都被选中,那么将父结点也选中 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; // 开始检查pNode的所有子节点是否都被选中 if (pNode != null ) { int index = 0 ; for (; index < pNode.children.size(); ++ index) { CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index); if (!pChildNode.isSelected()) break ; } /* * 表明pNode所有子结点都已经选中,则选中父结点, * 该方法是一个递归方法,因此在此不需要进行迭代,因为 * 当选中父结点后,父结点本身会向上检查的。 */ if(index == pNode.children.size()) { if(pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } else { /* * 如果是取消父结点导致子结点取消,那么此时所有的子结点都应该是选择上的; * 否则就是子结点取消导致父结点取消,然后父结点取消导致需要取消子结点,但 * 是这时候是不需要取消子结点的。 */ if (children != null ) { int index = 0 ; for (; index < children.size(); ++ index) { CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index); if (!childNode.isSelected()) break ; } // 从上向下取消的时候 if (index == children.size()) { for ( int i = 0 ; i < children.size(); ++ i) { CheckBoxTreeNode node = (CheckBoxTreeNode)children.get(i); if (node.isSelected() != _isSelected) node.setSelected(_isSelected); } } } // 向上取消,只要存在一个子节点不是选上的,那么父节点就不应该被选上。 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; if (pNode != null && pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } |
第一个差异通过继承DefaultMutableTreeNode定义CheckBoxTreeNode解决了,接下来需要解决第二个差异。第二个差异是外观上的差异,JTree的每个结点是通过TreeCellRenderer进行显示的。为了解决第二个差异,我们定义一个新的类CheckBoxTreeCellRenderer,该类实现了TreeCellRenderer接口。CheckBoxTreeRenderer的源代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
package demo; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import javax.swing.tree.TreeCellRenderer; public class CheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer { protected JCheckBox check; protected CheckBoxTreeLabel label; public CheckBoxTreeCellRenderer() { setLayout( null ); add(check = new JCheckBox()); add(label = new CheckBoxTreeLabel()); check.setBackground(UIManager.getColor( "Tree.textBackground" )); label.setForeground(UIManager.getColor( "Tree.textForeground" )); } /** * 返回的是一个<code>JPanel</code>对象,该对象中包含一个<code>JCheckBox</code>对象 * 和一个<code>JLabel</code>对象。并且根据每个结点是否被选中来决定<code>JCheckBox</code> * 是否被选中。 */ @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus); setEnabled(tree.isEnabled()); check.setSelected(((CheckBoxTreeNode)value).isSelected()); label.setFont(tree.getFont()); label.setText(stringValue); label.setSelected(selected); label.setFocus(hasFocus); if (leaf) label.setIcon(UIManager.getIcon( "Tree.leafIcon" )); else if (expanded) label.setIcon(UIManager.getIcon( "Tree.openIcon" )); else label.setIcon(UIManager.getIcon( "Tree.closedIcon" )); return this ; } @Override public Dimension getPreferredSize() { Dimension dCheck = check.getPreferredSize(); Dimension dLabel = label.getPreferredSize(); return new Dimension(dCheck.width + dLabel.width, dCheck.height < dLabel.height ? dLabel.height: dCheck.height); } @Override public void doLayout() { Dimension dCheck = check.getPreferredSize(); Dimension dLabel = label.getPreferredSize(); int yCheck = 0 ; int yLabel = 0 ; if (dCheck.height < dLabel.height) yCheck = (dLabel.height - dCheck.height) / 2 ; else yLabel = (dCheck.height - dLabel.height) / 2 ; check.setLocation( 0 , yCheck); check.setBounds( 0 , yCheck, dCheck.width, dCheck.height); label.setLocation(dCheck.width, yLabel); label.setBounds(dCheck.width, yLabel, dLabel.width, dLabel.height); } @Override public void setBackground(Color color) { if (color instanceof ColorUIResource) color = null ; super .setBackground(color); } } |
在CheckBoxTreeCellRenderer的实现中,getTreeCellRendererComponent方法返回的是JPanel,而不是像DefaultTreeCellRenderer那样返回JLabel,因此JPanel中的JLabel无法对选中做出反应,因此我们重新实现了一个JLabel的子类CheckBoxTreeLabel,它可以对选中做出反应,其源代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
package demo; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; public class CheckBoxTreeLabel extends JLabel { private boolean isSelected; private boolean hasFocus; public CheckBoxTreeLabel() { } @Override public void setBackground(Color color) { if (color instanceof ColorUIResource) color = null ; super .setBackground(color); } @Override public void paint(Graphics g) { String str; if ((str = getText()) != null ) { if ( 0 < str.length()) { if (isSelected) g.setColor(UIManager.getColor( "Tree.selectionBackground" )); else g.setColor(UIManager.getColor( "Tree.textBackground" )); Dimension d = getPreferredSize(); int imageOffset = 0 ; Icon currentIcon = getIcon(); if (currentIcon != null ) imageOffset = currentIcon.getIconWidth() + Math.max( 0 , getIconTextGap() - 1 ); g.fillRect(imageOffset, 0 , d.width - 1 - imageOffset, d.height); if (hasFocus) { g.setColor(UIManager.getColor( "Tree.selectionBorderColor" )); g.drawRect(imageOffset, 0 , d.width - 1 - imageOffset, d.height - 1 ); } } } super .paint(g); } @Override public Dimension getPreferredSize() { Dimension retDimension = super .getPreferredSize(); if (retDimension != null ) retDimension = new Dimension(retDimension.width + 3 , retDimension.height); return retDimension; } public void setSelected( boolean isSelected) { this .isSelected = isSelected; } public void setFocus( boolean hasFocus) { this .hasFocus = hasFocus; } } |
通过定义CheckBoxTreeNode和CheckBoxTreeCellRenderer。我们解决了CheckBoxTree和JTree的两个根本差异,但是还有一个细节问题需要解决,就是CheckBoxTree可以响应用户事件决定是否选中某个结点。为此,我们为CheckBoxTree添加一个响应用户鼠标事件的监听器CheckBoxTreeNodeSelectionListener,该类的源代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package demo; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JTree; import javax.swing.tree.TreePath; import javax.swing.tree.DefaultTreeModel; public class CheckBoxTreeNodeSelectionListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent event) { JTree tree = (JTree)event.getSource(); int x = event.getX(); int y = event.getY(); int row = tree.getRowForLocation(x, y); TreePath path = tree.getPathForRow(row); if (path != null ) { CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent(); if (node != null ) { boolean isSelected = !node.isSelected(); node.setSelected(isSelected); ((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node); } } } } |
到此为止,CheckBoxTree所需要的所有组件都已经完成了,接下来就是如何使用这些组件。下面给出了使用这些组件的源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package demo; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultTreeModel; public class DemoMain { public static void main(String[] args) { JFrame frame = new JFrame( "CheckBoxTreeDemo" ); frame.setBounds( 200 , 200 , 400 , 400 ); JTree tree = new JTree(); CheckBoxTreeNode rootNode = new CheckBoxTreeNode( "root" ); CheckBoxTreeNode node1 = new CheckBoxTreeNode( "node_1" ); CheckBoxTreeNode node1_1 = new CheckBoxTreeNode( "node_1_1" ); CheckBoxTreeNode node1_2 = new CheckBoxTreeNode( "node_1_2" ); CheckBoxTreeNode node1_3 = new CheckBoxTreeNode( "node_1_3" ); node1.add(node1_1); node1.add(node1_2); node1.add(node1_3); CheckBoxTreeNode node2 = new CheckBoxTreeNode( "node_2" ); CheckBoxTreeNode node2_1 = new CheckBoxTreeNode( "node_2_1" ); CheckBoxTreeNode node2_2 = new CheckBoxTreeNode( "node_2_2" ); node2.add(node2_1); node2.add(node2_2); rootNode.add(node1); rootNode.add(node2); DefaultTreeModel model = new DefaultTreeModel(rootNode); tree.addMouseListener( new CheckBoxTreeNodeSelectionListener()); tree.setModel(model); tree.setCellRenderer( new CheckBoxTreeCellRenderer()); JScrollPane scroll = new JScrollPane(tree); scroll.setBounds( 0 , 0 , 300 , 320 ); frame.getContentPane().add(scroll); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible( true ); } } |
其执行结果如下图所示:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://blog.csdn.net/wangpingfang/article/details/7174540