0

我想知道如何实现这个功能:

我有可编辑的 JTree,我可以在其中编辑节点的名称。如果我有一些节点是分支节点(其中有一些叶节点)并且在编辑时扩展了这个分支节点,编辑后这个节点将被折叠。

编辑完成后,如果该分支节点处于打开状态,我想保持该分支节点处于打开状态;如果它处于折叠状态,则该分支节点处于折叠状态。

我试图查看TreeWillExpandListener但它似乎不能解决我的问题,因为我需要在调用这些方法之前识别实际节点是否处于编辑模式......

如何做到这一点?很明显这是必要的,但我根本找不到答案:/

好的,这就是代码,我将尝试解释它。首先,我有实现 TreeModel 的类 ContactTreeModel。构造函数只是从主应用程序框架加载地址簿和组管理器,我创建新的根并在第二种方法中从数据库加载数据。

public ContactTreeModel() {
    addressBookManager = ContactManagerFrame.getAddressBookManager();
    groupManager = ContactManagerFrame.getGroupManager();
    root = new DefaultMutableTreeNode();
    processTreeHierarchy();
}

private void processTreeHierarchy() {
    DefaultMutableTreeNode group, contact;
    for (Group g : addressBookManager.getGroups()) {
        group = new DefaultMutableTreeNode(g);
        root.add(group);
        for (Contact c : addressBookManager.getContactsFromGroup(g)) {
            contact = new DefaultMutableTreeNode(c);
            group.add(contact);
        }
    }
}

我读到TreeModel 中的valueForPathChanged方法在以下情况下被启动

当用户将路径标识的项目的值更改为 newValue 时发送消息。如果 newValue 表示一个真正的新值,模型应该发布一个 treeNodesChanged 事件。

所以我写了这样的方法:

@Override
public void valueForPathChanged(TreePath path, Object newValue) {
    // backup of the original group
    Group oldGroup = (Group) path.getLastPathComponent();
    try {
        Group testGroup = (Group) path.getLastPathComponent();
        testGroup.setName((String) newValue);
        // validation of the group to be updated
        groupManager.validateGroup(testGroup, true);
        oldGroup.setName((String) newValue);
        // updating of the group in db
        groupManager.updateGroup(oldGroup);
    } catch (ServiceFailureException | ValidationException ex) {
        // if database error occured or validation exception is raised, 
        // update label in gui 
        ContactManagerFrame.getStatusPanelLabel().setText(ex.getMessage());
    } finally {
        fireTreeStructureChanged();
    }
}

注意 finally 块中的方法,该方法总是被启动。看起来像

protected void fireTreeStructureChanged() {
    Object[] o = {root};
    TreeModelEvent e = new TreeModelEvent(this, o);
    for (TreeModelListener l : treeModelListeners) {
        l.treeNodesChanged(e);
    }
}

这有点棘手,也是它不起作用的原因(我猜)。我只是遍历所有 treeModelListeners 并启动 treeNodesChanged 方法。

我将树模型侦听器指定的数组作为 ContactTreeModel 和相关方法中的私有属性:

private Vector<TreeModelListener> treeModelListeners = new Vector<>();

@Override
public void addTreeModelListener(TreeModelListener l) {
    treeModelListeners.addElement(l);
}

@Override
public void removeTreeModelListener(TreeModelListener l) {
    treeModelListeners.removeElement(l);
}

最后一件事,我添加到模型中的模型监听器长什么样?它来了:

public class ContactTreeModelListener implements TreeModelListener {
    @Override
    public void treeNodesChanged(TreeModelEvent e) {
        System.out.println("nodes changed");
    }

    @Override
        public void treeNodesInserted(TreeModelEvent e) {
        System.out.println("nodes inserted");
    }

    @Override
    public void treeNodesRemoved(TreeModelEvent e) {
        System.out.println("nodes removed");
    }

    @Override
    public void treeStructureChanged(TreeModelEvent e) {
        System.out.println("structure changed");
    }
}

所以它基本上什么都不做。我在其他地方注册了监听器,现在没关系。

所以,当我执行它时,在这种状态下,树不会崩溃,并且行为是想要的。但即使它真的重写了该节点的名称(标签),如果原始字符串大约是 5 个字符,而我将其更改为 4 个字符,那么标签中将只有 4 个字符,但还有第五个字符的空白空间。因此,在我完成编辑该标签后,原始标签的大小并没有改变。同样,当我扩展组的名称时,例如,我将其从 4 个字符重命名为 5 个字符,该节点的标签将包含点,表示整个文本太大而无法显示。这很奇怪......我如何制作该标签的更新日期?

最后一件事......因为我在 JTree 中有自定义图标 + 我在空组和非空组之间进行识别,所以每次我在树中执行一些操作时,我都需要检查 db 中的实际图标(我检查了它即使我只是打开和关闭节点也会执行)。这是非常低效的,所以我扩展了 DefaultTreeCellRenderer,我在其中进行实际缓存:

@Override
public Component getTreeCellRendererComponent(
        JTree tree,
        Object value,
        boolean sel,
        boolean expanded,
        boolean leaf,
        int row,
        boolean hasFocus) {

    if (iconCache == null) {
        throw new NullPointerException("iconCache in " 
             + this.getClass().getName() + " is null");
    }

    super.getTreeCellRendererComponent(
            tree, value, sel,
            expanded, leaf, row,
            hasFocus);

    if (value instanceof Group) {
        Group g = (Group) value;
        Icon groupIcon = iconCache.get(g);
        if (groupIcon == null) {
            if (groupHasContacts(g)) {
                groupIcon = groupNonEmpty;
            } else {
                groupIcon = groupEmptyIcon;
            }
            iconCache.put(g, groupIcon);
        }
        JLabel result = (JLabel) super.getTreeCellRendererComponent(tree,
                g.getName(), sel, expanded, leaf, row, hasFocus);

        result.setIcon(groupIcon);
        return result;
    }
    else if (value instanceof Contact) {
        Contact c = (Contact) value;
        Icon icon = iconCache.get(c);
        if (icon == null) {
            icon = this.contactIcon;
            iconCache.put(c, icon);
        }
        JLabel result = (JLabel) super.getTreeCellRendererComponent(
                tree, c.getName() + c.getSurname(), 
                sel, expanded, leaf, row, hasFocus);
        result.setIcon(icon);
        return result;
    }

    JLabel defaultNode = (JLabel) super.getTreeCellRendererComponent(
            tree, "?", sel, expanded, leaf, row, hasFocus);

    defaultNode.setIcon(unknownNode);
    return defaultNode;
}
4

1 回答 1

0

未正确重绘标签的更新问题可能与您仅在标识路径的数组中触发treeNodesChanged事件有关。root

尝试从以下位置调用valueForPathChanged

public void fireTreeNodesChanged(TreePath path) {
    TreeModelEvent e = new TreeModelEvent(this, path.getPath());
    for (TreeModelListener l : treeModelListeners) {
        l.treeNodesChanged(e);
    }
}

顺便说一句,你的名字fireTreeStructureChanged实际上treeNodesChanged很容易引起误解。

于 2012-05-03T23:16:54.573 回答