Matt
Matt

Reputation: 295

Get new user object string when user is editing a node within my JTree and clicks away from the node

I've created a custom TreeModel by extending DefaultTreeModel, so the user can rename nodes in my JTree. This works fine if my user inputs a new name and then hits enter. If instead of hitting enter, the user clicks away from the node then my valueForPathChanged method doesn't fire and I can't get the new String. How can I get the new user String without the user hitting enter and instead clicking somewhere else in the Tree/Panel/Frame?

Upvotes: 1

Views: 964

Answers (2)

kleopatra
kleopatra

Reputation: 51535

To improve the situation slightly you can set the invokesStopCellEditing property of the JTree: being true the ui will commit a pending edit on some internal changes, like expansion or selection change.

    final JTree tree = new JTree();
    tree.setEditable(true);
    // this will often help (see its api doc), but no guarantee
    tree.setInvokesStopCellEditing(true);
    // a focusListener is **not** helping
    FocusListener l = new FocusListener() {

        @Override
        public void focusGained(FocusEvent e) {
        }

        @Override
        public void focusLost(FocusEvent e) {
            // this would prevent editing at all
           // tree.stopEditing();
        }

    };
    tree.addFocusListener(l);
    JComponent panel = new JPanel(new BorderLayout());
    panel.add(new JScrollPane(tree));
    panel.add(new JButton("just something to focus"), BorderLayout.SOUTH);

The snippet (to play with) also demonstrates that a focusListener is not working.

CellEditorRemover and its usage in JXTree (as you see, there's slightly more to add than the bare remover (which basically is-a listener to KeyboardFocusManager's focusOwner property):

/**
 * {@inheritDoc} <p>
 * Overridden to fix focus issues with editors. 
 * This method installs and updates the internal CellEditorRemover which
 * terminates ongoing edits if appropriate. Additionally, it
 * registers a CellEditorListener with the cell editor to grab the 
 * focus back to tree, if appropriate.
 * 
 * @see #updateEditorRemover()
 */
@Override
public void startEditingAtPath(TreePath path) {
    super.startEditingAtPath(path);
    if (isEditing()) {
        updateEditorListener();
        updateEditorRemover();
    }
}


/**
 * Hack to grab focus after editing.
 */
private void updateEditorListener() {
    if (editorListener == null) {
        editorListener = new CellEditorListener() {

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

            /**
             * @param e
             */
            private void terminated(ChangeEvent e) {
                analyseFocus();
                ((CellEditor) e.getSource()).removeCellEditorListener(editorListener);
            }

            @Override
            public void editingStopped(ChangeEvent e) {
                terminated(e);
            }

        };
    }
    getCellEditor().addCellEditorListener(editorListener);

}

/**
 * This is called from cell editor listener if edit terminated.
 * Trying to analyse if we should grab the focus back to the
 * tree after. Brittle ... we assume we are the first to 
 * get the event, so we can analyse the hierarchy before the
 * editing component is removed.
 */
protected void analyseFocus() {
    if (isFocusOwnerDescending()) {
        requestFocusInWindow();
    }
}


/**
 * Returns a boolean to indicate if the current focus owner 
 * is descending from this table. 
 * Returns false if not editing, otherwise walks the focusOwner
 * hierarchy, taking popups into account. <p>
 * 
 * PENDING: copied from JXTable ... should be somewhere in a utility
 * class?
 * 
 * @return a boolean to indicate if the current focus
 *   owner is contained.
 */
private boolean isFocusOwnerDescending() {
    if (!isEditing()) return false;
    Component focusOwner = 
        KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    // PENDING JW: special casing to not fall through ... really wanted?
    if (focusOwner == null) return false;
    if (SwingXUtilities.isDescendingFrom(focusOwner, this)) return true;
    // same with permanent focus owner
    Component permanent = 
        KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
    return SwingXUtilities.isDescendingFrom(permanent, this);
}



/**
 * Overridden to release the CellEditorRemover, if any.
 */
@Override
public void removeNotify() {
    if (editorRemover != null) {
        editorRemover.release();
        editorRemover = null;
    }
    super.removeNotify();
}

/**
 * Lazily creates and updates the internal CellEditorRemover.
 * 
 *
 */
private void updateEditorRemover() {
    if (editorRemover == null) {
        editorRemover = new CellEditorRemover();
    }
    editorRemover.updateKeyboardFocusManager();
}

/** This class tracks changes in the keyboard focus state. It is used
 * when the JXTree is editing to determine when to terminate the edit.
 * If focus switches to a component outside of the JXTree, but in the
 * same window, this will terminate editing. The exact terminate 
 * behaviour is controlled by the invokeStopEditing property.
 * 
 * @see javax.swing.JTree#setInvokesStopCellEditing(boolean)
 * 
 */
public class CellEditorRemover implements PropertyChangeListener {
    /** the focusManager this is listening to. */
    KeyboardFocusManager focusManager;

    public CellEditorRemover() {
        updateKeyboardFocusManager();
    }

    /**
     * Updates itself to listen to the current KeyboardFocusManager. 
     *
     */
    public void updateKeyboardFocusManager() {
        KeyboardFocusManager current = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        setKeyboardFocusManager(current);
    }

    /**
     * stops listening.
     *
     */
    public void release() {
        setKeyboardFocusManager(null);
    }

    /**
     * Sets the focusManager this is listening to. 
     * Unregisters/registers itself from/to the old/new manager, 
     * respectively. 
     * 
     * @param current the KeyboardFocusManager to listen too.
     */
    private void setKeyboardFocusManager(KeyboardFocusManager current) {
        if (focusManager == current)
            return;
        KeyboardFocusManager old = focusManager;
        if (old != null) {
            old.removePropertyChangeListener("permanentFocusOwner", this);
        }
        focusManager = current;
        if (focusManager != null) {
            focusManager.addPropertyChangeListener("permanentFocusOwner",
                    this);
        }

    }
    @Override
    public void propertyChange(PropertyChangeEvent ev) {
        if (!isEditing()) {
            return;
        }

        Component c = focusManager.getPermanentFocusOwner();
        JXTree tree = JXTree.this;
        while (c != null) {
            if (c instanceof JPopupMenu) {
                c = ((JPopupMenu) c).getInvoker();
            } else {

                if (c == tree) {
                    // focus remains inside the table
                    return;
                } else if ((c instanceof Window) ||
                        (c instanceof Applet && c.getParent() == null)) {
                    if (c == SwingUtilities.getRoot(tree)) {
                        if (tree.getInvokesStopCellEditing()) {
                            tree.stopEditing();
                        }
                        if (tree.isEditing()) {
                            tree.cancelEditing();
                        }
                    }
                    break;
                }
                c = c.getParent();
            }
        }
    }
}

Upvotes: 3

Ion Cojocaru
Ion Cojocaru

Reputation: 2583

you can add an anonymous instance of FocusListener and implement

void    focusLost(FocusEvent e)  

this gets triggered before the value is saved so will be not help you in getting the last value. Instead you should set

myTree.setInvokesStopCellEditing(true);

that fires a property change for the INVOKES_STOP_CELL_EDITING_PROPERTY, which

means that you need to have in your tree model something like

public void valueForPathChanged(TreePath path, Object newValue)
{
AdapterNode node = (AdapterNode)
path.getLastPathComponent();
node.getDomNode().setNodeValue((String)newValue);
fireTreeNodesChanged(new TreeModelEvent(this,
path));
} 

Regards

Upvotes: 1

Related Questions