在使用Java Swing开发UI程序时,很有可能会遇到使用带复选框的树的需求,但是Java Swing并没有提供这个组件,因此如果你有这个需求,你就得自己动身实现带复选框的树。
CheckBoxTree与JTree在两个层面上存在差异:
在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。
在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。
既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。
现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:
如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。
如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。
注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,这种非手动导致的选中或者非取消选中则不适用于上述规则。
按照上述规则实现的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.