Baumdarstellungen mit JTree

Die Klasse JTree ermöglicht es, hierarchische Bäume flexibel und vielfach modifizierbar einzusetzen. Inhalt und Erscheinung können dabei getrennt voneinander definiert und manipuliert werden.

Ein durch die Klasse JTree repräsentierter Datenbaum besteht im Wesentlichen aus drei Hauptelementen:

  1. dem Inhalt des Baumes, dem sog. Model, d.h. den Daten, die im Baum dargestellt werden
  2. der Erscheinung des Baumes, d.h. der Swing-Komponente JTree
  3. einem Renderer, der für die detaillierte Darstellung und Manipulation der Baumknoten verantwortlich ist.

Im einfachsten Fall stellt Java jedoch diese Elemente in einer Standardvariante bereit, sodass man sich gar nicht oder nur rudimentär darum kümmern muss. Betrachten wir einen solchen Fall im Beispiel unten.
Es zeigt eine von JFrame abgeleitete Klasse, deren Besonderheit lediglich in der Methode initTree() besteht, die im Konstruktor aufgerufen wird.
Der Ablauf der Routinen in ihr ist charakteristisch für die Erzeugung eines Baumes:

  1. Erzeugung eines Wurzelknotens
  2. Herstellung eines leeren Models mit der soeben erzeugten Wurzel
  3. Füllen des Models, hier am Beispiel des Hinzufügens eines Elementes
  4. Generierung des JTree mit dem erzeugten Model

Es fällt auf, dass der o.a. Renderer im Quelltext gar nicht auftaucht. Vielmehr wird der Baum in der Standard-Darstellung erzeugt.
Die beiden folgenden Methodenaufrufe sind selbst erklärend: Sie bewirken, dass der Wurzelknoten sichbar ist und dass neben mit Unterelementen versehenen Einträgen ein Zeichen - je nach Look-and-Feel oft ein Dreieck - zum 'Aufklappen' des Baumabschnitts erscheint. Das letzte Statement der Methode fügt den Baum einem JScrollPane hinzu, das wiederum in den Frame eingebettet wird. Dies ist notwendig, um den Baum bei entsprechender Länge scrollfähig zu machen.

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

public class VerySimpleTreeClass extends JFrame {

    private JTree tree;

    public VerySimpleTreeClass() {
        initTree();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setExtendedState(JFrame.MAXIMIZED_BOTH);
        this.setLocationRelativeTo(null);
        this.setTitle("Ein sehr einfacher Baum");
        this.setVisible(true);
    }

    private void initTree() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        DefaultTreeModel model = new DefaultTreeModel(root);
        root.add(new DefaultMutableTreeNode("Version 1"));
        tree = new JTree(model);
        tree.setRootVisible(true);
        tree.setShowsRootHandles(true);
        this.add(new JScrollPane(tree));
    }

    public static void main(String[] args) {
        new VerySimpleTreeClass();
    }
}

Das zweite Beispiel demonstriert einige Möglichkeiten der individuellen Konfiguration eines JTree. Hierbei sollen

Die Knoten

Die Klasse der Baumknoten wird von DefaultMutableTreeNode abgeleitet. Sie definiert lediglich zwei String-Instanzvariablen, die über den Konstruktor initialisiert werden.

Das Model

Das Model erweitert DefaultTreemodel und übergibt sein Konstruktor-Attribut vom Typ DefaultMutableTreeNode als Wurzelknoten an den Superkonstruktor. Zur Demonstration wird anschließend ein weiterer Knoten als Blatt angefügt.

Der Renderer

Er ist abgeleitet von der Klasse DefaultTreeCellRenderer, die wiederum das Interface TreeCellRenderer implementiert. Die hier deklarierte Methode getTreeCellRendererComponent() besitzt eine Reihe von Attributen, die den Baum selbst, den Inhalt eines Knoten-Objekts und diverse seiner Eigenschaften repräsentieren. Sie können abgefragt werden und auf diese Weise die Eigenschaften der Knoten selbst modifizieren.

Der 'Zusammenbau' des Baumes erfolgt wie im ersten Beispiel in der Methode initTree(). Die Unterschiede neben der Anpassung der neuen Typen für das Model und die Knoten bestehen im Hinzufügen des Renderers und der Anmeldung des Baumes bei einem TreeSelectionListener. In dessen Methode valueChanged() werden die Eigenschaften der selektierten Knoten abgefragt und auf der Konsole ausgegeben.

Ein Wechsel des Models erfolgt im einfachsten Fall, wie im Beispiel, über ein beliebiges Event (hier ein ActionEvent eines Buttons), das das Setzen des neuen Models mittels JTree.setModel() anstößt. Wichtig ist hier, dass das Neuladen des Tree-UserInterface durch updateUI() erfolgt.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.border.LineBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;

public class SimpleTreeClass extends JFrame implements TreeSelectionListener {

    private JTree tree;

    public SimpleTreeClass() {
        this.setLayout(new BorderLayout());
        initTree();
        JButton butt = new JButton("Anderes Model");
        butt.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("Wurzel")));
                tree.updateUI();
            }
        });
        this.add(butt, BorderLayout.SOUTH);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setTitle("Ein einfacher Baum");
        this.setVisible(true);
    }

    private void initTree() {
        MyLeaf root = new MyLeaf("", "Mitarbeiter");
        DefaultTreeModel model = new MyModel(root);
        root.add(new MyLeaf("Paul", "Meier"));
        tree = new JTree(model);
        tree.addTreeSelectionListener(this);
        tree.setCellRenderer(new MyRenderer());
        tree.setRootVisible(true);
        tree.setShowsRootHandles(true);
        this.add(new JScrollPane(tree), BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        new SimpleTreeClass();
    }

    public void valueChanged(TreeSelectionEvent e) {
        MyLeaf node = (MyLeaf) tree.getLastSelectedPathComponent();
        if (node != tree.getModel().getRoot() && node != null)
            System.out.println(((MyLeaf) node).getFirstName() + " "
                    + ((MyLeaf) node).getLastName());
    }
}

class MyLeaf extends DefaultMutableTreeNode {

    String firstName, lastName;

    public MyLeaf(String firstName, String lastName) {
        super(lastName);
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

class MyModel extends DefaultTreeModel {
    public MyModel(DefaultMutableTreeNode node) {
        super(node);
        DefaultMutableTreeNode wurzel = node;
        wurzel.add(new MyLeaf("Karl", "Schmitz"));
    }
}

class MyRenderer extends DefaultTreeCellRenderer {

    public MyRenderer() {
        setTextSelectionColor(new Color(120, 120, 120));
        setOpaque(true);
    }

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

        if (sel)
            this.setBorder(new LineBorder(Color.BLACK));
        else
            this.setBorder(null);

        if (leaf) {
            this.setBackground(Color.RED);
        } else {
            this.setBackground(Color.GREEN);
        }

        if (sel) {
            this.setBackground(Color.YELLOW);
        }

        return this;
    }
}