Java带复选框的树(Java CheckBox Tree)实现和应用

 更新时间:2017年11月23日 11:28:41   作者:女武神的骑行  
这篇文章主要为大家详细介绍了Java带复选框的树实现和应用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

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

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

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

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

按照上述规则实现的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) 
  { 
   // 如果选中,则将其所有的子结点都选中 
   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的源代码如下:

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,它可以对选中做出反应,其源代码如下:

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,该类的源代码如下:

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所需要的所有组件都已经完成了,接下来就是如何使用这些组件。下面给出了使用这些组件的源代码:

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); 
 } 
} 

其执行结果如下图所示:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java之Spring简单的读取和存储对象

    Java之Spring简单的读取和存储对象

    这篇文章主要介绍了Spring的读取和存储对象,获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊,想进一步了解的同学可以参考本文
    2023-04-04
  • java自定义任务类定时执行任务示例 callable和future接口使用方法

    java自定义任务类定时执行任务示例 callable和future接口使用方法

    Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务
    2014-01-01
  • Java实现简易版猜灯谜游戏的示例代码

    Java实现简易版猜灯谜游戏的示例代码

    灯谜是中秋节传统的活动之一,而现代化的方式则是将其制作成一个小游戏,让用户在游戏的过程中猜灯谜,互动体验更佳,所以本文小编就用Java制作一款猜灯谜小游戏吧
    2023-09-09
  • SpringBoot之自定义Banner详解

    SpringBoot之自定义Banner详解

    这篇文章主要介绍了SpringBoot之自定义Banner详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • SpringBoot应用程序启动监听功能的常见方法

    SpringBoot应用程序启动监听功能的常见方法

    应用启动监听在 Spring Boot 和其他框架中扮演着重要的角色,它们的主要作用是在应用启动或关闭时触发特定的操作或任务,本文给大家介绍了SpringBoot应用程序启动监听功能的常见方法,需要的朋友可以参考下
    2024-05-05
  • mybatis使用pagehelper插件过程详解

    mybatis使用pagehelper插件过程详解

    这篇文章主要介绍了mybatis使用pagehelper插件过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java开发之手把手教你搭建企业级工程SSM框架

    Java开发之手把手教你搭建企业级工程SSM框架

    这篇文章主要为大家介绍Java教程中搭建企业级工程SSM框架,手把手的过程操作,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • Java继承的实现与继承限制分析

    Java继承的实现与继承限制分析

    这篇文章主要介绍了Java继承的实现与继承限制,结合具体实例形式分析了Java继承的定义、实现以及继承的相关限制,需要的朋友可以参考下
    2019-01-01
  • idea配置springboot热部署终极解决办法(解决热部署失效问题)

    idea配置springboot热部署终极解决办法(解决热部署失效问题)

    这篇文章主要介绍了idea配置springboot热部署终极解决办法(解决热部署失效问题),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-07-07
  • Lombok中关于@Data的使用解析

    Lombok中关于@Data的使用解析

    这篇文章主要介绍了Lombok中关于@Data的使用解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12

最新评论