java 树 复选框_Java中带复选框的树的实现和应用

本文介绍了如何在Java Swing中自定义实现带复选框的树形组件CheckBoxTree,包括解决模型层和视图层的差异,定义CheckBoxTreeNode类并处理结点选中状态的递归规则,以及实现CheckBoxTreeCellRenderer类以展示结点的复选框。通过这些组件的组合,可以创建响应用户交互的可选中树节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在使用Java Swing开发UI程序时,很有可能会遇到使用带复选框的树的需求,但是Java Swing并没有提供这个组件,因此如果你有这个需求,你就得自己动身实现带复选框的树。

CheckBoxTree与JTree在两个层面上存在差异:

在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。

在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。

既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。

现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:

如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。

如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。

注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,这种非手动导致的选中或者非取消选中则不适用于上述规则。

按照上述规则实现的CheckBoxTreeNode源代码如下:

packagedemo;

importjavax.swing.tree.DefaultMutableTreeNode;

publicclassCheckBoxTreeNodeextendsDefaultMutableTreeNode

{

protectedbooleanisSelected;

publicCheckBoxTreeNode()

{

this(null);

}

publicCheckBoxTreeNode(Object userObject)

{

this(userObject,true,false);

}

publicCheckBoxTreeNode(Object userObject,booleanallowsChildren,booleanisSelected)

{

super(userObject, allowsChildren);

this.isSelected = isSelected;

}

publicbooleanisSelected()

{

returnisSelected;

}

publicvoidsetSelected(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)

{

intindex =0;

for(; 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)

{

intindex =0;

for(; index 

{

CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index);

if(!childNode.isSelected())

break;

}

// 从上向下取消的时候

if(index == children.size())

{

for(inti =0; 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的源代码如下:

packagedemo;

importjava.awt.Color;

importjava.awt.Component;

importjava.awt.Dimension;

importjavax.swing.JCheckBox;

importjavax.swing.JPanel;

importjavax.swing.JTree;

importjavax.swing.UIManager;

importjavax.swing.plaf.ColorUIResource;

importjavax.swing.tree.TreeCellRenderer;

publicclassCheckBoxTreeCellRendererextendsJPanelimplementsTreeCellRenderer

{

protectedJCheckBox check;

protectedCheckBoxTreeLabel label;

publicCheckBoxTreeCellRenderer()

{

setLayout(null);

add(check =newJCheckBox());

add(label =newCheckBoxTreeLabel());

check.setBackground(UIManager.getColor("Tree.textBackground"));

label.setForeground(UIManager.getColor("Tree.textForeground"));

}

/**

* 返回的是一个JPanel对象,该对象中包含一个JCheckBox对象

* 和一个JLabel对象。并且根据每个结点是否被选中来决定JCheckBox

* 是否被选中。

*/

@Override

publicComponent getTreeCellRendererComponent(JTree tree, Object value,

booleanselected,booleanexpanded,booleanleaf,introw,

booleanhasFocus)

{

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"));

elseif(expanded)

label.setIcon(UIManager.getIcon("Tree.openIcon"));

else

label.setIcon(UIManager.getIcon("Tree.closedIcon"));

returnthis;

}

@Override

publicDimension getPreferredSize()

{

Dimension dCheck = check.getPreferredSize();

Dimension dLabel = label.getPreferredSize();

returnnewDimension(dCheck.width + dLabel.width, dCheck.height 

}

@Override

publicvoiddoLayout()

{

Dimension dCheck = check.getPreferredSize();

Dimension dLabel = label.getPreferredSize();

intyCheck =0;

intyLabel =0;

if(dCheck.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

publicvoidsetBackground(Color color)

{

if(colorinstanceofColorUIResource)

color =null;

super.setBackground(color);

}

}

在CheckBoxTreeCellRenderer的实现中,为了处理背景色等问题,我们重新实现了一个JLabel的子类CheckBoxTreeLabel,其源代码如下:

packagedemo;

importjava.awt.Color;

importjava.awt.Dimension;

importjava.awt.Graphics;

importjavax.swing.Icon;

importjavax.swing.JLabel;

importjavax.swing.UIManager;

importjavax.swing.plaf.ColorUIResource;

publicclassCheckBoxTreeLabelextendsJLabel

{

privatebooleanisSelected;

privatebooleanhasFocus;

publicCheckBoxTreeLabel()

{

}

@Override

publicvoidsetBackground(Color color)

{

if(colorinstanceofColorUIResource)

color =null;

super.setBackground(color);

}

@Override

publicvoidpaint(Graphics g)

{

String str;

if((str = getText()) !=null)

{

if(0

{

if(isSelected)

g.setColor(UIManager.getColor("Tree.selectionBackground"));

else

g.setColor(UIManager.getColor("Tree.textBackground"));

Dimension d = getPreferredSize();

intimageOffset =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

publicDimension getPreferredSize()

{

Dimension retDimension =super.getPreferredSize();

if(retDimension !=null)

retDimension =newDimension(retDimension.width +3, retDimension.height);

returnretDimension;

}

publicvoidsetSelected(booleanisSelected)

{

this.isSelected = isSelected;

}

publicvoidsetFocus(booleanhasFocus)

{

this.hasFocus = hasFocus;

}

}

通过定义CheckBoxTreeNode和CheckBoxTreeCellRenderer。我们解决了CheckBoxTree和JTree的两个根本差异,但是还有一个细节问题需要解决,就是CheckBoxTree可以响应用户事件决定是否选中某个结点。为此,我们为CheckBoxTree添加一个响应用户鼠标事件的监听器CheckBoxTreeNodeSelectionListener,该类的源代码如下:

packagedemo;

importjava.awt.event.MouseAdapter;

importjava.awt.event.MouseEvent;

importjavax.swing.JTree;

importjavax.swing.tree.TreePath;

importjavax.swing.tree.DefaultTreeModel;

publicclassCheckBoxTreeNodeSelectionListenerextendsMouseAdapter

{

@Override

publicvoidmouseClicked(MouseEvent event)

{

JTree tree = (JTree)event.getSource();

intx = event.getX();

inty = event.getY();

introw = tree.getRowForLocation(x, y);

TreePath path = tree.getPathForRow(row);

if(path !=null)

{

CheckBoxTreeNode node = (CheckBoxTreeNode)path.getLastPathComponent();

if(node !=null)

{

booleanisSelected = !node.isSelected();

node.setSelected(isSelected);

((DefaultTreeModel)tree.getModel()).nodeStructureChanged(node);

}

}

}

}

到此为止,CheckBoxTree所需要的所有组件都已经完成了,接下来就是如何使用这些组件。下面给出了使用这些组件的源代码:

packagedemo;

importjavax.swing.JFrame;

importjavax.swing.JScrollPane;

importjavax.swing.JTree;

importjavax.swing.tree.DefaultTreeModel;

publicclassDemoMain

{

publicstaticvoidmain(String[] args)

{

JFrame frame =newJFrame("CheckBoxTreeDemo");

frame.setBounds(200,200,400,400);

JTree tree =newJTree();

CheckBoxTreeNode rootNode =newCheckBoxTreeNode("root");

CheckBoxTreeNode node1 =newCheckBoxTreeNode("node_1");

CheckBoxTreeNode node1_1 =newCheckBoxTreeNode("node_1_1");

CheckBoxTreeNode node1_2 =newCheckBoxTreeNode("node_1_2");

CheckBoxTreeNode node1_3 =newCheckBoxTreeNode("node_1_3");

node1.add(node1_1);

node1.add(node1_2);

node1.add(node1_3);

CheckBoxTreeNode node2 =newCheckBoxTreeNode("node_2");

CheckBoxTreeNode node2_1 =newCheckBoxTreeNode("node_2_1");

CheckBoxTreeNode node2_2 =newCheckBoxTreeNode("node_2_2");

node2.add(node2_1);

node2.add(node2_2);

rootNode.add(node1);

rootNode.add(node2);

DefaultTreeModel model =newDefaultTreeModel(rootNode);

tree.addMouseListener(newCheckBoxTreeNodeSelectionListener());

tree.setModel(model);

tree.setCellRenderer(newCheckBoxTreeCellRenderer());

JScrollPane scroll =newJScrollPane(tree);

scroll.setBounds(0,0,300,320);

frame.getContentPane().add(scroll);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);

}

}

其执行结果如下图所示:

300719a2692a64cf33096c4a0d4bb28d.png

【编辑推荐】

【责任编辑:小林 TEL:(010)68476606】

点赞 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值