Как я могу обновить JTree после добавления некоторых узлов в базовую модель?
Прежде всего, позвольте мне сказать, что я не использую DefaultTreeModel. Я реализую свой собственный TreeModel, поэтому я не могу использовать материал DefaultXXX. Проблема заключается в следующем: с помощью некоторых методов addStuff(), которые определяет моя модель, я добавляю узлы в базовую структуру данных. Затем я уведомляю слушателей, вызывая treeNodesChanged() внутри функции addStuff() (я знаю, что есть методы treeNodesInserted, но это то же самое, он просто уведомляет слушателей другим способом). Теперь один из слушателей - это статический класс в моей основной форме, и этот слушатель может рассказать JTree, который также содержится в моей основной форме, для обновления самого себя. Как сообщить JTree "перезагрузить" некоторые или все его узлы из модели?
UPDATE:
Найденный question, хотя это не совсем то же самое, он дает ответ, который я хочу.
ОБНОВЛЕНИЕ 2:
Моя проблема заключалась не в том, как уведомить зрителя (JTree), а скорее о том, каким образом jtree будет перезагружаться после уведомления из модели.
Прежде всего позвольте мне сказать, что единственный способ узнать дерево, чтобы отразить основные изменения, - вызвать updateUI() или повторно использовать метод setModel(). По сути, моя проблема заключается в следующем:
Предположим, что TreeModelListener только что был уведомлен (через API TreeModelListener), что изменение произошло в модели. Хорошо, что теперь?
У меня есть эта проблема, потому что JTree не реализует TreeModelListener. Таким образом, слушателем в моей ситуации является контейнер JTree или внутренний класс, реализующий Listener, живущий в том же контейнере, что и Jtree.
Итак, предположим, что я - реализация TreeModelListener, живущей счастливо в JForm с моим братом JTree. Внезапно вызывается мой метод treeNodesInserted (TreeModelEvent evt). Что мне теперь делать? Если я вызову Jtree.updateUI() изнутри меня, то список прослушивателей моделей выдает ConcurrentModification Exception. Могу ли я вызвать что-то другое, кроме updateUI?
Я пробовал несколько вещей, но только updateUI обновил JTree. Поэтому я сделал это за пределами слушателя. Из JForm я просто вызываю метод модели, который изменяет структуру, остающуюся без ссылки, а затем я вызываю updateUI. Нет использования TreeModelListener.
Update3:
Я узнал, что зарегистрированы неявные данные TreeModelListeners. В моей модели addTreeModelListener (ресивер TreeModelListener) я установил строку отладки system.out:
System.out.println("listener added: " + listener.getClass().getCanonicalName());
и я увидел этот отладочный вывод, только когда я выполнил jTree.setModel(model):
прослушиватель добавлен: javax.swing.JTree.TreeModelHandler
прослушиватель добавлен: javax.swing.plaf.basic.BasicTreeUI.Handler
Ошибка ConcurrentModificationException вызвана тем, что вызов jtree.updateUI() повторно регистрирует слушателя (только плафон, а не оба), поэтому он возникает, когда я вызываю updateUI внутри цикла уведомления слушателя.
Единственный способ обновить дерево - сделать это за пределами TreeModelListener. Любые комментарии или идеи для лучшего решения? Я что-то пропустил?
Ответы
Ответ 1
Я столкнулся с одной и той же "проблемой": вызов treeNodesInserted()
не заставил мой JTree
обновить его содержимое.
Но проблема была в другом месте: я использовал неправильный конструктор для TreeModelEvent
. Я думал, что я могу создать TreeModelEvent
для treeNodesInserted()
следующим образом:
//-- Wrong!!
TreePath path_to_inserted_item = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(my_source, path_to_inserted_item);
Это не работает.
Как указано в TreeModelEvent
docs, этот конструктор нужен только для treeStructureChanged()
. Но для treeNodesInserted()
, treeNodesRemoved()
, treeNodesChanged()
мы должны использовать другой конструктор:
TreePath path_to_parent_of_inserted_items = /*....*/ ;
int[] indices_of_inserted_items = /*....*/ ;
Object[] inserted_items = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(
my_source,
path_to_parent_of_inserted_items,
indices_of_inserted_items,
inserted_items
);
Этот код работает, а JTree
правильно обновляет его содержимое.
UPD: Фактически, документы неясно об использовании этих TreeModelEvent
s, и особенно с JTree
, поэтому я хочу рассказать о некоторых вопросах, которые пришли ко мне, когда я попытался понять как справиться со всем этим.
Во-первых, поскольку Паралайф отметил это своим комментарием, случаи, когда узлы вставлены/изменены/удалены или когда древовидная структура изменена, не являются ортогональными. Таким образом,
Вопрос № 1:, когда мы должны использовать treeNodesInserted()
/Changed()
/Removed()
, а когда treeStructureChanged()
?
Ответ: treeNodesInserted()
/Changed()
/Removed()
может использоваться, если только все затронутые узлы имеют один и тот же родительский элемент. В противном случае вы можете сделать несколько вызовов этих методов или просто вызвать treeStructureChanged()
один раз (и передать ему root node затронутых узлов). Итак, treeStructureChanged()
является своего рода универсальным способом, а treeNodesInserted()
/Changed()
/Removed()
более конкретным.
Вопрос № 2: Насколько treeStructureChanged()
является универсальным способом, почему мне нужно иметь дело с этими treeNodesInserted()
/Changed()
/Removed()
? Просто вызов treeStructureChanged()
кажется более простым.
Ответ.. Если вы используете JTree
для отображения содержимого вашего дерева, то для вас может быть сюрпризом (как и для меня): когда вы вызываете treeStructureChanged()
, то JTree
не сохраняет состояние суб-узлов! Рассмотрим пример, здесь содержимое нашего JTree
теперь:
[A]
|-[B]
|-[C]
| |-[E]
| | |-[G]
| | |-[H]
| |-[F]
| |-[I]
| |-[J]
| |-[K]
|-[D]
Затем вы вносите некоторые изменения в C
(скажем, переименуйте его на C2
), и вы вызываете treeStructureChanged()
для этого:
myTreeModel.treeStructureChanged(
new TreeModelEvent(
this,
new Object[] { myNodeA, myNodeC } // Path to changed node
)
);
Затем узлы E
и F
будут свернуты! И ваш JTree
будет выглядеть так:
[A]
|-[B]
|-[C2]
| +-[E]
| +-[F]
|-[D]
Чтобы этого избежать, вы должны использовать treeNodesChanged()
, например:
myTreeModel.treeNodesChanged(
new TreeModelEvent(
this,
new Object[] { myNodeA }, // Path to the _parent_ of changed item
new int[] { 1 }, // Indexes of changed nodes
new Object[] { myNodeC }, // Objects represents changed nodes
// (Note: old ones!!!
// I.e. not "C2", but "C",
// in this example)
)
);
Тогда состояние расширения будет сохранено.
Я надеюсь, что этот пост будет полезен для кого-то.
Ответ 2
Я всегда находил TreeModel немного запутанным. Я согласен с вышеприведенным утверждением о том, что модель должна уведомить точку зрения при внесении изменений, чтобы представление могло перекрасить себя. Однако это не похоже на использование DefaultTreeModel. Я считаю, что вам нужно вызвать метод reload() после обновления модели. Что-то вроде:
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);
Ответ 3
Я также обнаружил, что использование TreeModel немного запутанно, когда дерево состоит из нескольких папок (root) и Files (child), поэтому я использовал DefaultTreeModel. Это работает для меня. Метод создает или обновляет JTree. Надеюсь, это поможет.
public void constructTree() {
DefaultMutableTreeNode root =
new DefaultMutableTreeNode(UbaEnv.DB_CONFIG);
DefaultMutableTreeNode child = null;
HashMap<String, DbConfig> dbConfigs = env.getDbConfigs();
Iterator it = dbConfigs.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
child = new DefaultMutableTreeNode(pair.getKey());
child.add(new DefaultMutableTreeNode(UbaEnv.PROP));
child.add(new DefaultMutableTreeNode(UbaEnv.SQL));
root.add(child);
}
if (tree == null) {
tree = new JTree(new DefaultTreeModel(root));
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addTreeSelectionListener(new TreeListener());
} else {
tree.setModel(new DefaultTreeModel(root));
}
}
Ответ 4
Ваш TreeModel должен запускать TreeModelEvents, когда он изменяется, и JTree наблюдает вашу модель, хотя TreeModelListener обновляется при изменении вашей модели.
Итак, если вы правильно поддерживаете поддержку TreeModelListener, вам не нужно наблюдать модель и сообщать JTree, поскольку она уже делает это сама. С точки зрения MVC, JTree - это ваш View/Controller, а TreeModel - ваша модель (или, скорее, адаптер модели), которая является наблюдаемой.
Вы можете попытаться заставить JTree обновить свое визуальное состояние, вызвав repaint(), но я бы рекомендовал не делать этого, так как он не гарантировал работу.
Когда вы не знаете, как выполнять мелкомасштабное уведомление TreeModelListener, используйте TreeModelListener.treeStructureChanged(..), чтобы уведомить об обновлении "всей модели" (предупреждение: может привести к потере выбора и состояния расширения node)..
Ответ 5
ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ:
Нашел проблему и решил ее: Следующие шаги решают проблему (но см. Принятый ответ для лучшего решения и глубокое объяснение проблемы):
- Зарегистрированных неявных слушателей достаточно для выполнения задания. Нет необходимости реализовывать мой собственный слушатель.
- При добавлении узлов и вызове
treeNodesInserted()
он не работает (JTree не обновляется). Но он работает с вызовом treeStructureChanged()
.
По-видимому, неявные слушатели внутренне обновляют дерево так, как я хочу, но только в реализации метода treeStructureChanged()
. Было бы хорошо, если бы JTree предоставил этот "алгоритм" как функцию, чтобы иметь возможность вызываться вручную.
Ответ 6
Вчера я изо всех сил пытался решить ту же проблему. Требовалось было вставлять и удалять узлы "на лету", не сворачивая расширенные узлы дерева. Я просмотрел веб-сайт и нашел множество возможных решений, пока не наткнулся на этот поток. Затем я применил anwser от "Dmitry Frank" с помощью TreeModelEvent
. Я был немного смущен, почему такая большая проблема - просто вставить или удалить простой node, а оставшуюся часть JTree
нетронутой!
Наконец, простые ванильные примеры из java2s помогли мне найти, вероятно, самое простое решение. (Ни один вызов вроде: nodeStructureChanged
, nodeChanged
, nodesWereRemoved
, nodesWereInserted
и т.д., И не было TreeModelEvent
, как предлагалось "Дмитрием Фрэнком".)
Здесь мое решение:
// Remove a node
treeModel.removeNodeFromParent(myNode);
// Insert a node (Actually this is a reinsert, since I used a Map called `droppedNodes` to keep the previously removed nodes. So I don't need to reload the data items of those nodes.)
MyNode root = treeModel.getRootNode();
treeModel.insertNodeInto(droppedNodes.get(nodeName), root, root.getChildCount());
Держите его простым;)
Ответ 7
Например: Jtree Refresh Dynamicically
package package_name;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
public final class homepage extends javax.swing.JFrame implements ActionListener
{
DefaultTreeModel model;
DefaultMutableTreeNode root;
File currentDir = new File("database");
public homepage() throws InterruptedException {
initComponents();
//------full screen window
this.setExtendedState(MAXIMIZED_BOTH);
//====================Jtree Added Details..
//result is the variable name for jtree
model =(DefaultTreeModel) treedbnm.getModel();
//gets the root of the current model used only once at the starting
root=(DefaultMutableTreeNode) model.getRoot();
//function called
dbcreate_panel1();
Display_DirectoryInfo(currentDir,root);
}//End homepage() Constructor
public void Display_DirectoryInfo(File dir,DefaultMutableTreeNode tmp_root) throws InterruptedException
{..........
}
public void dbcreate_panel1()
{
//------------------Jtree Refresh Dynamically-------------------//
root.removeFromParent();
root.removeAllChildren();
model.reload();
Display_DirectoryInfo(currentDir,root);
}
}//End homepage
Ответ 8
Кажется, что можно повторно инициализировать все дерево, установив модель в null:
например.
TreePath path = tree.getSelectionPath();
model.doChanges(); // do something with model
tree.setModel(null);
tree.setModel(model);
tree.setSelectionPath(path);
tree.expandPath(path);
Обновление дерева для меня работает
kr Achim