1

我通过扩展 DefaultTreeModel 创建了一个自定义 TreeModel,因此用户可以重命名我的 JTree 中的节点。如果我的用户输入一个新名称然后按回车,这很好用。如果用户没有按回车键,而是单击远离节点,那么我的 valueForPathChanged 方法不会触发,并且我无法获取新的字符串。如何在用户不按 Enter 键而不是单击树/面板/框架中的其他位置的情况下获取新用户字符串?

4

2 回答 2

3

为了稍微改善这种情况,您可以设置 JTree 的 invokesStopCellEditing 属性:如果为 true,则 ui 将提交对某些内部更改(例如扩展或选择更改)的挂起编辑。

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

该片段(用于播放)还表明 focusListener 不起作用。

CellEditorRemover 及其在 JXTree 中的用法(如您所见,要添加的内容比裸移除器(它基本上是 KeyboardFocusManager 的 focusOwner 属性的侦听器)稍微多一点):

/**
 * {@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();
            }
        }
    }
}
于 2013-03-13T17:43:09.757 回答
1

您可以添加 FocusListener 的匿名实例并实现

void    focusLost(FocusEvent e)  

这会在保存值之前触发,因此不会帮助您获取最后一个值。相反,您应该设置

myTree.setInvokesStopCellEditing(true);

触发 INVOKES_STOP_CELL_EDITING_PROPERTY 的属性更改,这

意味着你需要在你的树模型中有类似的东西

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

问候

于 2013-03-13T17:21:57.773 回答