Java中带复选框的树的实现和应用

开发 后端
在使用Java Swing开发UI程序时,很有可能会遇到使用带复选框的树的需求,但是Java Swing并没有提供这个组件,因此如果你有这个需求,你就得自己动身实现带复选框的树。

在使用Java Swing开发UI程序时,很有可能会遇到使用带复选框的树的需求,但是Java Swing并没有提供这个组件,因此如果你有这个需求,你就得自己动身实现带复选框的树。

CheckBoxTree与JTree在两个层面上存在差异:

  1. 在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。
  2. 在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。

既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。

现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:

  1. 如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。
  2. 如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。

注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,这种非手动导致的选中或者非取消选中则不适用于上述规则。

按照上述规则实现的CheckBoxTreeNode源代码如下:

  1. package demo;  
  2.  
  3. import javax.swing.tree.DefaultMutableTreeNode;  
  4.  
  5. public class CheckBoxTreeNode extends DefaultMutableTreeNode  
  6. {  
  7.     protected boolean isSelected;  
  8.       
  9.     public CheckBoxTreeNode()  
  10.     {  
  11.         this(null);  
  12.     }  
  13.       
  14.     public CheckBoxTreeNode(Object userObject)  
  15.     {  
  16.         this(userObject, truefalse);  
  17.     }  
  18.       
  19.     public CheckBoxTreeNode(Object userObject, boolean allowsChildren, boolean isSelected)  
  20.     {  
  21.         super(userObject, allowsChildren);  
  22.         this.isSelected = isSelected;  
  23.     }  
  24.  
  25.     public boolean isSelected()  
  26.     {  
  27.         return isSelected;  
  28.     }  
  29.       
  30.     public void setSelected(boolean _isSelected)  
  31.     {  
  32.         this.isSelected = _isSelected;  
  33.           
  34.         if(_isSelected)  
  35.         {  
  36.             // 如果选中,则将其所有的子结点都选中  
  37.             if(children != null)  
  38.             {  
  39.                 for(Object obj : children)  
  40.                 {  
  41.                     CheckBoxTreeNode node = (CheckBoxTreeNode)obj;  
  42.                     if(_isSelected != node.isSelected())  
  43.                         node.setSelected(_isSelected);  
  44.                 }  
  45.             }  
  46.             // 向上检查,如果父结点的所有子结点都被选中,那么将父结点也选中  
  47.             CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent;  
  48.             // 开始检查pNode的所有子节点是否都被选中  
  49.             if(pNode != null)  
  50.             {  
  51.                 int index = 0;  
  52.                 for(; index < pNode.children.size(); ++ index)  
  53.                 {  
  54.                     CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index);  
  55.                     if(!pChildNode.isSelected())  
  56.                         break;  
  57.                 }  
  58.                 /*   
  59.                  * 表明pNode所有子结点都已经选中,则选中父结点,  
  60.                  * 该方法是一个递归方法,因此在此不需要进行迭代,因为  
  61.                  * 当选中父结点后,父结点本身会向上检查的。  
  62.                  */ 
  63.                 if(index == pNode.children.size())  
  64.                 {  
  65.                     if(pNode.isSelected() != _isSelected)  
  66.                         pNode.setSelected(_isSelected);  
  67.                 }  
  68.             }  
  69.         }  
  70.         else   
  71.         {  
  72.             /*  
  73.              * 如果是取消父结点导致子结点取消,那么此时所有的子结点都应该是选择上的;  
  74.              * 否则就是子结点取消导致父结点取消,然后父结点取消导致需要取消子结点,但  
  75.              * 是这时候是不需要取消子结点的。  
  76.              */ 
  77.             if(children != null)  
  78.             {  
  79.                 int index = 0;  
  80.                 for(; index < children.size(); ++ index)  
  81.                 {  
  82.                     CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index);  
  83.                     if(!childNode.isSelected())  
  84.                         break;  
  85.                 }  
  86.                 // 从上向下取消的时候  
  87.                 if(index == children.size())  
  88.                 {  
  89.                     for(int i = 0; i < children.size(); ++ i)  
  90.                     {  
  91.                         CheckBoxTreeNode node = (CheckBoxTreeNode)children.get(i);  
  92.                         if(node.isSelected() != _isSelected)  
  93.                             node.setSelected(_isSelected);  
  94.                     }  
  95.                 }  
  96.             }  
  97.               
  98.             // 向上取消,只要存在一个子节点不是选上的,那么父节点就不应该被选上。  
  99.             CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent;  
  100.             if(pNode != null && pNode.isSelected() != _isSelected)  
  101.                 pNode.setSelected(_isSelected);  
  102.         }  
  103.     }  

 第一个差异通过继承DefaultMutableTreeNode定义CheckBoxTreeNode解决了,接下来需要解决第二个差异。第二个差异是外观上的差异,JTree的每个结点是通过TreeCellRenderer进行显示的。为了解决第二个差异,我们定义一个新的类CheckBoxTreeCellRenderer,该类实现了TreeCellRenderer接口。CheckBoxTreeRenderer的源代码如下:

  1. package demo;  
  2.  
  3. import java.awt.Color;  
  4. import java.awt.Component;  
  5. import java.awt.Dimension;  
  6.  
  7. import javax.swing.JCheckBox;  
  8. import javax.swing.JPanel;  
  9. import javax.swing.JTree;  
  10. import javax.swing.UIManager;  
  11. import javax.swing.plaf.ColorUIResource;  
  12. import javax.swing.tree.TreeCellRenderer;  
  13.  
  14. public class CheckBoxTreeCellRenderer extends JPanel implements TreeCellRenderer  
  15. {  
  16.     protected JCheckBox check;  
  17.     protected CheckBoxTreeLabel label;  
  18.       
  19.     public CheckBoxTreeCellRenderer()  
  20.     {  
  21.         setLayout(null);  
  22.         add(check = new JCheckBox());  
  23.         add(label = new CheckBoxTreeLabel());  
  24.         check.setBackground(UIManager.getColor("Tree.textBackground"));  
  25.         label.setForeground(UIManager.getColor("Tree.textForeground"));  
  26.     }  
  27.       
  28.     /**  
  29.      * 返回的是一个<code>JPanel</code>对象,该对象中包含一个<code>JCheckBox</code>对象  
  30.      * 和一个<code>JLabel</code>对象。并且根据每个结点是否被选中来决定<code>JCheckBox</code>  
  31.      * 是否被选中。  
  32.      */ 
  33.     @Override 
  34.     public Component getTreeCellRendererComponent(JTree tree, Object value,  
  35.             boolean selected, boolean expanded, boolean leaf, int row,  
  36.             boolean hasFocus)  
  37.     {  
  38.         String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus);  
  39.         setEnabled(tree.isEnabled());  
  40.         check.setSelected(((CheckBoxTreeNode)value).isSelected());  
  41.         label.setFont(tree.getFont());  
  42.         label.setText(stringValue);  
  43.         label.setSelected(selected);  
  44.         label.setFocus(hasFocus);  
  45.         if(leaf)  
  46.             label.setIcon(UIManager.getIcon("Tree.leafIcon"));  
  47.         else if(expanded)  
  48.             label.setIcon(UIManager.getIcon("Tree.openIcon"));  
  49.         else 
  50.             label.setIcon(UIManager.getIcon("Tree.closedIcon"));  
  51.               
  52.         return this;  
  53.     }  
  54.  
  55.     @Override 
  56.     public Dimension getPreferredSize()  
  57.     {  
  58.         Dimension dCheck = check.getPreferredSize();  
  59.         Dimension dLabel = label.getPreferredSize();  
  60.         return new Dimension(dCheck.width + dLabel.width, dCheck.height < dLabel.height ? dLabel.height: dCheck.height);  
  61.     }  
  62.       
  63.     @Override 
  64.     public void doLayout()  
  65.     {  
  66.         Dimension dCheck = check.getPreferredSize();  
  67.         Dimension dLabel = label.getPreferredSize();  
  68.         int yCheck = 0;  
  69.         int yLabel = 0;  
  70.         if(dCheck.height < dLabel.height)  
  71.             yCheck = (dLabel.height - dCheck.height) / 2;  
  72.         else 
  73.             yLabel = (dCheck.height - dLabel.height) / 2;  
  74.         check.setLocation(0, yCheck);  
  75.         check.setBounds(0, yCheck, dCheck.width, dCheck.height);  
  76.         label.setLocation(dCheck.width, yLabel);  
  77.         label.setBounds(dCheck.width, yLabel, dLabel.width, dLabel.height);  
  78.     }  
  79.       
  80.     @Override 
  81.     public void setBackground(Color color)  
  82.     {  
  83.         if(color instanceof ColorUIResource)  
  84.             color = null;  
  85.         super.setBackground(color);  
  86.     }  

在CheckBoxTreeCellRenderer的实现中,为了处理背景色等问题,我们重新实现了一个JLabel的子类CheckBoxTreeLabel,其源代码如下:

  1. package demo;  
  2.  
  3. import java.awt.Color;  
  4. import java.awt.Dimension;  
  5. import java.awt.Graphics;  
  6.  
  7. import javax.swing.Icon;  
  8. import javax.swing.JLabel;  
  9. import javax.swing.UIManager;  
  10. import javax.swing.plaf.ColorUIResource;  
  11.  
  12. public class CheckBoxTreeLabel extends JLabel  
  13. {  
  14.     private boolean isSelected;  
  15.     private boolean hasFocus;  
  16.       
  17.     public CheckBoxTreeLabel()  
  18.     {  
  19.     }  
  20.       
  21.     @Override 
  22.     public void setBackground(Color color)  
  23.     {  
  24.         if(color instanceof ColorUIResource)  
  25.             color = null;  
  26.         super.setBackground(color);  
  27.     }  
  28.       
  29.     @Override 
  30.     public void paint(Graphics g)  
  31.     {  
  32.         String str;  
  33.         if((str = getText()) != null)  
  34.         {  
  35.             if(0 < str.length())  
  36.             {  
  37.                 if(isSelected)  
  38.                     g.setColor(UIManager.getColor("Tree.selectionBackground"));  
  39.                 else 
  40.                     g.setColor(UIManager.getColor("Tree.textBackground"));  
  41.                 Dimension d = getPreferredSize();  
  42.                 int imageOffset = 0;  
  43.                 Icon currentIcon = getIcon();  
  44.                 if(currentIcon != null)  
  45.                     imageOffset = currentIcon.getIconWidth() + Math.max(0, getIconTextGap() - 1);  
  46.                 g.fillRect(imageOffset, 0, d.width - 1 - imageOffset, d.height);  
  47.                 if(hasFocus)  
  48.                 {  
  49.                     g.setColor(UIManager.getColor("Tree.selectionBorderColor"));  
  50.                     g.drawRect(imageOffset, 0, d.width - 1 - imageOffset, d.height - 1);  
  51.                 }  
  52.             }  
  53.         }  
  54.         super.paint(g);  
  55.     }  
  56.       
  57.     @Override 
  58.     public Dimension getPreferredSize()  
  59.     {  
  60.         Dimension retDimension = super.getPreferredSize();  
  61.         if(retDimension != null)  
  62.             retDimension = new Dimension(retDimension.width + 3, retDimension.height);  
  63.         return retDimension;  
  64.     }  
  65.       
  66.     public void setSelected(boolean isSelected)  
  67.     {  
  68.         this.isSelected = isSelected;  
  69.     }  
  70.       
  71.     public void setFocus(boolean hasFocus)  
  72.     {  
  73.         this.hasFocus = hasFocus;  
  74.     }  

通过定义CheckBoxTreeNode和CheckBoxTreeCellRenderer。我们解决了CheckBoxTree和JTree的两个根本差异,但是还有一个细节问题需要解决,就是CheckBoxTree可以响应用户事件决定是否选中某个结点。为此,我们为CheckBoxTree添加一个响应用户鼠标事件的监听器CheckBoxTreeNodeSelectionListener,该类的源代码如下:

  1. package demo;  
  2.  
  3. import java.awt.event.MouseAdapter;  
  4. import java.awt.event.MouseEvent;  
  5.  
  6. import javax.swing.JTree;  
  7. import javax.swing.tree.TreePath;  
  8. import javax.swing.tree.DefaultTreeModel;  
  9.  
  10. public class CheckBoxTreeNodeSelectionListener extends MouseAdapter  
  11. {  
  12.     @Override 
  13.     public void mouseClicked(MouseEvent event)  
  14.     {  
  15.         JTree tree = (JTree)event.getSource();  
  16.         int x = event.getX();  
  17.         int y = event.getY();  
  18.         int row = tree.getRowForLocation(x, y);  
  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.     }  

到此为止,CheckBoxTree所需要的所有组件都已经完成了,接下来就是如何使用这些组件。下面给出了使用这些组件的源代码:

  1. package demo;  
  2.  
  3. import javax.swing.JFrame;  
  4. import javax.swing.JScrollPane;  
  5. import javax.swing.JTree;  
  6. import javax.swing.tree.DefaultTreeModel;  
  7.  
  8. public class DemoMain   
  9. {  
  10.     public static void main(String[] args)  
  11.     {  
  12.         JFrame frame = new JFrame("CheckBoxTreeDemo");  
  13.         frame.setBounds(200200400400);  
  14.         JTree tree = new JTree();  
  15.         CheckBoxTreeNode rootNode = new CheckBoxTreeNode("root");  
  16.         CheckBoxTreeNode node1 = new CheckBoxTreeNode("node_1");  
  17.         CheckBoxTreeNode node1_1 = new CheckBoxTreeNode("node_1_1");  
  18.         CheckBoxTreeNode node1_2 = new CheckBoxTreeNode("node_1_2");  
  19.         CheckBoxTreeNode node1_3 = new CheckBoxTreeNode("node_1_3");  
  20.         node1.add(node1_1);  
  21.         node1.add(node1_2);  
  22.         node1.add(node1_3);  
  23.         CheckBoxTreeNode node2 = new CheckBoxTreeNode("node_2");  
  24.         CheckBoxTreeNode node2_1 = new CheckBoxTreeNode("node_2_1");  
  25.         CheckBoxTreeNode node2_2 = new CheckBoxTreeNode("node_2_2");  
  26.         node2.add(node2_1);  
  27.         node2.add(node2_2);  
  28.         rootNode.add(node1);  
  29.         rootNode.add(node2);  
  30.         DefaultTreeModel model = new DefaultTreeModel(rootNode);  
  31.         tree.addMouseListener(new CheckBoxTreeNodeSelectionListener());  
  32.         tree.setModel(model);  
  33.         tree.setCellRenderer(new CheckBoxTreeCellRenderer());  
  34.         JScrollPane scroll = new JScrollPane(tree);  
  35.         scroll.setBounds(00300320);  
  36.         frame.getContentPane().add(scroll);  
  37.           
  38.         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
  39.         frame.setVisible(true);  
  40.     }  

其执行结果如下图所示:

原文链接:http://blog.csdn.net/wangpingfang/article/details/7174540

【编辑推荐】

  1. Java网络编程菜鸟进阶:TCP和套接字入门
  2. 三个类似Sinatra的Java框架介绍
  3. Java调用C/C++编写的第三方dll动态链接库
  4. 《青花瓷》JAVA版:周杰伦告诉你怎么学Java
  5. Java网页数据采集器实例教程:数据存储
责任编辑:林师授 来源: wangpingfang的博客
相关推荐

2009-12-31 17:26:43

Silverlight

2010-01-25 10:35:12

Android复选框

2009-11-24 19:12:58

PHP接收复选框信息

2024-01-12 10:25:51

PyQt6Python复选框

2016-12-13 10:32:33

EasyUIComboTree任务

2011-04-11 14:14:29

checkboxlistviewAndroid

2020-11-27 07:54:53

Golang GinW

2021-10-25 09:13:07

前端开发技术

2022-04-26 10:46:53

微软Windows 11

2009-11-17 11:24:00

PHP应用技巧

2015-07-07 10:20:47

WebCSS框架

2010-08-06 15:11:44

Flex界面控件

2009-07-16 12:58:50

Swing控件

2020-12-02 11:18:28

Golang GinW

2012-03-29 15:15:49

Java

2020-12-03 09:28:05

Golang GinW

2018-07-04 09:59:23

Android评论回复

2020-11-24 08:45:04

Active Choi

2023-09-27 09:39:08

Java优化

2012-03-08 11:23:09

jQuery Mobi
点赞
收藏

51CTO技术栈公众号