JTree - Teil 4 - Veränderliche Daten

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
JTree - Teil 4 - Veränderliche Daten
Richtig 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.

1. Das DefaultTreeModel
Das 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.

2. Eigene TreeModels
Wenn 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:
  • treeNodesChanged
    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 Indices, sowie ein weiterer Array mit den veränderten Knoten. Sollte sich die Wurzel verändert haben, sind diese beiden Arrays auf null zu setzen.
  • treeNodesInserted
    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 Indices der neuen Knoten. Diese beiden Arrays müssen aufsteigend sortiert sein.
  • treeNodesRemoved
    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 Indices 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.
  • treeStructureChanged
    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 geniessen, 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:
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() );
                }
            }
        }
    }
}
 
Zuletzt bearbeitet von einem Moderator:
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben