Java SwingのJTreeを完全マスター!javax.swing.treeの使い方詳細解説

この記事から得られる知識

  • JTreeの基本構造: ツリー、ノード、モデルといったJTreeを構成する基本的な要素の関係性を理解できます。
  • 静的および動的なツリー作成: 事前に定義された静的なツリーの作成方法から、実行時にノードを追加・削除する動的なツリーの構築方法までを学べます。
  • 見た目のカスタマイズ: `TreeCellRenderer`を用いて、ノードのアイコンやテキスト、背景色などを自由に変更し、アプリケーションのUIを向上させる方法を習得できます。
  • ユーザー操作のハンドリング: `TreeSelectionListener`を使い、ユーザーによるノードの選択イベントを検知し、それに応じた処理を実行する方法を理解できます。
  • 実践的な活用テクニック: 右クリックでコンテキストメニューを表示するなど、より実践的なJTreeの活用例を学べます。

はじめに: javax.swing.treeとは?

Java Swingは、JavaでデスクトップアプリケーションのGUI(グラフィカルユーザーインターフェース)を開発するための標準的なツールキットの一部です。その中でも、javax.swing.treeパッケージは、ファイルシステムのエクスプローラーや組織図のように、階層的なデータを視覚的に表現するためのコンポーネント群を提供します。

中心となるクラスはJTreeで、このクラスを利用することで、ユーザーが直感的に操作できるツリー構造のUIを簡単にアプリケーションに組み込むことができます。しかし、JTreeを効果的に使いこなすには、その背後にあるデータ構造、すなわち「モデル」や、各項目の表示を制御する「レンダラー」の概念を理解することが不可欠です。

この記事では、JTreeの基本的な作成方法から、データの動的な操作、表示のカスタマイズ、イベント処理といった応用的なテクニックまで、網羅的に詳しく解説していきます。


第1章: JTreeの基本概念 – MVCアーキテクチャ

JTreeの構造を理解する上で最も重要なのが、モデル・ビュー・コントローラ(MVC)という設計パターンです。Swingコンポーネントの多くはこのパターンに基づいて設計されており、JTreeも例外ではありません。

コンポーネント 役割 対応する主なクラス・インタフェース
Model (モデル) ツリーのデータ構造そのものを管理します。ノードの階層関係、データの追加や削除といったロジックを担当します。 TreeModel, DefaultTreeModel, TreeNode, MutableTreeNode, DefaultMutableTreeNode
View (ビュー) モデルが保持するデータを画面上にどのように表示するかを担当します。ツリーの見た目、アイコン、テキストの描画などを制御します。 JTree, TreeCellRenderer, DefaultTreeCellRenderer
Controller (コントローラ) ユーザーからの入力(クリック、ドラッグなど)を受け取り、モデルやビューに適切な変更を指示します。 TreeSelectionListener, TreeExpansionListener, UIデリゲート (例: BasicTreeUI)

このMVCアーキテクチャにより、「データ(Model)」と「見た目(View)」が分離されます。これにより、例えば同じデータ構造を保ったまま、見た目だけを自由に変更したり、データの変更を自動的に表示に反映させたりすることが容易になります。この分離の概念を念頭に置くことで、以降の章の理解が格段に深まります。


第2章: 静的なツリーを作成する

まずは最も基本的な、あらかじめデータ構造が決定している「静的なツリー」を作成する方法を見ていきましょう。ここでは、ツリーの各項目であるノードを作成し、それらを階層的に組み立てていきます。

2.1. ノードの作成: DefaultMutableTreeNode

ツリーの各要素は「ノード」と呼ばれます。Swingでは、javax.swing.tree.TreeNodeインタフェースを実装したオブジェクトがノードとして機能します。しかし、通常はより便利な機能を持つDefaultMutableTreeNodeクラスを使用します。このクラスは、ノードに子ノードを追加したり、削除したりする機能を提供します。

以下は、いくつかのノードを作成し、親子関係を構築する例です。


// ルートノード(一番上の階層)を作成
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("ルート");

// 第1階層のノードを作成
DefaultMutableTreeNode category1 = new DefaultMutableTreeNode("カテゴリ1");
DefaultMutableTreeNode category2 = new DefaultMutableTreeNode("カテゴリ2");

// ルートノードに第1階層のノードを追加
rootNode.add(category1);
rootNode.add(category2);

// 第2階層のノードを作成
DefaultMutableTreeNode item1_1 = new DefaultMutableTreeNode("アイテム1-1");
DefaultMutableTreeNode item1_2 = new DefaultMutableTreeNode("アイテム1-2");
DefaultMutableTreeNode item2_1 = new DefaultMutableTreeNode("アイテム2-1");

// 第1階層のノードに第2階層のノードを追加
category1.add(item1_1);
category1.add(item1_2);
category2.add(item2_1);
      

2.2. JTreeのインスタンス化と表示

ノードの階層構造が完成したら、それをJTreeのコンストラクタに渡すだけでツリーコンポーネントが作成できます。作成したJTreeは、他のSwingコンポーネントと同様に、JScrollPaneに配置してウィンドウ(JFrame)に追加するのが一般的です。

JTreeを直接JFrameに追加することも可能ですが、ノード数が表示領域を超えた場合にスクロールバーが表示されるように、JScrollPaneでラップすることを強く推奨します。

以下に完全なサンプルコードを示します。


import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;

public class StaticTreeExample extends JFrame {

    public StaticTreeExample() {
        // ウィンドウの設定
        setTitle("静的JTreeのサンプル");
        setSize(300, 400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        // 1. ノードの階層構造を構築
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("ルート");

        DefaultMutableTreeNode category1 = new DefaultMutableTreeNode("書籍");
        category1.add(new DefaultMutableTreeNode("Java入門"));
        category1.add(new DefaultMutableTreeNode("デザインパターン"));

        DefaultMutableTreeNode category2 = new DefaultMutableTreeNode("文房具");
        DefaultMutableTreeNode pens = new DefaultMutableTreeNode("ペン");
        pens.add(new DefaultMutableTreeNode("ボールペン"));
        pens.add(new DefaultMutableTreeNode("万年筆"));
        category2.add(pens);
        category2.add(new DefaultMutableTreeNode("ノート"));


        rootNode.add(category1);
        rootNode.add(category2);

        // 2. ルートノードを元にJTreeを生成
        JTree tree = new JTree(rootNode);

        // 3. JTreeをJScrollPaneに配置
        JScrollPane scrollPane = new JScrollPane(tree);

        // 4. JScrollPaneをフレームに追加
        add(scrollPane, BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        // イベントディスパッチスレッドでGUIを生成
        SwingUtilities.invokeLater(() -> {
            StaticTreeExample ex = new StaticTreeExample();
            ex.setVisible(true);
        });
    }
}
      

このコードを実行すると、定義した階層構造を持つツリーがウィンドウに表示されます。


第3章: 動的なツリー – TreeModelの活用

アプリケーションの実行中にツリーの内容を変化させたい場合(例:ユーザー操作に応じてノードを追加・削除する)、TreeModelを正しく扱う必要があります。モデルに変更があったことをビュー(JTree)に通知し、再描画を促すのがモデルの重要な役割です。

3.1. DefaultTreeModel

静的なツリーの例では意識しませんでしたが、JTree(TreeNode)コンストラクタを使用すると、内部的にDefaultTreeModelが自動的に作成されます。動的な操作を行うには、このモデルを明示的に取得または作成し、そのメソッドを呼び出します。

DefaultTreeModelには、ツリー構造を安全に変更するためのメソッドが用意されています。

  • insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, int index): 指定した親ノードに新しい子ノードを挿入します。
  • removeNodeFromParent(MutableTreeNode node): 親ノードから指定したノードを削除します。
  • nodeChanged(TreeNode node): ノードの表現(表示される文字列など)が変更されたことを通知します。
  • reload(): ツリー全体を再読み込みし、再描画します。

重要な注意点: ノードの追加や削除をDefaultMutableTreeNodeadd()remove()メソッドで行っただけでは、JTreeの表示は自動的に更新されません。必ずDefaultTreeModelのメソッドを使って変更を行い、ビューに通知する必要があります。

3.2. ノードの動的な追加と削除

ここでは、ボタン操作で選択中のノードに新しい子ノードを追加したり、選択中のノードを削除したりするサンプルを作成します。


import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;

public class DynamicTreeExample extends JFrame {

    private JTree tree;
    private DefaultTreeModel treeModel;

    public DynamicTreeExample() {
        setTitle("動的JTreeのサンプル");
        setSize(400, 500);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        // ルートノードとモデルの作成
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("ルート");
        treeModel = new DefaultTreeModel(rootNode);

        // モデルを元にJTreeを生成
        tree = new JTree(treeModel);
        JScrollPane scrollPane = new JScrollPane(tree);

        // ボタンパネルの作成
        JPanel buttonPanel = new JPanel();
        JButton addButton = new JButton("ノード追加");
        JButton removeButton = new JButton("ノード削除");
        buttonPanel.add(addButton);
        buttonPanel.add(removeButton);

        // ボタンのアクションリスナーを設定
        addButton.addActionListener(e -> addNode());
        removeButton.addActionListener(e -> removeNode());

        // コンポーネントの配置
        add(scrollPane, BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    private void addNode() {
        // 現在選択されているノードのパスを取得
        TreePath parentPath = tree.getSelectionPath();
        DefaultMutableTreeNode parentNode;

        if (parentPath == null) {
            // 何も選択されていない場合はルートを親とする
            parentNode = (DefaultMutableTreeNode) treeModel.getRoot();
        } else {
            // 選択されているノードを親とする
            parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
        }

        // 新しいノードを作成
        String newNodeName = JOptionPane.showInputDialog(this, "新しいノード名を入力してください:");
        if (newNodeName != null && !newNodeName.trim().isEmpty()) {
            DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(newNodeName);
            // モデル経由でノードを挿入
            treeModel.insertNodeInto(newNode, parentNode, parentNode.getChildCount());

            // 追加したノードが見えるようにツリーを展開
            tree.scrollPathToVisible(new TreePath(newNode.getPath()));
        }
    }

    private void removeNode() {
        TreePath currentSelection = tree.getSelectionPath();
        if (currentSelection != null) {
            DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) currentSelection.getLastPathComponent();
            // ルートノードは削除させない
            if (currentNode.getParent() != null) {
                // モデル経由でノードを削除
                treeModel.removeNodeFromParent(currentNode);
            } else {
                JOptionPane.showMessageDialog(this, "ルートノードは削除できません。", "エラー", JOptionPane.ERROR_MESSAGE);
            }
        } else {
            JOptionPane.showMessageDialog(this, "削除するノードを選択してください。", "情報", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            DynamicTreeExample ex = new DynamicTreeExample();
            ex.setVisible(true);
        });
    }
}

      
このサンプルでは、`tree.getSelectionPath()`で現在選択されているノードの情報を取得し、`DefaultTreeModel`の`insertNodeInto`と`removeNodeFromParent`メソッドを呼び出してツリーを操作しています。これにより、変更が即座に画面に反映されます。

第4章: イベント処理 – TreeSelectionListener

ユーザーがツリーのノードを選択した際に特定の処理を行いたい場合、TreeSelectionListenerを利用します。これは、ツリーの選択状態が変更されたことを検知するためのリスナーです。

TreeSelectionListenerインタフェースにはvalueChanged(TreeSelectionEvent e)というメソッドが1つだけ定義されています。このメソッドが、選択状態の変更時に呼び出されます。

4.1. 選択されたノードの情報を取得する

TreeSelectionEventオブジェクトから、選択されたノードに関する様々な情報を取得できます。

  • e.getPath(): 選択されたノードへのパス(TreePathオブジェクト)を返します。
  • e.getNewLeadSelectionPath(): 新しく選択されたノードのパスを返します。

取得したTreePathオブジェクトから、getLastPathComponent()メソッドを呼び出すことで、選択されたノード(通常はDefaultMutableTreeNode)そのものを取得できます。

4.2. 実装例: 選択ノードの詳細を表示する

以下の例では、ツリーのノードを選択すると、ウィンドウ下部のラベルにそのノードの名前と階層レベルを表示します。


import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;

public class TreeSelectionListenerExample extends JFrame {

    private JTree tree;
    private JLabel statusLabel;

    public TreeSelectionListenerExample() {
        setTitle("TreeSelectionListenerのサンプル");
        setSize(350, 450);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        // 静的ツリーを作成
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("FileSystem");
        DefaultMutableTreeNode diskC = new DefaultMutableTreeNode("ドライブ (C:)");
        diskC.add(new DefaultMutableTreeNode("Program Files"));
        diskC.add(new DefaultMutableTreeNode("Users"));
        diskC.add(new DefaultMutableTreeNode("Windows"));
        DefaultMutableTreeNode diskD = new DefaultMutableTreeNode("ドライブ (D:)");
        diskD.add(new DefaultMutableTreeNode("Data"));
        diskD.add(new DefaultMutableTreeNode("Backup"));
        root.add(diskC);
        root.add(diskD);

        tree = new JTree(root);
        JScrollPane scrollPane = new JScrollPane(tree);

        // ステータス表示用のラベル
        statusLabel = new JLabel("ノードを選択してください");
        statusLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

        // JTreeにTreeSelectionListenerを追加
        tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
            @Override
            public void valueChanged(TreeSelectionEvent e) {
                // 選択されたノードのパスを取得
                TreePath selectedPath = e.getNewLeadSelectionPath();

                if (selectedPath != null) {
                    // パスからノードオブジェクトを取得
                    DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectedPath.getLastPathComponent();
                    
                    // ノードの情報(ユーザーオブジェクト)と階層レベルを取得
                    String nodeName = selectedNode.getUserObject().toString();
                    int level = selectedNode.getLevel();
                    
                    // ラベルに情報を設定
                    statusLabel.setText("選択中: " + nodeName + " (階層レベル: " + level + ")");
                } else {
                    statusLabel.setText("選択が解除されました");
                }
            }
        });

        add(scrollPane, BorderLayout.CENTER);
        add(statusLabel, BorderLayout.SOUTH);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            TreeSelectionListenerExample ex = new TreeSelectionListenerExample();
            ex.setVisible(true);
        });
    }
}
      

このコードでは、ラムダ式を使ってTreeSelectionListenerを実装しています。ノードがクリックされるたびに`valueChanged`メソッドが呼び出され、選択されたノードの名前と階層の深さを計算してラベルを更新します。


第5章: 見た目のカスタマイズ – TreeCellRenderer

JTreeのデフォルトの見た目(フォルダのアイコンやテキスト)を、よりリッチで分かりやすい表示に変更したい場合、TreeCellRendererの出番です。

TreeCellRendererは、ツリーの各ノードを描画する方法を定義するインタフェースです。通常は、このインタフェースを実装したDefaultTreeCellRendererクラスを継承し、その挙動をカスタマイズするのが最も簡単な方法です。

5.1. getTreeCellRendererComponentメソッド

カスタマイズの鍵となるのが、getTreeCellRendererComponent()メソッドのオーバーライドです。このメソッドは、各ノードが描画される直前に呼び出され、そのノードを表示するためのコンポーネント(通常はJLabel)を返します。

このメソッド内で、引数として渡される情報(ノードが葉か、展開されているか、選択されているかなど)に基づいて、アイコン、テキスト、文字色、背景色などを動的に変更できます。

5.2. 実装例: ノードの種類に応じてアイコンと色を変更する

この例では、ファイルシステムを模したツリーで、ドライブ、フォルダ、ファイルといったノードの種類に応じて、表示するアイコンと文字色を変更するカスタムレンダラーを作成します。

まず、ノードの種類を保持するためのカスタムノードクラスを定義します。


// ノードの種類を定義するenum
enum NodeType { DRIVE, FOLDER, FILE }

// ノードの種類を保持できるカスタムノードクラス
class FileSystemNode extends DefaultMutableTreeNode {
    private NodeType type;

    public FileSystemNode(Object userObject, NodeType type) {
        super(userObject);
        this.type = type;
    }

    public NodeType getType() {
        return type;
    }
}
      

次に、このカスタムノードを解釈して表示を変更するカスタムレンダラーを作成します。


import javax.swing.*;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;

class CustomTreeCellRenderer extends DefaultTreeCellRenderer {

    // 事前にアイコンを読み込んでおく
    private Icon driveIcon = UIManager.getIcon("FileView.hardDriveIcon");
    private Icon folderIcon = UIManager.getIcon("FileView.directoryIcon");
    private Icon fileIcon = UIManager.getIcon("FileView.fileIcon");

    @Override
    public Component getTreeCellRendererComponent(JTree tree, Object value,
                                                 boolean sel, boolean expanded,
                                                 boolean leaf, int row, boolean hasFocus) {

        // 親クラスのメソッドを呼び出し、基本的な設定(選択時の背景色など)を行わせる
        super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);

        // valueをカスタムノードにキャスト
        if (value instanceof FileSystemNode) {
            FileSystemNode node = (FileSystemNode) value;
            
            // ノードの種類に応じてアイコンと文字色を設定
            switch (node.getType()) {
                case DRIVE:
                    setIcon(driveIcon);
                    setForeground(new Color(0, 0, 128)); // 濃い青
                    break;
                case FOLDER:
                    setIcon(folderIcon);
                    setForeground(new Color(139, 69, 19)); // 茶色
                    break;
                case FILE:
                    setIcon(fileIcon);
                    setForeground(Color.BLACK);
                    break;
            }
        }
        
        return this;
    }
}
      

最後に、これらのクラスを使ってJTreeを組み立てます。


public class RendererExample extends JFrame {
    public RendererExample() {
        setTitle("TreeCellRendererのサンプル");
        setSize(400, 400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        
        // カスタムノードを使って階層を構築
        FileSystemNode root = new FileSystemNode("マイコンピュータ", NodeType.FOLDER);
        FileSystemNode driveC = new FileSystemNode("ドライブ (C:)", NodeType.DRIVE);
        FileSystemNode folderDocs = new FileSystemNode("ドキュメント", NodeType.FOLDER);
        folderDocs.add(new FileSystemNode("report.docx", NodeType.FILE));
        folderDocs.add(new FileSystemNode("data.xlsx", NodeType.FILE));
        driveC.add(folderDocs);
        root.add(driveC);

        JTree tree = new JTree(root);
        
        // 作成したカスタムレンダラーをJTreeに設定
        tree.setCellRenderer(new CustomTreeCellRenderer());
        
        // 行の高さをアイコンに合わせて調整
        tree.setRowHeight(20);

        add(new JScrollPane(tree));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new RendererExample().setVisible(true));
    }
}
      

ポイント:

  • `getTreeCellRendererComponent`の最初で`super`を呼び出すことで、選択状態のハイライトなど、デフォルトの便利な挙動を継承できます。
  • `UIManager.getIcon(…)` を使うと、実行環境のLook & Feelに合わせた標準的なアイコンを手軽に利用できます。
  • ノードのデータ構造とレンダラーをうまく連携させることで、非常に表現力豊かなツリーUIを実現できます。

まとめ

この記事では、Java Swingの`javax.swing.tree`パッケージ、特に`JTree`コンポーネントの包括的な使い方を解説しました。

基本的な静的ツリーの作成から始まり、MVCアーキテクチャの重要性、`TreeModel`を介した動的なノード操作、`TreeSelectionListener`によるイベント処理、そして`TreeCellRenderer`を用いた高度な見た目のカスタマイズまで、段階的に学習を進めてきました。

`JTree`は一見すると複雑に感じるかもしれませんが、その中心にあるモデル・ビュー・コントローラの役割分担を理解すれば、その柔軟性と強力さを最大限に引き出すことができます。データ構造(モデル)と表示(ビュー)を分離して考える習慣をつけることが、`JTree`をマスターするための鍵となります。

ここで紹介した知識を土台として、ノードの編集機能、ドラッグ&ドロップ、遅延読み込み(Lazy Loading)といった、さらに発展的なトピックにもぜひ挑戦してみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です