JTree (Tutorial)©: Unterschied zwischen den Versionen
K (→Der JTree) |
K |
||
Zeile 1: | Zeile 1: | ||
+ | {{Copyright}} | ||
+ | |||
© August 2005 Dieses Tutorial unterliegt dem Copyright, Kopien (auch nur von Teilen) sind nur für nicht-komerzielle Zwecke gestattet; Detailfragen (insbesondere bei Unsicherheiten), bitte direkt an die Autoren. | © August 2005 Dieses Tutorial unterliegt dem Copyright, Kopien (auch nur von Teilen) sind nur für nicht-komerzielle Zwecke gestattet; Detailfragen (insbesondere bei Unsicherheiten), bitte direkt an die Autoren. | ||
Version vom 27. April 2014, 19:36 Uhr
Die Datei ist urheberrechtlich geschützt. Diese Datei darf ohne ausdrückliche Zustimmung des Autors oder des Byte-Welt-Teams nicht für kommerzielle Zwecke verwendet werden!
© August 2005 Dieses Tutorial unterliegt dem Copyright, Kopien (auch nur von Teilen) sind nur für nicht-komerzielle Zwecke gestattet; Detailfragen (insbesondere bei Unsicherheiten), bitte direkt an die Autoren. InhaltsverzeichnisEinführungDer JTreeDer JTree ist eine Component aus dem Package javax.swing. Der JTree wird verwendet um Bäume (wie z.B. die Verzeichnisse auf einer Festplatte) darzustellen, und auch dem Benutzer die Möglichkeit zu geben, diese Bäume zu verändern. Und sollte jemand diese Componente noch nicht kennen, hier ein Bild: Kurzer ÜberblickDer JTree arbeitet streng mach dem MVC-Pattern:
TreeCellRenderer und TreeCellEditor sind Erweiterungen der View, und erlauben die Daten interessanter darzustellen, oder zu verändern. Verschiedene Listener sorgen für die Kommunikation zwischen den Objekten, so dass z,B. die View stets die aktuellen Daten anzeigt. Nicht vergessen darf man die Klasse TreePath, welche einen einzelnen Knoten des Baumes eindeutig repräsentiert. Dazu jedoch mehr im letzten Teil dieses Tutorials. RessourcenIm Internet findet man viele Beispiele und Informationen zu dem JTree, hier ein paar Beispiele:
Wie bringt man Daten in den JTree?TreeNodeDie wohl intuitivste Variante Daten in einen JTree zu bringen, sind TreeNodes. Jeder dieser Nodes stellt einen Knoten des Baumes dar, und hat eine beliebige Anzahl Kindknoten. TreeNode selbst ist nur ein Interface. Entweder schreibt man selbst eine Klasse welche das Interface implementiert, oder man benutzt den sehr bequemen DefaultMutableTreeNode. Die MutableTreeNodes sind nur Wrapper um ein Userobject herum. Das Userobject bestimmt den Wert des Knotens, die Knoten selbst bestimmen die Struktur des Baumes. Der Baum wird dann schliesslich so aufgebaut: <code=java> package jtree; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; public class Demo1 { public static void main( String[] args ) { // Der Wurzelknoten wird hergestellt TreeNode root = createTree(); // Der Wurzelknoten wird dem neuen JTree im Konstruktor übergeben JTree tree = new JTree( root ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } private static TreeNode createTree(){ /* * Der Baum wird folgende Form haben: * Wurzel * +- Buchstaben * | +- A * | +- B * | +- C * +- Zahlen * +- 1 * +- 2 * +- 3 */ // Zuerst werden alle Knoten hergestellt... DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Wurzel" ); DefaultMutableTreeNode letters = new DefaultMutableTreeNode( "Buchstaben" ); DefaultMutableTreeNode digits = new DefaultMutableTreeNode( "Zahlen" ); DefaultMutableTreeNode letterA = new DefaultMutableTreeNode( "A" ); DefaultMutableTreeNode letterB = new DefaultMutableTreeNode( "B" ); DefaultMutableTreeNode letterC = new DefaultMutableTreeNode( "C" ); DefaultMutableTreeNode digit1 = new DefaultMutableTreeNode( "1" ); DefaultMutableTreeNode digit2 = new DefaultMutableTreeNode( "2" ); DefaultMutableTreeNode digit3 = new DefaultMutableTreeNode( "3" ); // ... dann werden sie verknüpft letters.add( letterA ); letters.add( letterB ); letters.add( letterC ); digits.add( digit1 ); digits.add( digit2 ); digits.add( digit3 ); root.add( letters ); root.add( digits ); return root; } } </code=java> TreeModelMit TreeNodes kann man einen Baum sehr einfach aufbauen. Intern arbeitet ein JTree aber stets mit einem TreeModel. Das TreeModel enthält alle Informationen über die Struktur des Baumes. Dafür weiß das TreeModel nicht, wie die Knoten tatsächlich dargestellt werden. Das TreeModel kann für jeden Knoten sagen, wieviele Kinder er besitzt, und wer diese Kinder sind. Die Methode getChildCount() gibt für einen Knoten an, wieviele Kinder er besitzt. Alle Kinder eines Knotens sind durchnummeriert (jeder Knoten ist sozusagen eine Liste von Kindknoten), dementsprechend gibt die Methode getChild() das Kind mit der Nummer "index" zurück. Achtung: getChild() muss für verschiedene Indizes (bei gleichbleibendem Vaterknoten) verschiedene Kinderknoten zurückgeben. Jeder Baum muss eine Wurzel haben, diese Wurzel wird durch getRoot() angegeben. Normalerweise besitzt ein Baum Knoten und Blätter. Als Blätter bezeichnet man Knoten die keine Kinder haben. Z.b. in einem Dateisystem entsprechen Verzeichnisse Knoten, und Dateien entsprechen Blätter. Der JTree behandelt Blätter und Knoten leicht unterschiedlich: Knoten kann man aufklappen, Blätter nicht. Ob ein Objekt Knoten oder Blatt ist, bestimmt die Methode isLeaf(). Eine reine Hilfsfunktion ist schließlich getIndexOfChild(), die "Umkehrmethode" von getChild(). Sie gibt an, unter welcher Nummer ein Kindknoten bei einem Vaterknoten gespeichert ist. Der folgende Code zeigt einen Baum, der als Knoten einfach Strings verwendet. <code=java> package jtree; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public class Demo2 { public static void main( String[] args ) { // Die Verknüpfungen werden hergestellt TreeModel model = createTree(); // Das Model wird dem Konstruktor des JTrees übergeben JTree tree = new JTree( model ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.pack(); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } private static TreeModel createTree(){ /* * Der Baum wird folgende Form haben: * Wurzel * +- Buchstaben * | +- A * | +- B * | +- C * +- Zahlen * +- 1 * +- 2 * +- 3 */ TreeModel model = new TreeModel(){ // Diese Methode gibt die Wurzel des Baumes an. public Object getRoot() { return "Wurzel"; } // Hier wird das index'te Kind des Knotens "parent" bestummen public Object getChild( Object parent, int index ) { if( parent.equals( "Wurzel" )){ switch( index ){ case 0: return "Buchstaben"; case 1: return "Zahlen"; } } if( parent.equals( "Buchstaben" )){ switch( index ){ case 0: return "A"; case 1: return "B"; case 2: return "C"; } } if( parent.equals( "Zahlen" )){ switch( index ){ case 0: return "1"; case 1: return "2"; case 2: return "3"; } } return null; } // Gibt für jeden Knoten an, wieviele Kinder er hat public int getChildCount( Object parent ) { if( parent.equals( "Wurzel" )) return 2; if( parent.equals( "Zahlen" )) return 3; if( parent.equals( "Buchstaben" )) return 3; // Dann wars A, B, C, 1, 2 oder 3 return 0; } // Gibt an, ob ein Knoten ein Blatt ist. Ein Blatt kann in einem // JTree nicht weiter geöffnet werden. // Experiment: immer true oder immer false zurückgeben... public boolean isLeaf( Object node ) { return getChildCount( node ) == 0; } // Gibt an, welchen Index der Knoten "child" als Kind vom // Knoten "parent" hat. Wenn man folgenden Code ausführt: // 'Object result = getChild( parent, getIndexOfChild( parent, child ))', // dann muss "result" und "child" dasselbe Objekt sein. public int getIndexOfChild( Object parent, Object child ){ int max = getChildCount( parent ); for( int i = 0; i < max; i++ ) if( getChild( parent, i ).equals( child )) return i; return -1; } public void addTreeModelListener( TreeModelListener listener ) { // siehe später } public void removeTreeModelListener( TreeModelListener listener ) { // siehe später } public void valueForPathChanged( TreePath path, Object value ) { // siehe später } }; return model; } } </code=java> Bäume mit unendlich vielen KnotenNicht immer ist es möglich, denn gesamten Baum zu kennen. Liest man z.B. einen Baum aus einer Datenbank, könnte man schlicht nicht genügend RAM besitzen, um den gesamten Baum zu speichern. Oder das Erstellen eines Knotens dauert solange (und man hat so viele), dass die Berechnung des Baumes Tage dauern würde. Ein besonders drastisches Beispiel ist ein Baum mit unendlich vielen Knoten. Selbstverständlich wird es niemals möglich sein, diesen Baum ganzheitlich herzustellen. Trotzdem kann man mit dem JTree auch solche Giganten darstellen. Der Trick dabei ist, dass der JTree das TreeModel erst nach einem Knoten fragt, wenn der Knoten tatsächlich gezeichnet werden soll. Im untenstehenden Codeschnipsel besitzt jeder Knoten 10 Unterknoten, die genau dann hergestellt werden, wenn sie das erste Mal abgefragt werden: <code=java> package jtree; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public class Demo3 { public static void main( String[] args ) { // Das Model mit dem unendlichen Baum wird erstellt JTree tree = new JTree( new InfiniteModel() ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } } // Dieses TreeModel stellt einen Baum mit unendlich vielen Knoten dar class InfiniteModel implements TreeModel{ private Node root = new Node( "" ){ @Override public String toString() { return "Wurzel"; } }; // Der Wurzelknoten public Object getRoot() { return root; } // Die Kinder sind in dem parent-Knoten gespeichert public Object getChild( Object parent, int index ) { Node node = (Node)parent; return node.getChild( index ); } // Jeder Knoten in diesem Baum hat exakt 10 Kinder public int getChildCount( Object node ) { return 10; } // in diesem Baum haben alle Knoten Kinder public boolean isLeaf( Object node ) { return false; } // Den Indes des Kindes bestimmen public int getIndexOfChild( Object parent, Object child ) { Node node = (Node)parent; for( int i = 0; i < 10; i++ ) if( node.getChild( i ) == child ) return i; return -1; } public void valueForPathChanged( TreePath path, Object node ) { // nicht beachten } public void addTreeModelListener( TreeModelListener listener ) { // nicht beachten } public void removeTreeModelListener( TreeModelListener listener ) { // nicht beachten } // Der unendliche Baum wird durch diese Node-Objekte dargestellt. Bei // Bedarf kann sich so ein Nodeobjekt selbstständig neue Kinder erschaffen. private class Node{ private String name; private Node[] children; public Node( String name ){ this.name = name; } // Gibt das index'te Kind dieses Nodes zurück public Node getChild( int index ){ ensureChildren(); return children[ index ]; } // Sorgt dafür, dass dieser Knoten wirklich Kinder hat private void ensureChildren(){ if( children == null ){ children = new Node[ 10 ]; for( int i = 0; i < 10; i++ ) children[i] = new Node( name + i ); } } // Der JTree ruft die "toString"-Methode auf, um den Namen eines // Knoten-Objektes zu erfahren. ßberschreibt man die Methode, kann // man andere Namen anzeigen lassen @Override public String toString() { return name; } } } </code=java> Knoten mehrfach verwendenBetrachtet man das TreeModel genauer, fällt auf, dass man nur mit dem Model alleine nicht herausfinden kann, wer denn der Vater von irgendeinem Knoten ist. Mit anderen Worten: das TreeModel erlaubt es, Knoten an verschiedenen Stellen mehrfach zu verwenden. Eine konkrete Anwendung könnte eine Liste sein, welche auf verschiedene Weisen sortiert ausgegeben werden kann. In diesem Fall gibt es Knoten "Sortiert nach X", "Sortiert nach Y", ... und diese Knoten besitzen jeweils dieselben Kinder, jediglich in unterschiedlicher Reihenfolge. Eine solche Datenstruktur nennt man Graph. Will man jeden Knoten nur einmal zeichnen, ist ein Graph ist (im Allgemeinen) nicht als Baum darstellbar. Darf man hingegen die Knoten mehr als einmal zeichnen, hat man keine Probleme. Der JTree ist in der Lage, denselben Knoten an verschiedenen Stellen zu zeichnen, kann daher auch Graphen darstellen (auch wenn diese Darstellung nicht unbedingt übersichtlich ist...) Im Gegensatz zu den anderen Beispielen dieses Kapitels, ist es nicht möglich einen Graph mit TreeNodes darzustellen. Das folgende Codestück zeigt einen solchen Graphen. Für jeden Knoten gibt es eine ganze Reihe von Folgeknoten, man kann stundenlang Knoten im JTree öffnen, und trotzdem wird man nie mehr als 13 unterschiedliche Knoten zu sehen bekommen. <code=java> package jtree; import java.util.HashMap; import java.util.Map; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public class Demo4 { public static void main( String[] args ) { // Das Model füllen GraphModel model = new GraphModel( "Tagesanbruch" ); model.put( "Tagesanbruch", "kochen", "schlafen" ); model.put( "kochen", "versalzen", "angebrannt" , "gekocht" ); model.put( "versalzen", "kochen" ); model.put( "angebrannt", "abwaschen", "trotzdem essen" ); model.put( "trotzdem essen", "vergiftet" ); model.put( "vergiftet" ); model.put( "gekocht", "essen" ); model.put( "essen", "abwaschen", "stehen lassen" ); model.put( "stehen lassen", "stehen lassen", "abwaschen" ); model.put( "abwaschen", "kochen", "schlafen" ); model.put( "schlafen", "schlafen", "schnarchen", "aufwachen" ); model.put( "schnarchen", "aufwachen" ); model.put( "aufwachen", "schlafen", "kochen" ); // Der Wurzelknoten wird dem neuen JTree im Konstruktor übergeben JTree tree = new JTree( model ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } } class GraphModel implements TreeModel{ // Die Verbindungen, vom Schlüsselknoten (key) kommt man zu allen Kindknoten (value) private Map<String, String[]> connections = new HashMap<String, String[]>(); // Die Wurzel private String root; public GraphModel( String root ){ this.root = root; } public Object getRoot() { return root; } // Für den Knoten "parent" alle möglichen Kinder angeben. public void put( String parent, String...children ){ connections.put( parent, children ); } // Das index'te Kind zurückgeben public Object getChild( Object parent, int index ) { return connections.get( parent )[index]; } // Die Anzahl Kinder bestimmen public int getChildCount( Object parent ) { return connections.get( parent ).length; } // Angabe, ob ein Knoten ein Blatt ist public boolean isLeaf( Object node ) { return getChildCount( node ) == 0; } // Den Index eines Knotens bestimmen public int getIndexOfChild( Object parent, Object child ) { String[] children = connections.get( parent ); for( int i = 0; i < children.length; i++ ) if( children[i].equals( child )) return i; return -1; }
public void valueForPathChanged( TreePath path, Object node ) { // nicht beachten } public void addTreeModelListener( TreeModelListener listener ) { // nicht beachten } public void removeTreeModelListener( TreeModelListener listener ) { // nicht beachten } } </code=java>
Darstellung der DatenDer JTree stellt jeden Knoten einzeln dar. Man hat dabei verschiedene Möglichkeiten die Darstellung drastisch zu verändern: StandardeinstellungNormalerweise ruft der JTree einfach für jedes Knoten-Objekt die toString()-Methode auf. Man kann also diese Methode überschreiben, einen anderen String zurückgeben, und schon steht ein anderer Text beim Knoten. Der DefaulTreeCellRendererSchon ein bisschen raffinierter als die toString()-Methode zu überschreiben, ist es, den TreeCellRenderer des JTrees auszutauschen. Man kann dies mit Hilfe der Methode setCellRenderer() erreichen. Am einfachsten ist es wohl, von DefaultTreeCellRenderer zu erben, und die Methode getTreeCellRendererComponent zu überschreiben. Diese Methode wird vom JTree immer dann aufgerufen, wenn ein Knoten dargestellt werden soll (eine genauere Erklärung folgt in Abschnitt 3. TreeCellRenderer). import java.awt.Color; import java.awt.Component; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeNode; public class Demo5 { public static void main( String[] args ) { // Den Baum mit den Farben erstellen JTree tree = new JTree( createTree() ); // Den CellRenderer setzen, der die Knoten zeichnet tree.setCellRenderer( new ColorRenderer() ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } private static TreeNode createTree(){ // TreeNodes mit String und Color-Objekten herstellen DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Farben" ); DefaultMutableTreeNode red = new DefaultMutableTreeNode( "Rot" ); DefaultMutableTreeNode green = new DefaultMutableTreeNode( "Grün" ); DefaultMutableTreeNode blue = new DefaultMutableTreeNode( "Blau" ); root.add( red ); root.add( green ); root.add( blue ); for( float f = 0; f <= 1f; f += 0.05f ) red.add( new DefaultMutableTreeNode( new Color( f, 0, 0 ))); for( float f = 0; f <= 1f; f += 0.05f ) green.add( new DefaultMutableTreeNode( new Color( 0, f, 0 ))); for( float f = 0; f <= 1f; f += 0.05f ) blue.add( new DefaultMutableTreeNode( new Color( 0, 0, f ))); return root; } } class ColorRenderer extends DefaultTreeCellRenderer{ public ColorRenderer(){ // Versichern, dass der Hintergrund gezeichnet wird setOpaque( true ); } @Override public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ){ // Die Originalmethode die Standardeinstellungen machen lassen super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus ); // Den Wert des Knotens abfragen DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; Object inside = node.getUserObject(); // Wenn der Knoten eine Farbe ist: den Hintergrund entsprechend setzen // Andernfalls soll der Hintergrund zum Baum passen. if( inside instanceof Color ){ setBackground( (Color)inside ); setForeground( Color.WHITE ); } else{ setBackground( tree.getBackground() ); setForeground( tree.getForeground() ); } return this; } } </code=java> Der TreeCellRendererDer DefaultTreeCellRenderer ist zwar nett zu benutzen, aber die volle Kapazität hat man damit noch nicht erreicht. Weitaus mehr Gestaltungsmöglichkeit besitzt man, wenn man das Interface TreeCellRenderer direkt implementiert. public void paint( Graphics g ){ // Der aktuelle TreeCellRenderer TreeCellRenderer renderer = ... // Alle sichtbaren Knoten werden aufgerufen for( Object node : visibleNodes ){ // Der Renderer wird benutzt um eine java.awt.Component bereitzustellen, // welche dann den Knoten zeichnen wird. Component component = renderer.getTreeCellRendererComponent( this, node, ... ); // Die Component wird an die richtige Position gesetzt, und ihre Grösse kann // verändert werden. component.setBounds( ... ); // Die Component zeichnet nun den Knoten component.paint( g ); } } } </code=java> Da ergeben sich nun mehrere Beobachtungen, die man beim Implementieren beachten sollte:
JTree tree Das ist die JTree-Instanz, für die der Renderer etwas zeichnen soll (der Renderer kann von mehreren JTrees gleichzeitig benutzt werden). Hier ist der Code eines CellRenderers der zwei Componenten kombiniert. Für die Daten wurde das schon bekannte GraphModel verwendet. <code=java> package jtree; import java.awt.Color; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.tree.TreeCellRenderer; public class Demo6 { public static void main( String[] args ) throws Exception { // LookAndFeel ändern, ergibt auf einigen Systemen einen // beeindruckenderen Effekt UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); // Als Model ein GraphModel, da hat man schnell genügend Knoten im Baum GraphModel model = new GraphModel( "Abend" ); model.put( "Abend", "Musikanlage" , "Fernseher" ); model.put( "Musikanlage", "lauter", "leiser", "abschalten" ); model.put( "lauter", "lauter", "leiser", "abschalten" ); model.put( "leiser", "lauter", "leiser", "abschalten" ); model.put( "Fernseher", "zappen", "Werbung", "abschalten" ); model.put( "zappen", "zappen", "Werbung", "abschalten" ); model.put( "Werbung", "zappen", "Wutanfall" ); model.put( "Wutanfall", "Amoklauf" ); model.put( "Amoklauf" ); model.put( "abschalten", "Musikanlage", "Fernseher" ); // Den Baum erstellen JTree tree = new JTree( model ); // Den CellRenderer setzen, der die Knoten zeichnet tree.setCellRenderer( new Renderer() ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } } // Der neue CellRenderer class Renderer implements TreeCellRenderer{ private JPanel panel; private JButton icon; private JLabel label; public Renderer(){ // Man hat völlige Freiheit, wie die Component des Renderers // zusammengebaut wird panel = new JPanel( new GridBagLayout() ); icon = new JButton(); label = new JLabel(); label.setOpaque( true ); panel.add( icon, new GridBagConstraints( 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.VERTICAL, new Insets( 0, 0, 0, 0 ), 0, 0 )); panel.add( label, new GridBagConstraints( 1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 )); } public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus ){ // Die Einstellungen kann man nach belieben verändern. if( leaf ){ icon.setBackground( Color.YELLOW ); icon.setText( "o" ); } else if( expanded ){ icon.setBackground( Color.RED ); icon.setText( "+" ); } else{ icon.setBackground( Color.GREEN ); icon.setText( "-" ); } if( hasFocus ) label.setBackground( Color.ORANGE ); else if( selected ) label.setBackground( Color.LIGHT_GRAY ); else label.setBackground( tree.getBackground() ); label.setText( value.toString() ); return panel; } } </code=java> Das TreeModel für den Baum: <code=java> class GraphModel implements TreeModel{ // Die Verbindungen, vom Schlüsselknoten (key) kommt man zu allen Kindknoten (value) private Map<String, String[]> connections = new HashMap<String, String[]>(); // Die Wurzel private String root; public GraphModel( String root ){ this.root = root; } public Object getRoot() { return root; } // Für den Knoten "parent" alle möglichen Kinder angeben. public void put( String parent, String...children ){ connections.put( parent, children ); } // Das index'te Kind zurückgeben public Object getChild( Object parent, int index ) { return connections.get( parent )[index]; } // Die Anzahl Kinder bestimmen public int getChildCount( Object parent ) { return connections.get( parent ).length; } // Angabe, ob ein Knoten ein Blatt ist public boolean isLeaf( Object node ) { return getChildCount( node ) == 0; } // Den Index eines Knotens bestimmen public int getIndexOfChild( Object parent, Object child ) { String[] children = connections.get( parent ); for( int i = 0; i < children.length; i++ ) if( children[i].equals( child )) return i; return -1; }
public void valueForPathChanged( TreePath path, Object node ) { // nicht beachten } public void addTreeModelListener( TreeModelListener listener ) { // nicht beachten } public void removeTreeModelListener( TreeModelListener listener ) { // nicht beachten } } </code=java>
Veränderliche DatenRichtig interessant werden Bäume erst, wenn sie auch veränderlich sind. In diesem Kapitel geht es um Bäume, welche von programminternen Prozessen, und nicht vom Benutzer, verändert werden. Verändern kann bedeuten: einen Knoten hinzufügen, einen Knoten entfernen, oder die "Daten" eines Knotens (und damit seine Darstellung) verändern. Das DefaultTreeModelDas DefaultTreeModel ist ein TreeModel, welches mit TreeNodes befüllt wird. Wenn man nun einen TreeNode verändert hat, muss man das Model von diesen Veränderungen unterrichten. Hat man z.B. den Text eines Nodes verändert, ruft man nodeChanged() auf. Der Rest erledigt sich automatisch. Um einen Knoten einzufügen, ruft man entweder direkt insertNodeInto() auf (und das Model wird den Kindknoten dem Vaterknoten einsetzen), oder man ruft nodesWereInserted() auf, nachdem der Knoten eingefügt wurde. Es gibt noch einen ganzen Bündel weiterer solcher Methoden, aber ihre Beschreibungen können in der API nachgelesen werden. Eigene TreeModelsWenn man ein eigenes TreeModel verwendet, hat man ein bisschen mehr Arbeit. Sobald ein TreeModel irgendwo benutzt wird, werden ihm TreeModelListeners hinzugefügt (über die Methode addTreeModelListener()). Hinter diesen Listenern können sich JTrees verbergen, selbstgeschriebene Programmteile, oder irgendwelche TreeUI's des aktuellen LookAndFeels... Jedenfalls: wenn man einen veränderlichen Baum hat, müssen diese Listener gespeichert werden. Sobald sich der Baum irgendwie verändert hat, müssen alle Listener mit einem korrekt aufgebauten TreeModelEvent aufgerufen werden. Grundsätzlich gibt es 4 unterschiedliche Kategorien von Ereignissen: Knoten wurden entfernt, Knoten wurden hinzugefügt, Knoten wurden verändert oder die Änderungen sind zu komplex um von den 3 anderen Kategorien erfasst zu werden. Der Listener bietet folgende 4 Methoden, passend zu diesen Kategorien:
Wenn einer oder mehrere Knoten ihre Daten, nicht aber den Platz im Baum verändert haben. "Ihre Daten verändert" bedeutet, dass sich ihre Darstellung verändert hat, und sie neu gezeichnet werden sollen. Das TreeModelEvent enthält den Pfad zu dem Vaterknoten der veränderten Knoten, ausserdem einen Array mit den Indizes, sowie ein weiterer Array mit den veränderten Knoten. Sollte sich die Wurzel verändert haben, sind diese beiden Arrays auf null zu setzen.
Diese Methode sollte aufgerufen werden, wenn Knoten in den Baum eingefügt wurden. Das Event besitzt den Pfad zu dem Vaterknoten der neuen Knoten. Ausserdem einen Array der neuen Knoten selbst, sowie der Indizes der neuen Knoten. Diese beiden Arrays müssen aufsteigend sortiert sein.
Soll benutzt werden, wenn ein oder mehrere Knoten entfernt wurden. Das Event enthält den Pfad zum Vaterknoten, sowie einen Array mit den entfernten Knoten. Zusätzlich muss das Event einen Array mit den ehemaligen Indizes der entfernten Knoten beinhalten. Diese beiden Arrays müssen aufsteigend sortiert sein. Wird ein ganzer Unterbaum entfernt, muss die Methode nur für den Wurzelknoten dieses Unterbaumes aufgerufen werden.
Wenn die Veränderungen derart komplex sind, dass die anderen Methoden nicht mehr passen, wird diese Variante benutzt. Das Event enthält lediglich einen Pfad zu dem Knoten, der zuoberst in der Hierarchie der veränderten Knoten steht. Wenn man die Wurzel austauschen möchte, übergibt man einen Pfad der Länge 1, mit der neuen Wurzel. Diese Methode ist mit Vorsicht zu genießen, ein JTree wird einfach alles zuklappen (die Einstellungen gehen verloren). Wenn möglich, sollte man also die anderen 3 Methoden verwenden. Der untenstehende Beispielcode zeigt ein TreeModel welches seinen Baum intern durch TreeNodes darstellt. Es gibt zwei Aktionen "Add" und "Remove". "Add" fügt allen selektierten Knoten ein Kind ein (durch die add()-Methode des ChangeableTreeModels), "Remove" entfernt alle selektierten Knoten. Das Model wird von zwei JTrees gleichzeitig benutzt. Das zeigt, dass das Model keine Ahnung haben muss, was um es herum geschieht. Seine Listener reichen völlig um mit der Umwelt zu kommunizieren. <code=java> import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTree; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; public class Demo7 { public static void main( String[] args ) { // Zwei Bäume und ein Model herstellen ChangeableModel model = new ChangeableModel(); // Ein Frame herstellen, um die Trees auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); JSplitPane split = new JSplitPane(); split.setResizeWeight( 0.5 ); split.setLeftComponent( createTreePanel( model )); split.setRightComponent( createTreePanel( model )); frame.add( split ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 700, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } // Generiert ein Panel mit einem JTree und zwei Buttons "add" und // "remove". Die Buttons rufen die "AddAcion", bzw. die "RemoveAction" auf. private static JPanel createTreePanel( ChangeableModel model ){ JTree tree = new JTree( model ); JButton buttonAdd = new JButton( "Add" ); buttonAdd.addActionListener( new AddAction( tree, model )); JButton buttonRemove = new JButton( "Remove" ); buttonRemove.addActionListener( new RemoveAction( tree, model )); JPanel buttonPanel = new JPanel( new GridLayout( 1, 2 )); buttonPanel.add( buttonAdd ); buttonPanel.add( buttonRemove ); JPanel content = new JPanel( new GridBagLayout() ); content.add( new JScrollPane( tree ), new GridBagConstraints( 0, 0, 1, 1, 1.0, 1000.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 1, 1, 1, 1 ), 0, 0 )); content.add( buttonPanel, new GridBagConstraints( 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LAST_LINE_END, GridBagConstraints.NONE, new Insets( 1, 1, 1, 1 ), 0, 0 )); return content; } } // Dieses Model kann registrierte TreeModelListener unterrichten, sollte ein // Knoten hinzugefügt oder entfernt werden. // Wichtig sind die beiden Methode "add" und "remove" class ChangeableModel implements TreeModel{ private DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Wurzel" ); private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>(); public Object getRoot() { return root; } public Object getChild( Object parent, int index ) { return ((TreeNode)parent).getChildAt( index ); } public int getChildCount( Object parent ) { return ((TreeNode)parent).getChildCount(); } public boolean isLeaf( Object node ) { return getChildCount( node ) == 0; } public int getIndexOfChild( Object parent, Object child ) { return ((TreeNode)parent).getIndex( (TreeNode)child ); } public void addTreeModelListener( TreeModelListener listener ) { listeners.add( listener ); } public void removeTreeModelListener( TreeModelListener listener ) { listeners.remove( listener ); } // Fügt dem parent-Knoten (durch den TreePath gegeben) den // Child-Knoten hinzu public void add( TreePath parent, MutableTreeNode child ){ // Den Knoten einbauen int index = getChildCount( parent.getLastPathComponent() ); ((MutableTreeNode)parent.getLastPathComponent()).insert( child, index ); // Die Listener unterrichten TreeModelEvent event = new TreeModelEvent( this, // Quelle des Events parent, // Pfad zum Vater des veränderten Knoten new int[]{ index }, // Index des veränderten Knotens new Object[]{ child } ); // Der neue Knoten for( TreeModelListener listener : listeners ) listener.treeNodesInserted( event ); } // Entfernt den Knoten, der durch den TreePath angegeben ist. public void remove( TreePath path ){ // Den Knoten entfernen Object parent = path.getParentPath().getLastPathComponent(); Object child = path.getLastPathComponent(); int index = getIndexOfChild( parent, child ); ((MutableTreeNode)parent).remove( index ); // Die Listener unterrichten TreeModelEvent event = new TreeModelEvent( this, // Quelle des Events path.getParentPath(), // Pfad zum Vater des entfernten Knotens new int[]{index}, // Ehemaliger Index des Knotens new Object[]{child} ); // Der entfernte Knoten for( TreeModelListener listener : listeners ) listener.treeNodesRemoved( event ); } public void valueForPathChanged( TreePath path, Object newValue ) { // nicht beachten } } // Ein Behälter für einen JTree und ein TreeModel. Eine TreeAction kann // an einen Button angehängt werden, und auf jedes Event reagieren. abstract class TreeAction implements ActionListener{ protected JTree tree; protected ChangeableModel model; public TreeAction( JTree tree, ChangeableModel model ){ this.tree = tree; this.model = model; } public void actionPerformed( ActionEvent e ) { invoked(); } protected abstract void invoked(); } // Diese Action fügt allen selektierten Knoten eines JTrees ein neues Kind hinzu. // Anschliessend werden die Kinder selektiert. class AddAction extends TreeAction{ private static int index = 0; public AddAction( JTree tree, ChangeableModel model ) { super(tree, model); } @Override protected void invoked() { TreePath[] paths = tree.getSelectionPaths(); if( paths != null ){ tree.clearSelection(); for( TreePath path : paths ){ MutableTreeNode node = new DefaultMutableTreeNode( "inserted: " + index++ ); model.add( path, node ); tree.expandPath( path ); tree.addSelectionPath( path.pathByAddingChild( node )); } } } } // Diese Action entfernt alle derzeit selektierten Knoten eines Baumes // und selektiert anschliessend die Vaterknoten. Die Wurzel kann nicht // entfernt werde class RemoveAction extends TreeAction{ public RemoveAction( JTree tree, ChangeableModel model ) { super(tree, model); } @Override protected void invoked() { TreePath[] paths = tree.getSelectionPaths(); if( paths != null ){ tree.clearSelection(); for( TreePath path : paths ){ if( path.getPathCount() > 1 ){ model.remove( path ); tree.addSelectionPath( path.getParentPath() ); } } } } } </code=java>
BenutzerinteraktionUm dem Benutzer die Möglichkeit der direkten Interaktion zu geben, kann man den JTree leicht erweitern. Als erstes muss man die Methode setEditable des JTrees aufrufen, und true übergeben (per Default ist false gesetzt). StandardmechanismusBenutzt man das DefaultTreeModel, und setzt den JTree editable, so kann man einen Doppelklick auf einen Knoten machen, und einen neuen String eingeben. Das DefaultTreeModel geht davon aus, dass man einen MutableTreeNode verändern will. Es wird den neu eingegebenen String direkt in den MutableTreeNode schreiben. Sollte man keine MutableTreeNodes verwenden, gibts lediglich eine ClassCastException. <code=java> import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; public class Demo8 { public static void main( String[] args ) { // Der Wurzelknoten wird hergestellt TreeNode root = createTree(); // Der Wurzelknoten wird dem neuen JTree im Konstruktor übergeben JTree tree = new JTree( root ); // Dem Tree das verändern der Knoten erlauben tree.setEditable( true ); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } private static TreeNode createTree(){ /* * Der Baum wird folgende Form haben: * Wurzel * +- Buchstaben * | +- A * | +- B * | +- C * +- Zahlen * +- 1 * +- 2 * +- 3 */ // Zuerst werden alle Knoten hergestellt... DefaultMutableTreeNode root = new DefaultMutableTreeNode( "Wurzel" ); DefaultMutableTreeNode letters = new DefaultMutableTreeNode( "Buchstaben" ); DefaultMutableTreeNode digits = new DefaultMutableTreeNode( "Zahlen" ); DefaultMutableTreeNode letterA = new DefaultMutableTreeNode( "A" ); DefaultMutableTreeNode letterB = new DefaultMutableTreeNode( "B" ); DefaultMutableTreeNode letterC = new DefaultMutableTreeNode( "C" ); DefaultMutableTreeNode digit1 = new DefaultMutableTreeNode( "1" ); DefaultMutableTreeNode digit2 = new DefaultMutableTreeNode( "2" ); DefaultMutableTreeNode digit3 = new DefaultMutableTreeNode( "3" ); // ... dann werden sie verknüpft letters.add( letterA ); letters.add( letterB ); letters.add( letterC ); digits.add( digit1 ); digits.add( digit2 ); digits.add( digit3 ); root.add( letters ); root.add( digits ); return root; } } </code=java>
TreeCellEditorNicht immer möchte man Strings in dem Baum speichern. Oder manchmal möchte man einige zusätzliche Bedingungen für Strings definieren (z.B. dass die Maximallänge 5 Zeichen ist). Ein TreeCellEditor ist immer auch ein CellEditor, dementsprechend müssen auch die Methoden des CellEditors implementiert werden.
Die wichtigste Methode des CellEditors. Die Methode wird direkt vom JTree tree aufgerufen, wenn der Benutzer ein Element des Baumes editieren will. Das editierte Element wird als value übergeben, der CellEditor generiert daraufhin eine Component, welche diesen Wert irgendwie darstellt. Die anderen 4 Parameter der Methode, isSelected, expanded, leaf und row haben eine geringere Bedeutung, sie könnten z.B. benutzt werden, um die Darstellung leicht zu verändern...
Der Editor kann selbst entscheiden, wann die Eingabe fertig ist (z.B. könnte ENTER bedeuten, dass der Benutzer fertig editiert hat, während ESC für "Abbrechen" steht...).
Entfernt früher hinzugefügte Listener.
Wird diese Methode aufgerufen, soll der Editor sofort mit dem Editieren aufhören, und die Veränderungen nicht akzeptieren. Bei einem Aufruf dieser Methode müssen die CellEditorListener informiert werden.
Wird diese Methode aufgerufen, soll der Editor sofort mit dem Editieren aufhören. Der veränderte Wert wird in den Baum zurückgeschrieben.
Gibt das veränderte Objekt zurück, welches der Editor derzeit darstellt. Ist die Editor-Component ein JTextField könnte der Rückgabewert einfach der String des Textfeldes sein.
Diese Methode wird aufgerufen um zu entscheiden, ob die Aktionen eines Benutzers ausreichen, um den Editor erscheinen zu lassen. Ein Editor könnte zum Beispiel darauf bestehen, dass der Benutzer einen Doppelklick mit der Maus machen muss, oder dass der Benutzer irgendeine Taste (mit Ausnahme der Taste "g") drücken muss.
Wenn diese Methode true zurück gibt (Standardverhalten), wird die Editor-Component selektiert. Wenn sie false zurückgibt, geschieht einfach nichts. Der folgende Code zeigt einen Editor, der eine JComboBox ausklappt. Für die Struktur des Baumes werden DefaultMutableTreeNodes verwendet, welche zusammen mit dem DefaultTreeModel die ganze Organisation von wegen "Knoten austauschen" übernehmen. Jeder Knoten wird durch ein Entry-Objekt (Code, siehe unten) dargestellt. <code=java> import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.EventObject; import javax.swing.DefaultComboBoxModel; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeNode; public class Demo9 { public static void main( String[] args ) { // Der Wurzelknoten wird hergestellt TreeNode root = createTree(); // Der Wurzelknoten wird dem neuen JTree im Konstruktor übergeben JTree tree = new JTree( root ); // Der Tree soll editierbar mit dem vorgegebenen Editor sein tree.setEditable( true ); tree.setCellEditor( new ComboBoxEditor()); // Ein Frame herstellen, um den Tree auch anzuzeigen JFrame frame = new JFrame( "JTree - Demo" ); frame.add( new JScrollPane( tree )); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.setSize( 400, 400 ); frame.setLocationRelativeTo( null ); frame.setVisible( true ); } private static TreeNode createTree(){ /* * Der Baum wird folgende Form haben: * Wurzel/Wurzelzwerg/Wurzelknolle * +- Buchstaben/Letter * | +- A/B/C * | +- A/B/C * | +- A/B/C * +- Zahlen/Digits * +- 1/2/3 * +- 1/2/3 * +- 1/2/3 */ // Zuerst werden alle Knoten hergestellt... DefaultMutableTreeNode root = new DefaultMutableTreeNode( new Entry( 0, "Wurzel", "Wurzelzwerg", "Wurzelknolle" ) ); DefaultMutableTreeNode letters = new DefaultMutableTreeNode( new Entry( 0, "Buchstaben", "Letters" )); DefaultMutableTreeNode digits = new DefaultMutableTreeNode( new Entry( 0, "Zahlen", "Digits" )); DefaultMutableTreeNode letterA = new DefaultMutableTreeNode( new Entry( 0, "A", "B", "C" ) ); DefaultMutableTreeNode letterB = new DefaultMutableTreeNode( new Entry( 1, "A", "B", "C" ) ); DefaultMutableTreeNode letterC = new DefaultMutableTreeNode( new Entry( 2, "A", "B", "C" ) ); DefaultMutableTreeNode digit1 = new DefaultMutableTreeNode( new Entry( 0, "1", "2", "3" ) ); DefaultMutableTreeNode digit2 = new DefaultMutableTreeNode( new Entry( 1, "1", "2", "3" ) ); DefaultMutableTreeNode digit3 = new DefaultMutableTreeNode( new Entry( 2, "1", "2", "3" ) ); // ... dann werden sie verknüpft letters.add( letterA ); letters.add( letterB ); letters.add( letterC ); digits.add( digit1 ); digits.add( digit2 ); digits.add( digit3 ); root.add( letters ); root.add( digits ); return root; } } // Entry ist ein Eintrag in den Baum. Jeder Eintrag besteht aus einer Liste // von möglichten Werten, die der Eintrag annehmen kann, sowie einem Indes // der aktuell ausgewählten Möglichkeit class Entry{ // Die Liste der Wahlmöglichkeiten private String[] choices; // Die aktuelle Wahl private int choice = 0; public Entry( int choice, String...choices ){ this.choice = choice; this.choices = choices; } public int getChoiceCount(){ return choices.length; } public String getChoice( int index ){ return choices[index]; } public int getChoice(){ return choice; } public void setChoice( int choice ){ this.choice = choice; } @Override public String toString() { // Da der DefaultTreeCellRenderer verwendet wird, und dieser Renderer // die "toString"-Methode benutzt, soll diese Methode die aktuelle // Wahl zurückgeben. return getChoice( getChoice() ); } } // Der Editor des JTrees. Dieser Editor wird durch einen Doppelklick mit // der Maus aktiviert. Er zeigt eine ComboBox, auf der man eine Auswahl // treffen kann, sobald man die Auswahl gemacht hat, "schliesst" sich der // Editor wieder. class ComboBoxEditor implements TreeCellEditor{ // die Liste aller registrierten Listener private Collection<CellEditorListener> listeners = new ArrayList<CellEditorListener>(); // Das Model der ComboBox (die Liste, was die ComboBox enthält...) private DefaultComboBoxModel model; // Die Box, die immer wieder angezeigt wird private JComboBox box; public ComboBoxEditor(){ model = new DefaultComboBoxModel(); box = new JComboBox( model ); // Sobald eine Auswahl gemacht wird, soll der Editor "stoppen" (und // der Wert wird in den Baum geschrieben). box.addActionListener( new ActionListener(){ public void actionPerformed(ActionEvent e) { stopCellEditing(); } }); } public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { // Sollte der Editor gerade sonstwo benutzt werden, könnte eine // Mischung der "getTreeCellEditorComponent"-Methode sehr "explosiv" sein... cancelCellEditing(); // Dem ComboBox-Model sagen, welche Auswahlen möglich sind, und was // aktuell ausgewählt ist. All diese Informationen stehen in dem // Entry-Objekt, welches vom JTree übergeben wird. DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; Entry entry = (Entry)node.getUserObject(); model.removeAllElements(); for( int i = 0, n = entry.getChoiceCount(); i<n; i++ ) model.addElement( entry.getChoice( i ) ); model.setSelectedItem( model.getElementAt( entry.getChoice() )); // Die neu konfigurierte ComboBox zurückgeben return box; } // Gibt den aktuellen Wert des Editors zurück. Dies kann dasselbe Objekt // sein, welches bei "getTreeCellEditorComponent" übergeben wurde, kann // aber auch ein total neues Objekt sein. In der vorliegenden Implementation // wird ein neues Objekt hergestellt public Object getCellEditorValue() { // Mit den Daten des ComboBox-Models ein "Entry" erzeugen. String[] choices = new String[ model.getSize() ]; for( int i = 0; i < choices.length; i++ ) choices[i] = (String)model.getElementAt( i ); int selected = model.getIndexOf( model.getSelectedItem() ); return new Entry( selected, choices ); } public boolean isCellEditable(EventObject anEvent) { // Bei einem Doppelklick soll der Benutzer editieren können. Die anderen // Events interessieren nicht, aber zur Sicherheit wird einfach mal // "true" zurückgegeben. Vielleicht will ja jemand mit der Tastatur // was machen... if( anEvent instanceof MouseEvent ){ return ((MouseEvent)anEvent).getClickCount() >= 2; } else return true; } public boolean shouldSelectCell(EventObject anEvent) { return true; } public boolean stopCellEditing() { // Unterrichtet alle Listener, dass nicht mehr editiert wird, // und dass das die neue Eingabe akzeptiert wurde. ChangeEvent event = new ChangeEvent( this ); for( CellEditorListener listener : listeners ) listener.editingStopped( event ); return true; } public void cancelCellEditing() { // Informiert alle Listener, dass nicht mehr editiert wird, // und dass die Eingabe verworfen wurde. ChangeEvent event = new ChangeEvent( this ); for( CellEditorListener listener : listeners ) listener.editingCanceled( event ); } public void addCellEditorListener(CellEditorListener listener) { listeners.add( listener ); } public void removeCellEditorListener(CellEditorListener listener) { listeners.remove( listener ); } } </code=java> Verbindung zum TreeModelWenn man ein eigenes TreeModel verwendet, muss man sich auch selbst um die veränderten Knoten kümmern. Wann immer der Benutzer einen TreeCellEditor benutzt hat, um ein Knoten abzuändern, wird die Methode valueForPathChanged() aufgerufen. Der erste Parameter ist der Pfad des veränderten Knotens, der zweite Parameter ist der Rückgabewert von CellEditor#getCellEditorValue().
Wichtige Klassen, Interfaces und MethodenDieses Kapitelchen soll einen Überblick über verschiedene Klassen und Methoden verschaffen, welche man immer wieder antrifft, wenn man mit JTree arbeitet. TreePathMit dem TreePath kommt man oft in Kontakt. Ein TreePath beschreibt die exakte Position eines Knotens, bzw. führt identifiziert einen Knoten eindeutig im Baum. Der TreePath ist dabei eigentlich nur ein Array von Knoten, wobei folgendes gilt:
Was sind aber diese "Knoten"-Objekte? Das sind die Objekte die das TreeModel in fast all seinen Methoden als Argument erwartet, oder als Rückgabewert angibt. Besondere Vorsicht muss man dabei walten lassen, wenn man die DefaultMutableTreeNodes (oder normale TreeNodes) verwendet. Denn der TreePath erwartet in diesem Fall Instanzen von DefaultMutableTreeNode, und nicht die Werte der Nodes. Weitere nützliche Dinge:
Falsch zusammengebaute TreePaths welche einem JTree übergeben werden, verursachen meist keine Exceptions. Der JTree wird einfach nicht auf den Pfad reagieren... TreeExpansionListenerDer TreeExpantionListener wird dem JTree direkt hinzugefügt. TreeWillExpandListenerDieser Listener macht eigentlich genau dasselbe wie der TreeExpansionListener: er wird informiert, wenn ein Knoten geöffnet/geschlossen wird. Im Gegensatz zum TreeExpansionListener wird der TreeWillExpandListener jedoch aufgerufen, bevor das Ereignis durchgeführt wurde. public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException{ if( <gefällt mir nicht> ) throw new ExpandVetoException( event, "Weil ich nicht will" ); } ... } </code=java> TreeSelectionListenerDer TreeSelectionListener wird immer dann aufgerufen, wenn die Selektion der Knoten verändert wurde.
Ein DirectoryChooserEine fertig implementierte Beispielanwendung stellt dieser DirectoryChooser dar. -- Beni (© Benjamin Sigg 03.12.2005, 14:25) |