Как я могу обновить 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