When developing UI programs using Java Swing, you are likely to encounter the need to use a tree with check boxes, but Java Swing does not provide this component, so if you have this requirement, you have to implement a tree with check boxes yourself.
There are differences between CheckBoxTree and JTree on two levels:
1. On the model layer, each node of CheckBoxTree needs a member to save whether it is selected, but JTree nodes do not.
2. On the view layer, each node of CheckBoxTree displays one more check box than the node of JTree.
Since there are two differences, as long as we fill these two differences with our own implementation, the tree with check boxes will be implemented.
Now start to resolve the first difference. To solve the first difference, a new node class CheckBoxTreeNode needs to be defined, which inherits the DefaultMutableTreeNode and adds a new member isSelected to indicate whether the node is selected. For a CheckBoxTree, if a node is selected, its check box will be checked, and the motivation for using CheckBoxTree is to select a subtree at once. Then, when a node is selected or canceled, its ancestor node and descendant node should make some changes. Here, we apply the following recursive rules:
1. If a node is manually selected, all its descendants should be selected; if the node is selected to make all children of its parent node selected, then its parent node is selected.
2. If a node is manually unchecked, all its descendants should be unchecked; if the parent node of the node is selected, its parent node is unchecked.
Note: The above two rules are recursive rules. When a node changes and causes another node to change, another node will also cause other nodes to change. In the above two rules, manual selection or manual deselecting of a node will cause non-manual selection or unselecting of other nodes. This non-manual selection or non-unselecting of this non-manual selection or unselecting will not apply to the above rules.
The source code of CheckBoxTreeNode implemented in accordance with the above rules is as follows:
package demo; import javax.swing.tree.DefaultMutableTreeNode; public class CheckBoxTreeNode extends DefaultMutableTreeNode { protected boolean isSelected; public CheckBoxTreeNode() { this(null); } public CheckBoxTreeNode(Object userObject, true, false); } public CheckBoxTreeNode(Object userObject, boolean allowsChildren, boolean isSelected) { super(userObject, allow Children); this.isSelected = isSelected; } public boolean isSelected() { return isSelected; } public void setSelected(boolean _isSelected) { this.isSelected = _isSelected; if(_isSelected) { // If selected, select all its child nodes if(children != null) { for(Object obj : children) { CheckBoxTreeNode node = (CheckBoxTreeNode)obj; if(_isSelected != node.isSelected()) node.setSelected(_isSelected); } } // Check up, if all child nodes of the parent node are selected, then select the parent node CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; // Start checking whether all child nodes of the pNode are selected if(pNode != null) { int index = 0; for(; index < pNode.children.size(); ++ index) { CheckBoxTreeNode pChildNode = (CheckBoxTreeNode)pNode.children.get(index); if(!pChildNode.isSelected()) break; } /* * Indicates that all child nodes of pNode have been selected, the parent node is selected. * This method is a recursive method, so there is no need to iterate here, because * When the parent node is selected, the parent node itself will check upwards. */ if(index == pNode.children.size()) { if(pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } else { /* * If the cancellation of the parent node causes the child node to be cancelled, then all child nodes should be selected at this time; * Otherwise, the cancellation of the child node will cause the parent node to be cancelled, and then the cancellation of the parent node will cause the child node to be cancelled, but * is not necessary to cancel the child node at this time. */ if(children != null) { int index = 0; for(; index < children.size(); ++ index) { CheckBoxTreeNode childNode = (CheckBoxTreeNode)children.get(index); if(!childNode.isSelected()) break; } // When canceling from top to bottom 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); } } } // Cancel up, as long as there is a child node that is not selected, the parent node should not be selected. CheckBoxTreeNode pNode = (CheckBoxTreeNode)parent; if(pNode != null && pNode.isSelected() != _isSelected) pNode.setSelected(_isSelected); } } } The first difference is solved by inheriting the DefaultMutableTreeNode definition CheckBoxTreeNode, and the second difference needs to be resolved next. The second difference is the difference in appearance, and each node of JTree is displayed through TreeCellRenderer. To resolve the second difference, we define a new class CheckBoxTreeCellRenderer that implements the TreeCellRenderer interface. The source code of CheckBoxTreeRenderer is as follows:
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")); } /** * Returns a <code>JPanel</code> object, which contains a <code>JCheckBox</code> object* and a <code>JLabel</code> object. And decide whether <code>JCheckBox</code> * is selected based on whether each node is selected. */ @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 instance of ColorUIResource) color = null; super.setBackground(color); } } In the implementation of CheckBoxTreeCellRenderer, the getTreeCellRendererComponent method returns JPanel, instead of returning JLabel like DefaultTreeCellRenderer. Therefore, the JLabel in the JPanel cannot react to the selection. Therefore, we reimplemented a subclass of JLabel CheckBoxTreeLabel, which can react to the selection, and its source code is as follows:
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 instance of 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; } } By defining CheckBoxTreeNode and CheckBoxTreeCellRenderer. We solved the two fundamental differences between CheckBoxTree and JTree, but there is still a detailed problem that needs to be solved, that is, CheckBoxTree can decide whether to select a certain node in response to user events. To do this, we add a CheckBoxTree listener that responds to user mouse events. The source code of this class is as follows:
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); } } } } } So far, all the components required by CheckBoxTree have been completed, and the next step is how to use them. The source code for using these components is given below:
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); } }The execution results are shown in the figure below:
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.