Как обновить JTree после добавления некоторых узлов в базовую модель?

прежде всего, позвольте мне сказать, что я не использую DefaultTreeModel. Я реализую свою собственную TreeModel, поэтому я не могу использовать материал DefaultXXX. Проблема заключается в следующем: с помощью некоторых методов addStuff (), которые определяет моя модель, я добавляю узлы в базовую структуру данных. Затем я уведомляю слушателей, вызывая treeNodesChanged() внутри функции addStuff () (я знаю, что есть методы treeNodesInserted, но это то же самое. Он просто уведомляет слушателей другим методом). Итак, один из слушателей является статическим классом в моей основной форме, и этот слушатель может сказать JTree, который также содержится в моей основной форме, чтобы обновить себя. Как сказать JTree "перезагрузить" некоторые или все его узлы из модели?

обновление: Нашел это вопрос это хотя и не совсем то же самое, но дает ответ, который я хочу.

обновление 2: Моя проблема заключалась не в том, как уведомить зрителя (JTree), а в том, каким образом jtree должен быть перезагружен после уведомления от модель.

прежде всего позвольте мне сказать, что единственный способ обновить дерево для отражения базовых изменений-это вызвать updateUI () или повторно использовать метод setModel (). По сути, моя проблема заключается в следующем:

предположим, что TreeModelListener только что был уведомлен (через API TreeModelListener), что в модели произошло изменение. Ладно, что теперь?

У меня есть эта проблема, потому что JTree не реализует TreeModelListener. Итак, слушатель, в моем ситуация-это контейнер JTree или внутренний класс, реализующий прослушиватель, живущий в том же контейнере, что и Jtree.

Итак, предположим, что я реализация TreeModelListener, счастливо живущая в JForm с моим братом JTree. Внезапно вызывается мой метод treeNodesInserted(TreeModelEvent evt). Что мне теперь делать? Если я называю jtree из.updateUI () изнутри меня, то список прослушивателей модели вызывает исключение ConcurrentModification. Могу ли я назвать что-то еще, кроме обновить?

Я пробовал несколько вещей, но только updateUI обновил JTree. Поэтому я сделал это вне слушателя. Из JForm я просто вызываю метод модели, который изменяет структуру undrlying, а затем вызываю updateUI. Нет TreeModelListener привыкает.

UPDATE3: Я узнал, что есть неявные TreeModelListeners зарегистрированы. В реализации моей модели addTreeModelListener(treemodellistener listener) Я поставил отладочную систему.out line:

System.out.println("listener added: " + listener.getClass().getCanonicalName());

и я видел этот отладочный вывод, когда я выполнял jTree.сетмоделя(модель):

добавлен прослушиватель: javax.качать.Jtree из.TreeModelHandler

добавлен прослушиватель: javax.качать.plafбыл.основной.BasicTreeUI.Обработчик

ConcurrentModificationException вызвано, потому что вызов jtree.updateUI () повторно регистрирует прослушиватель (только plaf, а не оба), поэтому он вызывается, когда я вызываю updateUI внутри цикла уведомлений прослушивателя. Этот единственный способ обновить дерево-сделать это за пределами TreeModelListener. Любые комментарии или идеи для лучшего решения? Я что-то упускаю?

8 ответов


я столкнулся с той же проблемой"": вызов 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: на самом деле, документы неясны об использовании этих TreeModelEventS, и особенно с JTree, Итак, я хочу рассказать о некоторых вопросах, которые пришли ко мне, когда я пытался понять, как справиться со всем этим.

во-первых, как Paralife отметил, что его комментарий, случаи, когда узлы вставляются/изменен/удален, или когда дерево структура изменились, не ортогональны. Итак,

Вопрос № 1: когда мы должны использовать treeNodesInserted()/Changed()/Removed(), и когда treeStructureChanged()?

ответ: treeNodesInserted()/Changed()/Removed() может использоваться, если только все затронутые узлы имеют один и тот же родитель. В противном случае вы можете сделать несколько вызовов этих методов, или просто позвоните treeStructureChanged() один раз (и передайте ему корневой узел затронутых узлов). Итак,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)
           )
        );

тогда расширяющееся состояние будет сохранено.


я надеюсь этот пост кому-то пригодится.


Я всегда находил TreeModel немного запутанным. Я согласен с приведенным выше утверждением, что модель должна уведомлять представление при внесении изменений, чтобы представление могло перекрасить себя. Однако это, по-видимому, не так при использовании DefaultTreeModel. Я считаю, что вам нужно вызвать метод reload() после обновления модели. Что-то вроде:

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);

Я также нашел реализацию TreeModel немного запутанной, когда дерево состоит не только из папок(root) и файлов (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));
        }
}

предполагается, что ваша TreeModel запускает TreeModelEvents при изменении, и JTree наблюдает за вашей моделью, хотя TreeModelListener обновляет себя при изменении вашей модели.

Если вы реализовать поддержку TreeModelListener правильно, вам не нужно наблюдать за моделью и информировать JTree, так как он уже делает это сам. С точки зрения MVC JTree - это ваш вид / контроллер, а TreeModel-ваша модель (или, скорее: адаптер модели), которая заметный.

вы можете попытаться заставить JTree обновить свое визуальное состояние, вызвав repaint (), но я бы рекомендовал этого не делать, поскольку это не гарантировано. Если вы не знаете, как сделать мелкозернистое уведомление TreeModelListener, используйте TreeModelListener.treeStructureChanged(..) для уведомления об обновлении "всей модели" (предупреждение: может привести к потере выбора и состояний расширения узла).


Я struggeled вокруг, чтобы исправить тот же вопрос. требование состояло в том, чтобы вставлять и удалять узлы "на лету", не сворачивая узлы развернутого дерева. я просмотрел интернет и нашел кучу возможных решений, пока не наткнулся на эту тему. Затем я применил anwser от "Dmitry Frank" с TreeModelEvent. Я был немного смущен, почему это такая большая проблема, чтобы просто вставить или удалить простым узлом и пусть остальные JTree нетронутый! Наконец, простые примеры ванили из java2s помог мне найти, вероятно, самое простое решение вообще. (Ни один звонок, как:nodeStructureChanged, nodeChanged, nodesWereRemoved, nodesWereInserted, etc. ни 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());

сохранить его простым ;)


ОКОНЧАТЕЛЬНОЕ ОБНОВЛЕНИЕ: Нашел проблему и решил ее: следующие шаги решают проблему,(но см. принятый ответ для лучшего решения и глубокого объяснения проблемы):

  1. зарегистрированных неявных слушателей достаточно, чтобы выполнить эту работу. Не нужно реализовывать моего собственного слушателя.
  2. при добавлении узлов и вызов treeNodesInserted() Он не работает (JTree не обновляется). Но это работает с вызовом treeStructureChanged().

по-видимому, неявные слушатели внутренне освежают дерево так, как я хочу, но только в их treeStructureChanged() реализация метода. Было бы хорошо для JTree предоставить этот "алгоритм" в качестве функции, чтобы иметь возможность вызываться вручную.


например: Jtree обновить динамически

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

кажется возможным повторно инициализировать все дерево, установив для модели значение 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