我想知道如何实现这个功能:
我有可编辑的 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);
}
}
}
我读到树模型中的方法值为路径更改被触发,如果
当用户更改了由 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 个字符,该节点的标签将包含表示整个文本太大而无法显示。这很奇怪...如何制作 udpdate那个标签?
最后一件事...由于我在 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;
}
未正确重新绘制的标签更新问题可能与您在标识路径的数组中仅使用root
触发treeNodesChanged
事件有关。
尝试从valueForPathChanged
调用以下内容:
public void fireTreeNodesChanged(TreePath path) {
TreeModelEvent e = new TreeModelEvent(this, path.getPath());
for (TreeModelListener l : treeModelListeners) {
l.treeNodesChanged(e);
}
}
顺便说一句,你对实际触发treeNodesChanged
的fireTreeStructureChanged
的名字非常具有误导性。