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

作者:女武神的骑行 时间:2021-07-25 20:53:46 

在使用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带复选框的树(Java CheckBox Tree)实现和应用

来源:http://blog.csdn.net/wangpingfang/article/details/7174540

标签:Java,CheckBox,Tree
0
投稿

猜你喜欢

  • Java中static关键字的作用和用法详细介绍

    2022-07-05 08:33:53
  • WinForm导出文件为Word、Excel、文本文件的方法

    2022-07-08 20:07:16
  • 区分C# 中的 Struct 和 Class

    2022-09-10 19:51:25
  • Android小程序实现选项菜单

    2022-04-12 00:34:01
  • 使用JAVA实现邮件发送功能的图文教程

    2021-09-04 17:29:36
  • 在C#项目中如何使用NHibernate详解

    2022-01-24 08:46:17
  • Java单例模式的几种常见写法

    2023-10-23 18:27:45
  • SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

    2022-07-18 03:38:36
  • Java Scanner类用法及nextLine()产生的换行符问题实例分析

    2022-12-22 21:44:04
  • Java emoji持久化mysql过程详解

    2023-10-10 23:11:49
  • 利用json2POJO with Lombok 插件自动生成java类的操作

    2023-07-12 09:31:28
  • Java实现获取指定个数的不同随机数

    2023-11-14 21:42:34
  • XAML如何获取元素的位置

    2023-03-16 14:24:12
  • 详细讲解Java的泛型

    2021-07-06 02:31:24
  • 详解如何将JAVA程序制作成可以直接执行的exe文件

    2023-11-23 21:12:40
  • 解决unity rotate旋转物体 限制物体旋转角度的大坑

    2022-01-14 01:41:49
  • Java中的回调

    2023-11-16 01:55:58
  • Vue3源码解读effectScope API及实现原理

    2023-12-11 19:28:49
  • Java中Servlet的生命周期详解

    2023-09-10 03:57:59
  • Java多线程编程中ThreadLocal类的用法及深入

    2022-03-17 03:21:29
  • asp之家 软件编程 m.aspxhome.com