使用 JCheckBox 节点进行 JTree 渲染

Posted

技术标签:

【中文标题】使用 JCheckBox 节点进行 JTree 渲染【英文标题】:JTree rendering with JCheckBox nodes 【发布时间】:2011-01-04 17:47:43 【问题描述】:

我正在尝试修改标准 Swing JTree 以混合使用和不使用复选框的节点。这是一个例子:

当我尝试选中/取消选中其中一个复选框(本示例中的“用户 01”节点)时,树会丢失节点:

我的代码是对这个例子的改编:http://forums.sun.com/thread.jspa?threadID=5321084&start=13。

而不是像这样在 DefaultMutableTreeNode 中嵌入 JCheckBox:

new DefaultMutableTreeNode(new CheckBoxNode("Accessibility", true));

我认为创建一个派生自 DefaultMutableTreeNode(我称之为 JTreeNode)的模型节点更有意义。此类自动将 DefaultMutableTreeNode 的 UserObject 设置为 JCheckBox。 TreeCellRenderer 使用类的 ShowCheckBox 属性来确定是使用 JCheckBox 还是 DefaultTreeCellRenderer。 JTreeNode 是这样使用的:

    JTreeNode user01 = new JTreeNode("User 01");
    user01.setShowCheckBox(true);
    user01.setSelected(true);

我认为问题出在实现 TreeCellEditor 的类上,特别是在 getCellEditorValue() 或 getTreeCellEditorComponent() 方法中。我怀疑这个问题与 getCellEditorValue() 返回 DefaultMutableTreeNode 的派生而不是更简单的模型实例有关。

public Object getCellEditorValue() 

    JCheckBox checkBox = renderer.getCheckBoxRenderer();

    JTreeNode node = new JTreeNode(checkBox.getText());
    node.setShowCheckBox(true);
    node.setSelected(checkBox.isSelected());
    return node;



public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) 

    Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);

    // editor always selected / focused
    ItemListener itemListener = new ItemListener() 
        public void itemStateChanged(ItemEvent itemEvent) 
            if (stopCellEditing()) 
                fireEditingStopped();
            
        
    ;

    if (editor instanceof JCheckBox) 
        ((JCheckBox) editor).addItemListener(itemListener);
    

    return editor;


这里是 TreeCellRender 的 getTreeCellRendererComponent() 方法:

public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) 

    Component component;

    //if object being passed for rendering is a JTreeNode that should show a JCheckBox, attempt to render it so
    if (((JTreeNode) value).getShowCheckBox()) 

        String stringValue = tree.convertValueToText(value, selected, expanded, leaf, row, false);

        //set default JCheckBox rendering
        checkBoxRenderer.setText(stringValue);
        checkBoxRenderer.setSelected(false);    //not checked
        checkBoxRenderer.setEnabled(tree.isEnabled());

        if (selected) 
            //removed colorization
            //checkBoxRenderer.setForeground(selectionForeground);
            //checkBoxRenderer.setBackground(selectionBackground);
        
        else 
            checkBoxRenderer.setForeground(textForeground);
            checkBoxRenderer.setBackground(textBackground);
        

        //DefaultMutableTreeNode
        if ((value != null) && (value instanceof JTreeNode)) 

            //userObject should be a JTreeNode instance
            //DefaultMutableTreeNode
            //Object userObject = ((JTreeNode) value).getUserObject();

            //if it is
            //if (userObject instanceof JTreeNode) 
                //cast as a JTreeNode
                //JTreeNode node = (JTreeNode) userObject;
                JTreeNode node = (JTreeNode) value;

                //set JCheckBox settings to match JTreeNode's settings
                checkBoxRenderer.setText(node.getText());
                checkBoxRenderer.setSelected(node.isSelected());

            //

        

        component = checkBoxRenderer;

    

    //if not, render the default
    else 

        component = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);

    

    return component;


非常感谢任何想法。

【问题讨论】:

这是基于截图的纯粹猜测,但可能是渲染问题。您可以在节点消失后尝试使用 SwingUtilities.updateComponentTreeUI。如果这有帮助,那么树只是没有正确重绘。 来吧伙计们,为什么没有人支持这个问题? 26 次浏览,你们中没有一个人认为这家伙投入了创造一个好问题的工作?嘘,你们都是骗子。 /咆哮 【参考方案1】:

我能够解决问题。

我创建了一个模型类(TreeNodeModel)来保存相关的节点数据:key、value、hasCheckBox、isSelected:

public class TreeNodeModel 

    int key;
    String value;
    boolean isSelected=false;
    boolean hasCheckBox=false;

    public TreeNodeModel() 
    
    public TreeNodeModel(int key, String value) 
        this.key=key;
        this.value = value;
    
    public TreeNodeModel(int key, String value, boolean hasCheckBox) 
        this.key=key;
        this.value = value;
        this.hasCheckBox = hasCheckBox;
    
    public TreeNodeModel(int key, String value, boolean hasCheckBox, boolean isSelected) 
        this.key=key;
        this.value = value;
        this.hasCheckBox=hasCheckBox;
        this.isSelected = isSelected;
    

    public boolean isSelected() 
        return this.isSelected;
    
    public void setSelected(boolean newValue) 
        this.isSelected = newValue;
    

    public boolean hasCheckBox() 
        return this.hasCheckBox;
    
    public void setCheckBox(boolean newValue) 
        this.hasCheckBox=newValue;
    

    public int getKey() 
        return this.key;
    
    public void setKey(int newValue) 
        this.key = newValue;
    

    public String getValue() 
        return this.value;
    
    public void setValue(String newValue) 
        this.value = newValue;
    

    @Override
    public String toString() 
        return getClass().getName() + "[" + this.value + "/" + this.isSelected + "]";
//        return this.text;
    


我创建了 TreeCellEditor 接口的实现:

public class TreeNodeEditor  extends AbstractCellEditor implements TreeCellEditor 

    private JTree tree;
    private TreeNodeModel editedModel = null;

    TreeNodeRenderer renderer = new TreeNodeRenderer();

    public TreeNodeEditor(JTree tree) 
        this.tree=tree;
    

    @Override
    public boolean isCellEditable(EventObject event) 

        boolean editable=false;

        if (event instanceof MouseEvent) 

            MouseEvent mouseEvent = (MouseEvent) event;
            TreePath path = tree.getPathForLocation(mouseEvent.getX(),mouseEvent.getY());

            if (path != null) 

                Object node = path.getLastPathComponent();

                if ((node != null) && (node instanceof DefaultMutableTreeNode)) 

                    DefaultMutableTreeNode editedNode = (DefaultMutableTreeNode) node;
                    TreeNodeModel model = (TreeNodeModel) editedNode.getUserObject();
                    editable = model.hasCheckBox;

                   //if (node)
               //if (path)
           //if (MouseEvent)

        return editable;

    

    public Object getCellEditorValue() 

        JCheckBox checkbox = renderer.getCheckBoxRenderer();

        TreeNodeModel model = new TreeNodeModel(editedModel.getKey(), checkbox.getText(), true, checkbox.isSelected());
        return model;

    

    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) 

        if (((DefaultMutableTreeNode) value).getUserObject() instanceof TreeNodeModel) 
            editedModel = (TreeNodeModel) ((DefaultMutableTreeNode) value).getUserObject();
        

        Component editor = renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true);

        // editor always selected / focused
        ItemListener itemListener = new ItemListener() 
            public void itemStateChanged(ItemEvent itemEvent) 
                if (stopCellEditing())
                    fireEditingStopped();
            
        ;

        if (editor instanceof JCheckBox) 
            ((JCheckBox) editor).addItemListener(itemListener);
        

        return editor;
    


关键是在 getTreeCellEditorComponent() 方法中捕获模型并在 getCellEditorValue() 方法中使用它的 Key。

构建树很容易:

DefaultMutableTreeNode server = new DefaultMutableTreeNode(new TreeNodeModel(0,"Server 01", false));

DefaultMutableTreeNode userFolder = new DefaultMutableTreeNode(new TreeNodeModel(1, "User Folders", false));
server.add(userFolder);

DefaultMutableTreeNode user01 =  new DefaultMutableTreeNode(new TreeNodeModel(2, "User 01", true, true));
userFolder.add(user01);

DefaultMutableTreeNode clientA = new DefaultMutableTreeNode(new TreeNodeModel(3, "Client A", true, true));
user01.add(clientA);

DefaultMutableTreeNode clientB = new DefaultMutableTreeNode(new TreeNodeModel(4, "Client B", true, true));
user01.add(clientB);

DefaultMutableTreeNode publicFolder = new DefaultMutableTreeNode(new TreeNodeModel(5, "Public Folders", false));
server.add(publicFolder);

DefaultMutableTreeNode clientC = new DefaultMutableTreeNode(new TreeNodeModel(6, "Client C", true));
publicFolder.add(clientC);
        Tree_Nodes.setCellRenderer(new TreeNodeRenderer());
        Tree_Nodes.setCellEditor(new TreeNodeEditor(Tree_Nodes));
Tree_Nodes.setModel(new DefaultTreeModel(server);

最后,确定检查哪些节点是检查模型的节点集合(通过按钮)的问题:

private Map<Integer, String> checked = new HashMap<Integer, String>();

private void Button_CheckedActionPerformed(java.awt.event.ActionEvent evt) 

    DefaultTableModel tableModel = ((DefaultTableModel) Table_Nodes.getModel());
    tableModel.getDataVector().removeAllElements();
    tableModel.fireTableDataChanged();

    checked.clear();

    DefaultTreeModel treeModel = (DefaultTreeModel) Tree_Nodes.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();

    getChildNodes(root);

    for (Iterator it=checked.entrySet().iterator(); it.hasNext(); ) 
        Map.Entry entry = (Map.Entry)it.next();
        tableModel.addRow(new Object[] entry.getKey(), entry.getValue());
    

    Button_Checked.requestFocus();




private void getChildNodes(DefaultMutableTreeNode parentNode) 

    for (Enumeration e=parentNode.children(); e.hasMoreElements();) 

        DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) e.nextElement();
        TreeNodeModel model = (TreeNodeModel) childNode.getUserObject();

        if (model.hasCheckBox && model.isSelected()) 
            checked.put(model.getKey(), model.getValue());
        

        //recurse
        if (childNode.getChildCount()>0) getChildNodes(childNode);

    


【讨论】:

+1 用于添加屏幕截图,因为我尽可能这样做(我想 +2 使用 OSX) 我想通过让 JCheckBox 向 JTree 中的适当机制发出信号来改进这一点,以便能够使用 JTree.getSelectionPaths() 枚举选定的节点。 你已经创建了 TreeNodeRenderer() 那么它不工作的那个类在哪里呢。【参考方案2】:

以下是一些导致我出现渲染问题的“陷阱”:

    TreeCellEditor 可能不共享传递给 JTree.setCellRenderer() 的 TreeCellRenderer 实例。如果TreeCellEditor.getTreeCellEditorComponent() 返回与树的TreeCellRenderer.getTreeCellRendererComponent() 相同的实例,则最终会出现渲染问题。您将尝试编辑一个单元格,但渲染器针对 5 个不相关的单元格运行。当编辑器尝试检索 JCheckBox 的状态时,它将从最后呈现的单元格(而不是最后编辑的单元格)中获取值,这显然是错误的。

    使用TreeCellEditor.getCellEditorValue() 修改单元格的值,而不是直接在复选框上添加鼠标侦听器。仅当值被保存时才会调用此方法。如果您立即对鼠标事件采取行动,则该值不会在 CellEditor.cancelCellEditing() 上回滚。

【讨论】:

以上是关于使用 JCheckBox 节点进行 JTree 渲染的主要内容,如果未能解决你的问题,请参考以下文章

JCheckBoxTree 初始状态问题

JTree 更新节点而不折叠

JTree 通过单击行上的任意位置选择节点

从 JTree 添加和删除节点

如何将复选框添加到 JTree 节点以管理多选?

JAVA 得到jtree 某节点下的子节点