Как я могу обновить JTree после добавления некоторых узлов в базовую модель? - PullRequest
14 голосов
/ 16 декабря 2009

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

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

ОБНОВЛЕНИЕ 2: Моя проблема была не в том, как уведомить зрителя (JTree), а в том, каким образом jtree следует перезагрузить после уведомления от модели.

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

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

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

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

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

Update3: Я обнаружил, что существуют неявные зарегистрированные TreeModelListeners. В реализации моей модели addTreeModelListener (TreeModelListener listener) я поставил отладочную строку system.out:

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

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

добавлен слушатель: javax.swing.JTree.TreeModelHandler

добавлен слушатель: javax.swing.plaf.basic.BasicTreeUI.Handler

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

Ответы [ 8 ]

18 голосов
/ 19 января 2013

Я столкнулся с той же «проблемой»: вызов treeNodesInserted() не заставил мой JTree обновить его содержимое.

Но проблема была в другом месте: я использовал неправильный конструктор для TreeModelEvent. Я думал, что могу создать TreeModelEvent для treeNodesInserted() так:

//-- Wrong!!
TreePath path_to_inserted_item = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(my_source, path_to_inserted_item);

Это не работает.

Как указано в TreeModelEvent документах , этот конструктор необходим только для 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 с, особенно с JTree, поэтому я хочу рассказать о некоторых вопросах, которые возникли у меня, когда я пытался выяснить как бороться со всем этим.

Во-первых, как отметил Паралифе в своем комментарии, случаи, когда узлы вставляются / изменяются / удаляются или когда изменяется древовидная структура, не являются ортогональными. Таким образом,

Вопрос № 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)
           )
        );

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


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

14 голосов
/ 16 декабря 2009

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

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);
5 голосов
/ 03 февраля 2010

Мне также показалось, что реализация 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));
        }
}
4 голосов
/ 15 февраля 2014

Вчера я боролся, чтобы решить ту же проблему. Требовалось вставлять и удалять узлы на лету, не сворачивая развернутые узлы дерева. Я просмотрел сеть и нашел кучу возможных решений, пока не наткнулся на этот поток , Затем я применил ответ от Дмитрия Франка с TreeModelEvent. Я был немного смущен, почему такая большая проблема - просто вставить или удалить простой узел и оставить остальную часть 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());

Будьте проще;)

3 голосов
/ 16 декабря 2009

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

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

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

2 голосов
/ 17 декабря 2009

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

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

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

0 голосов
/ 05 июля 2017

Кажется возможным переинициализировать все дерево, установив для модели значение null: например,

        TreePath path = tree.getSelectionPath();
        model.doChanges(); // do something with model
        tree.setModel(null);
        tree.setModel(model);
        tree.setSelectionPath(path);
        tree.expandPath(path);

У меня работает обновление дерева

кр Ахим

0 голосов
/ 20 августа 2016

Например: динамическое обновление 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
...