JTree rendering with JCheckBox nodes

旧街凉风 提交于 2019-12-05 00:50:07
craig

I was able to solve the problem.

I created a model class (TreeNodeModel) to hold the relevant node data: 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;
    }

}

I created an implementation of the TreeCellEditor interface:

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

}

The key was capturing the model in the getTreeCellEditorComponent() method and using its Key in the getCellEditorValue() method.

Building the tree was easy:

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

Finally, determining which nodes where checked was a matter of examining the model's node collection (via a button):

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

    }

}

Here are a few "gotchas" that caused rendering problems for me:

  1. The TreeCellEditor may not share the TreeCellRenderer instance passed into JTree.setCellRenderer(). If TreeCellEditor.getTreeCellEditorComponent() returns the same instance as the tree's TreeCellRenderer.getTreeCellRendererComponent() you will end up with rendering problems. You will try editing one cell, but the renderer is run against 5 unrelated cells. When the editor attempts to retrieve the JCheckBox's state it will get the value from the last rendered cell (as opposed to the last edited cell) which is clearly wrong.

  2. Use TreeCellEditor.getCellEditorValue() to modify a cell's value, instead of adding mouse listeners directly on the checkbox. This method only gets invoked if the value gets saved. If you act immediately on mouse events the value won't roll back on CellEditor.cancelCellEditing().

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!