您的位置:首页 > 编程语言 > Java开发

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

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

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

在模型层上,CheckBoxTree的每个结点需要一个成员来保存其是否被选中,但是JTree的结点则不需要。
在视图层上,CheckBoxTree的每个结点比JTree的结点多显示一个复选框。
既然存在两个差异,那么只要我们把这两个差异部分通过自己的实现填补上,那么带复选框的树也就实现了。
现在开始解决第一个差异。为了解决第一个差异,需要定义一个新的结点类CheckBoxTreeNode,该类继承DefaultMutableTreeNode,并增加新的成员isSelected来表示该结点是否被选中。对于一颗CheckBoxTree,如果某一个结点被选中的话,其复选框会勾选上,并且使用CheckBoxTree的动机在于可以一次性地选中一颗子树。那么,在选中或取消一个结点时,其祖先结点和子孙结点应该做出某种变化。在此,我们应用如下递归规则:

如果某个结点被手动选中,那么它的所有子孙结点都应该被选中;如果选中该结点使其父节点的所有子结点都被选中,则选中其父结点。
如果某个结点被手动取消选中,那么它的所有子孙结点都应该被取消选中;如果该结点的父结点处于选中状态,则取消选中其父结点。
注意:上面的两条规则是递归规则,当某个结点发生变化,导致另外的结点发生变化时,另外的结点也会导致其他的结点发生变化。在上面两条规则中,强调手动,是因为手动选中或者手动取消选中一个结点,会导致其他结点发生非手动的选中或者取消选中,这种非手动导致的选中或者非取消选中则不适用于上述规则。

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

package demo; 
 
import javax.swing.tree.DefaultMutableTreeNode; 
 
public class CheckBoxTreeNodeextends 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); 
        } 
    } 


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 CheckBoxTreeCellRendererextends 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); 
    } 


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,它可以对选中做出反应,其源代码如下:

[java]
view plaincopyprint?

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 CheckBoxTreeLabelextends 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; 
    } 


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,该类的源代码如下:

[java]
view plaincopyprint?

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 CheckBoxTreeNodeSelectionListenerextends 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); 
            } 
        } 
    } 


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所需要的所有组件都已经完成了,接下来就是如何使用这些组件。下面给出了使用这些组件的源代码:

[java]
view plaincopyprint?

package demo; 
 
import javax.swing.JFrame; 
import javax.swing.JScrollPane; 

import javax.swing.JTree; 
import javax.swing.tree.DefaultTreeModel; 
 
public class DemoMain  

    public staticvoid 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); 
    } 


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);
}
}
其执行结果如下图所示:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐