web-dev-qa-db-ja.com

基になるモデルにノードを追加した後、JTreeを更新するにはどうすればよいですか?

まず、DefaultTreeModelを使用しないとしましょう。私は自分のTreeModelを実装しているので、DefaultXXXのものを使用できません。問題はこれです:モデルが定義するいくつかのaddStuff()メソッドを使用して、基になるデータ構造にノードを追加します。次に、addStuff()関数内でtreeNodesChanged()を呼び出してリスナーに通知します(treeNodesInsertedメソッドがあることは知っていますが、同じことです。異なるメソッドでリスナーに通知するだけです)。これで、リスナーの1つがメインフォームの静的クラスになり、このリスナーはメインフォームにも含まれているJTreeに自身をリフレッシュするように指示できます。 JTreeにノードの一部またはすべてをモデルから「再ロード」するように指示するにはどうすればよいですか?

更新:これが見つかりました 質問 完全に同じではありませんが、それが私が望む答えを与えます。

更新2:私の問題は、ビューア(JTree)に通知する方法ではなく、モデルからの通知後にjtreeをどのようにリロードするかでした。

まず、根本的な変更を反映するためにツリーを更新する唯一の方法は、updateUI()を呼び出すか、setModel()メソッドを再利用することです。基本的に、私の問題はこれです:

TreeModelListenerが(TreeModelListener APIを介して)モデルで変更が発生したことを通知されたと仮定します。さて、今何ですか?

JTreeはTreeModelListenerを実装していないため、この問題が発生します。したがって、私の状況では、リスナーはJTreeのコンテナー、またはJtreeと同じコンテナーの下にあるリスナーを実装する内部クラスです。

それで、私がTreeModelListenerの実装で、兄のJTreeと一緒にJFormで楽しく生活しているとします。突然、私のメソッドtreeNodesInserted(TreeModelEvent evt)が呼び出されます。私は今何をしますか?内部からJtree.updateUI()を呼び出すと、モデルのリスナーリストがConcurrentModification Exceptionをスローします。 updateUI以外のものを呼び出すことはできますか?

多くのことを試しましたが、JTreeを更新したのはupdateUIだけでした。だから私はそれをリスナーの外でやった。 JFormから、モデルのメソッドを呼び出して、構造を変更し、次にupdateUIを呼び出します。 TreeModelListenerは使用されません。

UPDATE3:暗黙のTreeModelListenerが登録されていることがわかりました。モデルのaddTreeModelListener(TreeModelListener listener)実装に、デバッグ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()への呼び出しがリスナーを再登録するために発生します(両方ではなく、plafのみ)。したがって、リスナー通知ループ内でupdateUIを呼び出すとスローされます。ツリーを更新する唯一の方法は、TreeModelListenerの外で行うことです。より良い解決策のためのコメントやアイデアはありますか?何か不足していますか?

16
Paralife

同じ「問題」に直面しました。treeNodesInserted()を呼び出しても、JTreeが内容を更新することはありませんでした。

しかし、問題は別の場所にありました:TreeModelEventに間違ったコンストラクターを使用しました。私はそのようにtreeNodesInserted()TreeModelEventを作成できると思いました:

//-- 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()

Answer:treeNodesInserted()/Changed()/Removed()は、影響を受けるすべてのノードだけが持っている場合に使用できます同じ親。それ以外の場合は、これらのメソッドを複数回呼び出すか、treeStructureChanged()を1回だけ呼び出す(そして、影響を受けるノードのルートノードをそれに渡す)ことができます。したがって、treeStructureChanged()は一種の普遍的な方法ですが、treeNodesInserted()/Changed()/Removed()はより具体的です。

質問#2:treeStructureChanged()が普遍的な方法である限り、なぜこれらのtreeNodesInserted()に対処する必要があるのですか?/Changed()/Removed()treeStructureChanged()を呼び出すだけの方が簡単なようです。

Answer: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)
           )
        );

その後、エキスパンド状態が保持されます。


この投稿が誰かにとって役立つことを願っています。

18
Dmitry Frank

私はいつもTreeModelを少し混乱させることに気づきました。ビューがそれ自体を再描画できるように、変更が行われたときにモデルがビューに通知する必要があるという上記の記述に同意します。ただし、DefaultTreeModelを使用する場合はそうではありません。モデルを更新した後でreload()メソッドを呼び出す必要があることがわかりました。何かのようなもの:

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

また、ツリーがFolders(root)とFiles(child)だけで構成されていない場合、TreeModelの実装が少しわかりにくいので、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));
        }
}
5
Ariel

昨日、私は同じ問題を修正するために奮闘しました。 要件は、展開されたツリーノードを折りたたむことなく、その場でノードを挿入および削除することでした。私はこのスレッドに出くわすまで、ウェブを閲覧して、可能な解決策の束を見つけました。次に、TreeModelEventを使用して「Dmitry Frank」からのアンサーを適用しました。少し混乱しました。単純なノードを挿入または削除して、残りのJTreeをそのままにしておくのはなぜそれほど大きな問題なのでしょうか。最後に Java2s の単純なバニラの例は、おそらく最も簡単な解決策を見つけるのに役立ちました。 (nodeStructureChangednodeChangednodesWereRemovednodesWereInsertedなどのような呼び出しも、 'Dmitry Frankが提案する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());

単純にする ;)

4
My-Name-Is

TreeModelは、変更時にTreeModelEventsを起動することになっています。JTreeはTreeModelListenerを介してモデルを監視し、モデルが変更されると自身を更新します。

したがって、TreeModelListenerサポートを正しく実装するの場合、モデルを監視してJTreeに通知する必要はありません。 MVCの観点から、JTreeはビュー/コントローラーであり、TreeModelはモデル(またはモデルアダプター)であり、監視可能です。

Repaint()を呼び出して、JTreeに視覚的な状態を強制的に更新させることもできますが、動作が保証されていないため、そうしないことをお勧めします。 TreeModelListenerにきめ細かい通知を行う方法がわからない場合は、TreeModelListener.treeStructureChanged(..)を使用して「モデル全体」の更新を通知します(警告:選択とノード展開の状態が失われる可能性があります)。

3
Peter Walser

最終更新:問題を見つけて解決しました:次の手順で問題を解決します(ただし、より良い解決策と問題の詳細な説明については、承認された回答を参照してください)

  1. 登録された暗黙のリスナーは、仕事をするのに十分です。自分のリスナーを実装する必要はありません。
  2. ノードを追加してtreeNodesInserted()を呼び出すと、機能しません(JTreeは更新されません)。しかし、これはtreeStructureChanged()の呼び出しで機能します。

どうやら、暗黙のリスナーは、私が望む方法でツリーを内部的に更新していますが、それらのtreeStructureChanged()メソッド実装でのみです。 JTreeが手動で呼び出せるようにするために、この「アルゴリズム」を関数として提供するとよいでしょう。

2
Paralife

モデルをnullに設定することで、ツリー全体を再初期化することが可能であるようです。

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

ツリー更新は私のために働きます

krアヒム

1
herberlin

次に例を示します。

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
0
Chetan Bhagat