Java带复选框的树(Java CheckBox Tree)实现和应用
在使用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); } }
其执行结果如下图所示:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。