Java中帶復(fù)選框的樹的實現(xiàn)和應(yīng)用
在使用Java Swing開發(fā)UI程序時,很有可能會遇到使用帶復(fù)選框的樹的需求,但是Java Swing并沒有提供這個組件,因此如果你有這個需求,你就得自己動身實現(xiàn)帶復(fù)選框的樹。
CheckBoxTree與JTree在兩個層面上存在差異:
- 在模型層上,CheckBoxTree的每個結(jié)點(diǎn)需要一個成員來保存其是否被選中,但是JTree的結(jié)點(diǎn)則不需要。
- 在視圖層上,CheckBoxTree的每個結(jié)點(diǎn)比JTree的結(jié)點(diǎn)多顯示一個復(fù)選框。
既然存在兩個差異,那么只要我們把這兩個差異部分通過自己的實現(xiàn)填補(bǔ)上,那么帶復(fù)選框的樹也就實現(xiàn)了。
現(xiàn)在開始解決第一個差異。為了解決第一個差異,需要定義一個新的結(jié)點(diǎn)類CheckBoxTreeNode,該類繼承DefaultMutableTreeNode,并增加新的成員isSelected來表示該結(jié)點(diǎn)是否被選中。對于一顆CheckBoxTree,如果某一個結(jié)點(diǎn)被選中的話,其復(fù)選框會勾選上,并且使用CheckBoxTree的動機(jī)在于可以一次性地選中一顆子樹。那么,在選中或取消一個結(jié)點(diǎn)時,其祖先結(jié)點(diǎn)和子孫結(jié)點(diǎn)應(yīng)該做出某種變化。在此,我們應(yīng)用如下遞歸規(guī)則:
- 如果某個結(jié)點(diǎn)被手動選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被選中;如果選中該結(jié)點(diǎn)使其父節(jié)點(diǎn)的所有子結(jié)點(diǎn)都被選中,則選中其父結(jié)點(diǎn)。
- 如果某個結(jié)點(diǎn)被手動取消選中,那么它的所有子孫結(jié)點(diǎn)都應(yīng)該被取消選中;如果該結(jié)點(diǎn)的父結(jié)點(diǎn)處于選中狀態(tài),則取消選中其父結(jié)點(diǎn)。
注意:上面的兩條規(guī)則是遞歸規(guī)則,當(dāng)某個結(jié)點(diǎn)發(fā)生變化,導(dǎo)致另外的結(jié)點(diǎn)發(fā)生變化時,另外的結(jié)點(diǎn)也會導(dǎo)致其他的結(jié)點(diǎn)發(fā)生變化。在上面兩條規(guī)則中,強(qiáng)調(diào)手動,是因為手動選中或者手動取消選中一個結(jié)點(diǎn),會導(dǎo)致其他結(jié)點(diǎn)發(fā)生非手動的選中或者取消選中,這種非手動導(dǎo)致的選中或者非取消選中則不適用于上述規(guī)則。
按照上述規(guī)則實現(xiàn)的CheckBoxTreeNode源代碼如下:
- 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),
- * 該方法是一個遞歸方法,因此在此不需要進(jìn)行迭代,因為
- * 當(dāng)選中父結(jié)點(diǎn)后,父結(jié)點(diǎn)本身會向上檢查的。
- */
- if(index == pNode.children.size())
- {
- if(pNode.isSelected() != _isSelected)
- pNode.setSelected(_isSelected);
- }
- }
- }
- else
- {
- /*
- * 如果是取消父結(jié)點(diǎn)導(dǎo)致子結(jié)點(diǎn)取消,那么此時所有的子結(jié)點(diǎn)都應(yīng)該是選擇上的;
- * 否則就是子結(jié)點(diǎn)取消導(dǎo)致父結(jié)點(diǎn)取消,然后父結(jié)點(diǎn)取消導(dǎo)致需要取消子結(jié)點(diǎn),但
- * 是這時候是不需要取消子結(jié)點(diǎn)的。
- */
- 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);
- }
- }
- }
- // 向上取消,只要存在一個子節(jié)點(diǎn)不是選上的,那么父節(jié)點(diǎn)就不應(yīng)該被選上。
- CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent;
- if(pNode != null && pNode.isSelected() != _isSelected)
- pNode.setSelected(_isSelected);
- }
- }
- }
第一個差異通過繼承DefaultMutableTreeNode定義CheckBoxTreeNode解決了,接下來需要解決第二個差異。第二個差異是外觀上的差異,JTree的每個結(jié)點(diǎn)是通過TreeCellRenderer進(jìn)行顯示的。為了解決第二個差異,我們定義一個新的類CheckBoxTreeCellRenderer,該類實現(xiàn)了TreeCellRenderer接口。CheckBoxTreeRenderer的源代碼如下:
- 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>對象。并且根據(jù)每個結(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的實現(xiàn)中,為了處理背景色等問題,我們重新實現(xiàn)了一個JLabel的子類CheckBoxTreeLabel,其源代碼如下:
- 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的兩個根本差異,但是還有一個細(xì)節(jié)問題需要解決,就是CheckBoxTree可以響應(yīng)用戶事件決定是否選中某個結(jié)點(diǎn)。為此,我們?yōu)镃heckBoxTree添加一個響應(yīng)用戶鼠標(biāo)事件的監(jiān)聽器CheckBoxTreeNodeSelectionListener,該類的源代碼如下:
- 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)完成了,接下來就是如何使用這些組件。下面給出了使用這些組件的源代碼:
- 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é)果如下圖所示:
原文鏈接:http://blog.csdn.net/wangpingfang/article/details/7174540
【編輯推薦】