当前位置: 移动技术网 > IT编程>开发语言>Java > Java带复选框的树(Java CheckBox Tree)实现和应用

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

在使用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); 
 } 
} 

其执行结果如下图所示:

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网