欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-04-01 18:24:22
在使用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源代码如下:

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)实现和应用

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