Get Edited TreeNode from a CellEditorListener
Asked Answered
D

2

3

Earlier I asked how to fire an event when a TreeNode was renamed (here). My question was answered, but I ran into another problem. I need to access the TreeNode that is being edited in the CellEditorListener's editingStopped event. This is the code I have to do so:

package com.gamecreator;

import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.tree.DefaultTreeCellEditor;

public class CustomCellEditorListener implements CellEditorListener {
    public CustomCellEditorListener() {

    }

    public void editingCanceled(ChangeEvent e) {

    }

    public void editingStopped(ChangeEvent e) {
        DefaultTreeCellEditor editor = (DefaultTreeCellEditor) e.getSource(); //This gives me the error.
        CustomTreeNode node = //What do I put here???;
        node.getResource().setName((String) node.getUserObject());

        //For debugging
        System.out.println(node.getResource().getName());
    }
}

I get this error:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: javax.swing.tree.DefaultTreeCellEditor$1 cannot be cast to javax.swing.tree.DefaultTreeCellEditor

EDIT: In another attempt, I used this code in the CustomCellEditorListener

public void editingStopped(ChangeEvent e) {
    TreePath path = ((CustomTreeCellEditor) e.getSource()).getLastPath();  //This gives me the error.
    CustomTreeNode node = (CustomTreeNode) path.getLastPathComponent();
    node.getResource().setName((String) node.getUserObject());

    //For debugging
    System.out.println(node.getResource().getName());
}

and this code in the CustomTreeCellEditor

public TreePath getLastPath() {
    return lastPath;
}

I got the same error (I expected I would). What I have should work, so the only real question remaining is, "Why am I getting the error and how can I fix it?," but if anyone has a better way to accomplish this, I'm willing to listen.

EDIT 2: I have made a small example of what I'm trying to accomplish that can be found here (It's an Eclipse archive).

Dermal answered 19/6, 2012 at 19:27 Comment(2)
terrible is that CellEditor (JTextField or JComboBox) is available only at 3rd. mouse_click,Hidalgo
@mKorbel: +1 for your answer contrasting the TreeCellEditor interface v. default implementation. I'd welcome your critical review of my approach.Hydrofoil
D
0

I found a solution that was actually very simple. When a TreeNode is renamed, it ends up being the only selected node in the tree. Because of that, I was able to use:

    CustomTreeNode node = (CustomTreeNode) tree.getLastSelectedPathComponent();
Dermal answered 28/6, 2012 at 19:8 Comment(1)
Dare I suggest that this isn't really ideal? What if your JTree selection model messes with multiple selection so renaming doesn't cancel other selections? Or if you have changed JTree in some way so you can edit without selecting? I suggest one way is to subclass DefaultTreeCellEditor (DTCE), add a CellEditorListener in the constructor, override getTreeCellEditorComponent, and then make whatever data which must persist through the edit into fields. One useful (protected) field in DTCE is "lastRow"... you can access this from inside editingStopped to get back to your Node, in most circs.Phia
H
8

It appears that you want to edit the name of a Resource in a DefaultMutableTreeNode. As you've found, the source of the ChangeEvent sent to editingStopped() in not a DefaultTreeCellEditor; it is the editor's (anonymous) UI delegate.

Instead, override getCellEditorValue() in your DefaultTreeCellEditor, as shown below. The DefaultTreeCellRenderer simply calls toString(), via convertValueToText(), which accesses the user object of DefaultMutableTreeNode.

Addenda: Note that isCellEditable() ensures that only leaf nodes can be edited.

As @kleopatra notes in comments, the previous TreeCellEditor implementation was invalid, as it modified the node being edited. The revised version below creates a new node having the updated name; a copy constructor would be useful in this context. The advantage is that the userObject remains a Resource. See also this alternative approach.

image

/**
 * @see https://mcmap.net/q/1177061/-easy-and-fast-jtree-cell-editor
 * @see https://mcmap.net/q/1177062/-uncertainties-regarding-implementation-of-actions-and-usage-of-a-single-model-with-multiple-views
 * @see https://mcmap.net/q/1176573/-get-edited-treenode-from-a-celleditorlistener
 */
public class TreeEditDemo extends JPanel {

    private JTree tree;
    private DefaultMutableTreeNode root;
    private DefaultTreeCellEditor editor;
    private JLabel label = new JLabel(" ", JLabel.CENTER);

    public TreeEditDemo() {
        super(new BorderLayout());
        root = new DefaultMutableTreeNode("Nodes");
        root.add(new DefaultMutableTreeNode(new Resource("one")));
        root.add(new DefaultMutableTreeNode(new Resource("two")));
        root.add(new DefaultMutableTreeNode(new Resource("three")));
        final DefaultTreeModel treeModel = new DefaultTreeModel(root);
        tree = new JTree(treeModel);
        tree.setEditable(true);
        editor = new MyTreeCellEditor(tree,
            (DefaultTreeCellRenderer) tree.getCellRenderer());
        tree.setCellEditor(editor);
        tree.getInputMap().put(
            KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "startEditing");
        this.add(new JScrollPane(tree));
        this.add(label, BorderLayout.SOUTH);
        tree.addTreeSelectionListener(new TreeSelectionListener() {

            @Override
            public void valueChanged(TreeSelectionEvent e) {
                TreePath path = e.getNewLeadSelectionPath();
                if (path != null) {
                    DefaultMutableTreeNode node =
                        (DefaultMutableTreeNode) path.getLastPathComponent();
                    if (node.isLeaf()) {
                        Resource user = (Resource) node.getUserObject();
                        label.setText(user.toString());
                    } else {
                        label.setText(" ");
                    }
                }
            }
        });
        editor.addCellEditorListener(new CellEditorListener() {

            @Override
            public void editingStopped(ChangeEvent e) {
                label.setText(editor.getCellEditorValue().toString());
            }

            @Override
            public void editingCanceled(ChangeEvent e) {
            }
        });
    }

    private static class MyTreeCellEditor extends DefaultTreeCellEditor {

        public MyTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
            super(tree, renderer);
        }

        @Override
        public Object getCellEditorValue() {
            String value = (String) super.getCellEditorValue();
            return new Resource(value);
        }

        @Override
        public boolean isCellEditable(EventObject e) {
            return super.isCellEditable(e)
                && ((TreeNode) lastPath.getLastPathComponent()).isLeaf();
        }
    }

    private static class Resource {

        String name;

        public Resource(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return getName();
        }
    }

    private void display() {
        JFrame f = new JFrame("TreeEditorDemo");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TreeEditDemo().display();
            }
        });
    }
}
Hydrofoil answered 20/6, 2012 at 6:2 Comment(9)
+1 and please see my questionHidalgo
I was actually able to change the number of clicks very easily. I just put this in my CustomTreeCellEditor class (extends DefaultTreeCellEditor): protected boolean canEditImmediately(EventObject event) { if (event == null) return true; if (((MouseEvent) event).getClickCount() >= 2) return true; return false; }Dermal
For convenience, one may bind a key to the existing editing Action: tree.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Fn, 0), "startEditing").Hydrofoil
hmm ... that's not really a valid editor implementation: it must not change anything in the model, as done above in the getCellEditorValue.Endo
@kleopatra: I think I understand why that would be a problem in getTreeCellRendererComponent(); but I thought getCellEditorValue() would be safe, as it is called after the editor concludes. If not, I'll have to re-work this example. As always, I appreciate your insight.Hydrofoil
there is no restriction on calling that method: any code could do so at any time, even if the editing would have been cancelled later. In that case, the data would have been changed under the feet of the model and ... whuummm :-) The general rule for an editor is that it must not touch the data (except for showing) and report when it has a replacement - nothing else. It's the task of whoever is listening (f.i. the tree/ui) to actually update the model with the new data.Endo
what it could do is to create a new Resource from the data: that's what JTable.GenericEditor tries, reflectively trying to call a constructor with the string as single parameter.Endo
another option is to let the data realm handle the raw string, as in my stolen - from yours here - snippetEndo
@kleopatra: I followed your suggestion to use new Resource(…) above.Hydrofoil
D
0

I found a solution that was actually very simple. When a TreeNode is renamed, it ends up being the only selected node in the tree. Because of that, I was able to use:

    CustomTreeNode node = (CustomTreeNode) tree.getLastSelectedPathComponent();
Dermal answered 28/6, 2012 at 19:8 Comment(1)
Dare I suggest that this isn't really ideal? What if your JTree selection model messes with multiple selection so renaming doesn't cancel other selections? Or if you have changed JTree in some way so you can edit without selecting? I suggest one way is to subclass DefaultTreeCellEditor (DTCE), add a CellEditorListener in the constructor, override getTreeCellEditorComponent, and then make whatever data which must persist through the edit into fields. One useful (protected) field in DTCE is "lastRow"... you can access this from inside editingStopped to get back to your Node, in most circs.Phia

© 2022 - 2024 — McMap. All rights reserved.