在使用Java Swing開發(fā)UI程序時(shí),很有可能會(huì)遇到使用帶復(fù)選框的樹的需求,但是Java Swing并沒有提供這個(gè)組件,因此如果你有這個(gè)需求,你就得自己動(dòng)手實(shí)現(xiàn)帶復(fù)選框的樹。
CheckBoxTree與JTree在兩個(gè)層面上存在差異:
1.在模型層上,CheckBoxTree的每個(gè)結(jié)點(diǎn)需要一個(gè)成員來保存其是否被選中,但是JTree的結(jié)點(diǎn)則不需要。
2.在視圖層上,CheckBoxTree的每個(gè)結(jié)點(diǎn)比JTree的結(jié)點(diǎn)多顯示一個(gè)復(fù)選框。
既然存在兩個(gè)差異,那么只要我們把這兩個(gè)差異部分通過自己的實(shí)現(xiàn)填補(bǔ)上,那么帶復(fù)選框的樹也就實(shí)現(xiàn)了。
現(xiàn)在開始解決第一個(gè)差異。為了解決第一個(gè)差異,需要定義一個(gè)新的結(jié)點(diǎn)類CheckBoxTreeNode,該類繼承DefaultMutableTreeNode,并增加新的成員isSelected來表示該結(jié)點(diǎn)是否被選中。對于一顆CheckBoxTree,如果某一個(gè)結(jié)點(diǎn)被選中的話,其復(fù)選框會(huì)勾選上,并且使用CheckBoxTree的動(dòng)機(jī)在于可以一次性地選中一顆子樹。那么,在選中或取消一個(gè)結(jié)點(diǎn)時(shí),其祖先結(jié)點(diǎn)和子孫結(jié)點(diǎn)應(yīng)該做出某種變化。在此,我們應(yīng)用如下遞歸規(guī)則:
1.如果某個(gè)結(jié)點(diǎn)被手動(dòng)選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被選中;如果選中該結(jié)點(diǎn)使其父節(jié)點(diǎn)的所有子結(jié)點(diǎn)都被選中,則選中其父結(jié)點(diǎn)。
2.如果某個(gè)結(jié)點(diǎn)被手動(dòng)取消選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被取消選中;如果該結(jié)點(diǎn)的父結(jié)點(diǎn)處于選中狀態(tài),則取消選中其父結(jié)點(diǎn)。
注意:上面的兩條規(guī)則是遞歸規(guī)則,當(dāng)某個(gè)結(jié)點(diǎn)發(fā)生變化,導(dǎo)致另外的結(jié)點(diǎn)發(fā)生變化時(shí),另外的結(jié)點(diǎn)也會(huì)導(dǎo)致其他的結(jié)點(diǎn)發(fā)生變化。在上面兩條規(guī)則中,強(qiáng)調(diào)手動(dòng),是因?yàn)槭謩?dòng)選中或者手動(dòng)取消選中一個(gè)結(jié)點(diǎn),會(huì)導(dǎo)致其他結(jié)點(diǎn)發(fā)生非手動(dòng)的選中或者取消選中,這種非手動(dòng)導(dǎo)致的選中或者非取消選中則不適用于上述規(guī)則。
按照上述規(guī)則實(shí)現(xiàn)的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) { // 如果選中,則將其所有的子結(jié)點(diǎn)都選中 if (children != null ) { for (Object obj : children) { CheckBoxTreeNode node = (CheckBoxTreeNode)obj; if (_isSelected != node.isSelected()) node.setSelected(_isSelected); } } // 向上檢查,如果父結(jié)點(diǎn)的所有子結(jié)點(diǎn)都被選中,那么將父結(jié)點(diǎn)也選中 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; // 開始檢查pNode的所有子節(jié)點(diǎn)是否都被選中 if (pNode != null ) { int index = 0 ; for (; index < pNode.children.size(); ++ index) { CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index); if (!pChildNode.isSelected()) break ; } /* * 表明pNode所有子結(jié)點(diǎn)都已經(jīng)選中,則選中父結(jié)點(diǎn), * 該方法是一個(gè)遞歸方法,因此在此不需要進(jìn)行迭代,因?yàn)? * 當(dāng)選中父結(jié)點(diǎn)后,父結(jié)點(diǎn)本身會(huì)向上檢查的。 */ if(index == pNode.children.size()) { if(pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } else { /* * 如果是取消父結(jié)點(diǎn)導(dǎo)致子結(jié)點(diǎn)取消,那么此時(shí)所有的子結(jié)點(diǎn)都應(yīng)該是選擇上的; * 否則就是子結(jié)點(diǎn)取消導(dǎo)致父結(jié)點(diǎn)取消,然后父結(jié)點(diǎn)取消導(dǎo)致需要取消子結(jié)點(diǎn),但 * 是這時(shí)候是不需要取消子結(jié)點(diǎn)的。 */ if (children != null ) { int index = 0 ; for (; index < children.size(); ++ index) { CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index); if (!childNode.isSelected()) break ; } // 從上向下取消的時(shí)候 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); } } } // 向上取消,只要存在一個(gè)子節(jié)點(diǎn)不是選上的,那么父節(jié)點(diǎn)就不應(yīng)該被選上。 CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; if (pNode != null && pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } |
第一個(gè)差異通過繼承DefaultMutableTreeNode定義CheckBoxTreeNode解決了,接下來需要解決第二個(gè)差異。第二個(gè)差異是外觀上的差異,JTree的每個(gè)結(jié)點(diǎn)是通過TreeCellRenderer進(jìn)行顯示的。為了解決第二個(gè)差異,我們定義一個(gè)新的類CheckBoxTreeCellRenderer,該類實(shí)現(xiàn)了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" )); } /** * 返回的是一個(gè)<code>JPanel</code>對象,該對象中包含一個(gè)<code>JCheckBox</code>對象 * 和一個(gè)<code>JLabel</code>對象。并且根據(jù)每個(gè)結(jié)點(diǎn)是否被選中來決定<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的實(shí)現(xiàn)中,getTreeCellRendererComponent方法返回的是JPanel,而不是像DefaultTreeCellRenderer那樣返回JLabel,因此JPanel中的JLabel無法對選中做出反應(yīng),因此我們重新實(shí)現(xiàn)了一個(gè)JLabel的子類CheckBoxTreeLabel,它可以對選中做出反應(yīng),其源代碼如下:
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的兩個(gè)根本差異,但是還有一個(gè)細(xì)節(jié)問題需要解決,就是CheckBoxTree可以響應(yīng)用戶事件決定是否選中某個(gè)結(jié)點(diǎn)。為此,我們?yōu)镃heckBoxTree添加一個(gè)響應(yīng)用戶鼠標(biāo)事件的監(jiān)聽器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所需要的所有組件都已經(jīng)完成了,接下來就是如何使用這些組件。下面給出了使用這些組件的源代碼:
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 ); } } |
其執(zhí)行結(jié)果如下圖所示:
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://blog.csdn.net/wangpingfang/article/details/7174540