JTree Dinâmica - método insertNodeInto lança ArrayIndexOutOfBoundsException [ RESOLVIDO ]

14 respostas
C

Fala, galera ! Já faz dias que estou enferentando um problema com a construção de um JTree dinâmica, tendo como base um TreeModel que trabalha com Lists. Não consegui achar um exemplo satisfatório em lugar algum ( ou o TreeModel era personalizado mas não permitia inserir novos nós ou a JTree era dinâmica mas não tinha um TreeModel personalizado baseado em Lists).

O programa constroi uma JTree dinamicamente de acordo com a Person que foi passada ( que por sua vez possui uma lista de filhos ( Vector children ), um pai ( Person father) e uma mãe( Person mother ) ). O problema é na hora de adicionar um novo nó a seguinte exceção é lançada:

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 3 > 0
	at java.util.Vector.insertElementAt(Unknown Source)
	at javax.swing.tree.DefaultMutableTreeNode.insert(Unknown Source)
	at javax.swing.tree.DefaultTreeModel.insertNodeInto(Unknown Source)
	at DynamicTree.addObject(DynamicTree.java:177)
	at DynamicTree.addObject(DynamicTree.java:156)
	at DynamicTreeDemo.actionPerformed(DynamicTreeDemo.java:95)
	at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
	at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
	at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
	at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
	at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
	at java.awt.Component.processMouseEvent(Unknown Source)
	at javax.swing.JComponent.processMouseEvent(Unknown Source)
	at java.awt.Component.processEvent(Unknown Source)
	at java.awt.Container.processEvent(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.run(Unknown Source)

O método causador da exceção é o insertNodeInto, mas não consegui identificar a causa de forma alguma.....Pesquisei em diversos sites e nada também, algumas pessoas até desconfiam que seja um bug do Swing....

Segue o programa abaixo ( percebam que é uma mescla entre os exemplos dos tutoriais da Sun - DynamicTreeDemo e GenealogyExample )

Classe Person
import java.util.Vector;

import javax.swing.tree.DefaultMutableTreeNode;

public class Person extends DefaultMutableTreeNode{
    Person father;
    Person mother;
    Vector<Person> children;
    private String name;

    public Person(String name) {
        this.name = name;
        mother = father = null;
        children = new Vector<Person>();
    }

    /**
    *   Link together all members of a family.
    *
    *   @param pa the father
    *   @param ma the mother
    *   @param kids the children
    */
    public static void linkFamily(Person pa,
                                  Person ma,
                                  Person[] kids) {
        for (Person kid : kids) {
            pa.children.addElement(kid);
            ma.children.addElement(kid);
            kid.father = pa;
            kid.mother = ma;
        }
    }

/// getter methods ///////////////////////////////////

    public String toString() { return name; }
    public String getName() { return name; }
    public Person getFather() { return father; }
    public Person getMother() { return mother; }
    public int getChildCount() { return children.size(); }
    public Person getChildAt(int i) {
    	return (Person)children.elementAt(i);
    }
    public int getIndexOfChild(Person kid) {
        return children.indexOf(kid);
    }
}
TreeModel personalizado
import java.util.Vector;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;

public class GenealogyModel extends DefaultTreeModel {
    private boolean showAncestors;
    private Vector<TreeModelListener> treeModelListeners =
        new Vector<TreeModelListener>();
    private Person rootPerson;

    public GenealogyModel(Person root) {
        super( root );
    	showAncestors = false;
        rootPerson = root;
    }

    /**
     * Used to toggle between show ancestors/show descendant and
     * to change the root of the tree.
     */
    public void showAncestor(boolean b, Object newRoot) {
        showAncestors = b;
        Person oldRoot = rootPerson;
        if (newRoot != null) {
           rootPerson = (Person)newRoot;
        }
        fireTreeStructureChanged(oldRoot);
    }


//////////////// Fire events //////////////////////////////////////////////

    /**
     * The only event raised by this model is TreeStructureChanged with the
     * root as path, i.e. the whole tree has changed.
     */
    protected void fireTreeStructureChanged(Person oldRoot) {
        int len = treeModelListeners.size();
        TreeModelEvent e = new TreeModelEvent(this, 
                                              new Object[] {oldRoot});
        for (TreeModelListener tml : treeModelListeners) {
            tml.treeStructureChanged(e);
        }
    }


//////////////// TreeModel interface implementation ///////////////////////

    /**
     * Adds a listener for the TreeModelEvent posted after the tree changes.
     */
    public void addTreeModelListener(TreeModelListener l) {
        treeModelListeners.addElement(l);
    }

    /**
     * Returns the child of parent at index index in the parent's child array.
     */
    public Object getChild(Object parent, int index) {
        Person p = (Person)parent;
        if (showAncestors) {
            if ((index > 0) && (p.getFather() != null)) {
                return p.getMother();
            }
            return p.getFather();
        }
        return p.getChildAt(index);
    }

    /**
     * Returns the number of children of parent.
     */
    public int getChildCount(Object parent) {
        Person p = (Person)parent;
        if (showAncestors) {
            int count = 0;
            if (p.getFather() != null) { 
                count++;
            }
            if (p.getMother() != null) { 
                count++;
            }
            return count;
        }
        return p.getChildCount();
    }

    /**
     * Returns the index of child in parent.
     */
    public int getIndexOfChild(Object parent, Object child) {
        Person p = (Person)parent;
        if (showAncestors) {
            int count = 0;
            Person father = p.getFather();
            if (father != null) {
                count++;
                if (father == child) {
                    return 0;
                }
            }
            if (p.getMother() != child) {
                return count;
            }
            return -1;
        }
        return p.getIndexOfChild((Person)child);
    }

    /**
     * Returns the root of the tree.
     */
    public Object getRoot() {
        return rootPerson;
    }

    /**
     * Returns true if node is a leaf.
     */
    public boolean isLeaf(Object node) {
        Person p = (Person)node;
        if (showAncestors) {
            return ((p.getFather() == null)
                 && (p.getMother() == null));
        }
        return p.getChildCount() == 0;
    }

    /**
     * Removes a listener previously added with addTreeModelListener().
     */
    public void removeTreeModelListener(TreeModelListener l) {
        treeModelListeners.removeElement(l);
    }

    /**
     * Messaged when the user has altered the value for the item
     * identified by path to newValue.  Not used by this model.
     */
    public void valueForPathChanged(TreePath path, Object newValue) {
        System.out.println("*** valueForPathChanged : "
                           + path + " --> " + newValue);
    }
}
Constrói a GUI
import java.awt.GridLayout;
import java.awt.Toolkit;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;

public class DynamicTree extends JPanel {
    protected DefaultMutableTreeNode rootNode;
    protected DefaultTreeModel treeModel;
    protected JTree tree;
    private Toolkit toolkit = Toolkit.getDefaultToolkit();

    public DynamicTree() {
        super(new GridLayout(1,0));
        
        rootNode = getRoot();
        treeModel = new GenealogyModel( (Person)rootNode );
        treeModel.addTreeModelListener(new MyTreeModelListener());
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setShowsRootHandles(true);

        JScrollPane scrollPane = new JScrollPane(tree);
        add(scrollPane);
    }
    
    public DefaultMutableTreeNode getRoot() {
        //the greatgrandparent generation
        Person a1 = new Person("Jack (great-granddaddy)");
        Person a2 = new Person("Jean (great-granny)");
        Person a3 = new Person("Albert (great-granddaddy)");
        Person a4 = new Person("Rae (great-granny)");
        Person a5 = new Person("Paul (great-granddaddy)");
        Person a6 = new Person("Josie (great-granny)");

        //the grandparent generation
        Person b1 = new Person("Peter (grandpa)");
        Person b2 = new Person("Zoe (grandma)");
        Person b3 = new Person("Simon (grandpa)");
        Person b4 = new Person("James (grandpa)");
        Person b5 = new Person("Bertha (grandma)");
        Person b6 = new Person("Veronica (grandma)");
        Person b7 = new Person("Anne (grandma)");
        Person b8 = new Person("Renee (grandma)");
        Person b9 = new Person("Joseph (grandpa)");

        //the parent generation
        Person c1 = new Person("Isabelle (mom)");
        Person c2 = new Person("Frank (dad)");
        Person c3 = new Person("Louis (dad)");
        Person c4 = new Person("Laurence (dad)");
        Person c5 = new Person("Valerie (mom)");
        Person c6 = new Person("Marie (mom)");
        Person c7 = new Person("Helen (mom)");
        Person c8 = new Person("Mark (dad)");
        Person c9 = new Person("Oliver (dad)");

        //the youngest generation
        Person d1 = new Person("Clement (boy)");
        Person d2 = new Person("Colin (boy)");
        
        
        Person.linkFamily(a1,a2,new Person[] {b1,b2,b3,b4});
        Person.linkFamily(a3,a4,new Person[] {b5,b6,b7});
        Person.linkFamily(a5,a6,new Person[] {b8,b9});
        Person.linkFamily(b3,b6,new Person[] {c1,c2,c3});
        Person.linkFamily(b4,b5,new Person[] {c4,c5,c6});
        Person.linkFamily(b8,b7,new Person[] {c7,c8,c9});
        Person.linkFamily(c4,c7,new Person[] {d1,d2});
        
        return a1;
    }

    /** Remove all nodes except the root node. */
    public void clear() {
        rootNode.removeAllChildren();
        treeModel.reload();
    }

    /** Remove the currently selected node. */
    public void removeCurrentNode() {
        TreePath currentSelection = tree.getSelectionPath();
        if (currentSelection != null) {
            DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode)
                         (currentSelection.getLastPathComponent());
            MutableTreeNode parent = (MutableTreeNode)(currentNode.getParent());
            if (parent != null) {
                treeModel.removeNodeFromParent(currentNode);
                return;
            }
        } 

        // Either there was no selection, or the root was selected.
        toolkit.beep();
    }

    /** Add child to the currently selected node. */
    public DefaultMutableTreeNode addObject(Object child) {
        DefaultMutableTreeNode parentNode = null;
        TreePath parentPath = tree.getSelectionPath();

        if (parentPath == null) {
            parentNode = rootNode;
        } else {
            parentNode = (DefaultMutableTreeNode)
                         (parentPath.getLastPathComponent());
        }

        return addObject(parentNode, child, true);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
                                            Object child) {
        return addObject(parent, child, false);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
                                            Object child, 
                                            boolean shouldBeVisible) {
        DefaultMutableTreeNode childNode = 
                new DefaultMutableTreeNode(child);

        if (parent == null) {
            parent = rootNode;
        }
        
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
        System.out.println( root );
	//It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode
        treeModel.insertNodeInto(childNode, parent, parent.getChildCount());


        //Make sure the user can see the lovely new node.
        if (shouldBeVisible) {
            tree.scrollPathToVisible(new TreePath(childNode.getPath()));
        }
        return childNode;
    }

    class MyTreeModelListener implements TreeModelListener {
        public void treeNodesChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node;
            node = (DefaultMutableTreeNode)(e.getTreePath().getLastPathComponent());

            /*
             * If the event lists children, then the changed
             * node is the child of the node we've already
             * gotten.  Otherwise, the changed node and the
             * specified node are the same.
             */

                int index = e.getChildIndices()[0];
                node = (DefaultMutableTreeNode)(node.getChildAt(index));

            System.out.println("The user has finished editing the node.");
            System.out.println("New value: " + node.getUserObject());
        }
        public void treeNodesInserted(TreeModelEvent e) {
        }
        public void treeNodesRemoved(TreeModelEvent e) {
        }
        public void treeStructureChanged(TreeModelEvent e) {
        }
    }
}
Main
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.tree.DefaultMutableTreeNode;

public class DynamicTreeDemo extends JPanel 
                             implements ActionListener {
    private int newNodeSuffix = 1;
    private static String ADD_COMMAND = "add";
    private static String REMOVE_COMMAND = "remove";
    private static String CLEAR_COMMAND = "clear";
    
    private DynamicTree treePanel;

    public DynamicTreeDemo() {
        super(new BorderLayout());
        
        //Create the components.
        treePanel = new DynamicTree();

        JButton addButton = new JButton("Add");
        addButton.setActionCommand(ADD_COMMAND);
        addButton.addActionListener(this);
        
        JButton removeButton = new JButton("Remove");
        removeButton.setActionCommand(REMOVE_COMMAND);
        removeButton.addActionListener(this);
        
        JButton clearButton = new JButton("Clear");
        clearButton.setActionCommand(CLEAR_COMMAND);
        clearButton.addActionListener(this);

        //Lay everything out.
        treePanel.setPreferredSize(new Dimension(300, 150));
        add(treePanel, BorderLayout.CENTER);

        JPanel panel = new JPanel(new GridLayout(0,3));
        panel.add(addButton);
        panel.add(removeButton); 
        panel.add(clearButton);
	add(panel, BorderLayout.SOUTH);
    }

   
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        
        if (ADD_COMMAND.equals(command)) {
            //Add button clicked
            treePanel.addObject("New Node " + newNodeSuffix++);
        } else if (REMOVE_COMMAND.equals(command)) {
            //Remove button clicked
            treePanel.removeCurrentNode();
        } else if (CLEAR_COMMAND.equals(command)) {
            //Clear button clicked.
            treePanel.clear();
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("DynamicTreeDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        DynamicTreeDemo newContentPane = new DynamicTreeDemo();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

É possível fazer isso, pessoal ? Pesquisando encontrei uma classe chamada AbstractTreeModel, ela resolveria este problema ?

14 Respostas

V

Sim. na verdade, é muito pouco prático usar o DefaultMutableTreeNode. A lógica fica toda na interface gráfica, e acaba havendo duplicação da estrutura da árvore. O melhor mesmo é criar o seu próprio TreeModel.

Dê uma olhada nesse exemplo:
http://www.guj.com.br/posts/list/37821.java#501820

C

Opa, obrigado ViniGodoy ! Algumas dúvidas: percebi que nessa interface o método para a inserção do nó ( void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) exige como parâmetros o caminho até o nó raiz…Esse caminho deve ser obtido através de métodos da própria árvore ou do nó ? Devo fazer com que Livro e Autor implementem TreeNode ? Percebi também que este método notifica Listeners interessados no evento treeNodesChanged…isso é o suficiente para ocasionar as mudanças no Tree ou devo implementar um listener que ative algum outro método pra fazer essa inserção ?

Mais uma vez agradeço a atenção !

V

O model deve fornecer essa informação. Se eu não me engano, existe um método lá para obter o caminho baseado no último componente do path.
Mas muitas vezes você a tem.

Não. Esqueça o TreeNode. A idéia do model é justamente trabalhar diretamente com suas classes de negócio, mas sem modifica-las.

É suficiente. O JTree escuta esse evento. Escuta também os eventos de insert e delete.

C

Pois é, ViniGodoy......tô tentando aqui mas realmente não estou conseguindo......estou tomando como base os exemplos do tópico que você citou ( lista de Livros, que possui uma lista de Autores ) e fiz as seguintes modificações ( sim eu sei, está horrível, mas é apenas para teste :wink:...):

Método main da classe Tela
public static void main(String[] args) {   
        List<Livro> livros = new ArrayList<Livro>();   
  
        Livro livro = new Livro("Duna");   
        livro.addAutor(new Autor("Frank Herbert"));   
        livros.add(livro);   
  
        Livro livro2 = new Livro("50 Anos Depois");   
        livro2.addAutor(new Autor("Chico Xavier"));   
        livro2.addAutor(new Autor("Emmanuel (Espírito)"));   
        livros.add(livro2);   
  
        Livro livro3 = new Livro("O rapto do garoto de ouro");   
        livro3.addAutor(new Autor("Marcos Rey"));   
        livros.add(livro3);
        
        LivroTreeModel model = new LivroTreeModel(livros);
        
        Tela tela = new Tela( model );   
        tela.setVisible(true);
        
        
        //Adicionando um novo nó
        Livro livro4 = new Livro("A Montanha Mágica");   
        livro4.addAutor(new Autor("Thomas Mann"));   
        livros.add(livro4);
        
        model.fireLastPathComponentInserted(livros);
        
        model.fireTreeStructureChanged();
        
    }
Sobrescrevendo o método fireLastPathComponentInserted na classe LivroTreeModel
public void fireLastPathComponentInserted(TreePath treePath)
    {
    	        
    	Object parent = getRoot();//O pai é a lista de livros ( root )
    	
    	System.out.println( "parent: " + parent );
        
    	Object leaf = livros.get(3); // Quero inserir um novo livro - mas a leaf não é um Autor ? 
                                                       //Só é possivel inserir novos Autores nos Livros da árvore ?
        
        System.out.println( "leaf: " + leaf );

        int index = getIndexOfChild(parent, leaf);
        
        System.out.println( "indexOfChild: " +  index );
        
        //Retirei treePath.getParentPath().getPath(), pois retorna null !
        fireTreeNodesInserted(this, treePath.getPath()/*treePath.getParentPath().getPath()*/,
                new int[] {index}, new Object[] {leaf});
    }

Sabe o que pode estar dando errado ?

V

Você entende o que é um “TreePath”? Ou o que ele quer dizer quando fala em “Path”?

C

Bom…até onde eu saiba o TreePath é um array de objetos que representa um caminho até a raiz. Percebi agora que a árvore esta estruturada da seguinte forma:

List de Livros = Raiz
[Duna, 50 Anos Depois, O rapto do garoto de ouro, A Montanha Mágica ]

Nó 1
Duna

Nó 2
50 Anos Depois

Nó 3
O rapto do garoto de ouro

Nó 4 - Não aparece
A Montanha Mágica

Ou seja, o path desta árvore é o seguinte array:
[livros, livro, livro2, livro3, livro4]

Logo, tentei passar este array para o método:

…mas não funcionou… Realmente não sei o que pode estar errado…

V

Sim, mas o caminho são todos os nós que você percorre até chegar na folha.

Por exemplo, na arvore

Editora 1
–Livro 1
----Autor A
----Autor B
Editora 2
–Livro 1
----Autor C
–Livro 2
----Autor D
----Autor E

O TreePath para o Autor D é:
Editora 2, Livro 2, Autor D

Portanto, seu TreePath não será a lista de livros. O TreePath refere-se ao nó. Quais são os nós que devo passar, para chegar nesse nó? Isso é o TreePath.

C

Hum…mas então…tomando como base a lista de livros do exemplo:

[Duna, 50 Anos Depois, O rapto do garoto de ouro, A Montanha Mágica ] (livros)----Raiz

–Duna ( livro )
----Frank Herbert ( autor )

–50 Anos Depois
----Chico Xavier ( livro2 )
----Emmanuel (Espírito) ( autor2 )

–O rapto do garoto de ouro ( livro3 )
----Marcos Rey ( autor3 )

–A Montanha Mágica ( livro4 ) ---------> Nó a ser inserido !
----Thomas Mann ( autor4 )

Quero inserir um novo nó ( livro ) na raiz. Um dos nós ( raiz ) É a lista de livros, correto ? Ou seja, pela definição o caminho a ser seguido é:

livros, livro4

É isso ? Tentei aqui mas novamente não funcionou… Desculpe a insistência ViniGodoy , mas realmente quero compreender a manipulação de JTrees dinâmicas…

V

É isso mesmo.

Você deve então inserir o livro na lista (raiz) e depois disparar o evento.

C

Ok....fiz isso e msm assim não atualizou :?....segue a alteração no código:

public static void main(String[] args) {     
        List<Livro> livros = new ArrayList<Livro>();     
      
        Livro livro = new Livro("Duna");     
        livro.addAutor(new Autor("Frank Herbert"));     
        livros.add(livro);     
      
        Livro livro2 = new Livro("50 Anos Depois");     
        livro2.addAutor(new Autor("Chico Xavier"));     
        livro2.addAutor(new Autor("Emmanuel (Espírito)"));     
        livros.add(livro2);     
      
        Livro livro3 = new Livro("O rapto do garoto de ouro");     
        livro3.addAutor(new Autor("Marcos Rey"));     
        livros.add(livro3);   
           
        LivroTreeModel model = new LivroTreeModel(livros);   
           
        Tela tela = new Tela( model );
        
        tela.setVisible(true);   
           
           
        //Adicionando um novo nó   
        Livro livro4 = new Livro("A Montanha Mágica");     
        livro4.addAutor(new Autor("Thomas Mann"));     
        
        livros.add(livro4);//atualiza a List de livros
        
        model.fireLastPathComponentInserted(livros, livro4 ); //chama o método de AbstractTreeModel, 
                                                                                    //não sobrescrevi em  LivroTreeModel

        model.fireTreeStructureChanged();

    }
V

Ok, passei um tempão aqui me batendo com esse erro, na hora de montar um exemplo para você. O mais "weird" é que sempre funcionou, então comecei a desconfiar que o erro não fosse tão óbvio assim.

Mas então, descobri o que é. É que você não pode usar um ArrayList como nó. Isso deve ser algum detalhe do funcionamento interno do JTree (provavelmente tem a ver com o fato do hashCode do ArrayList mudar assim que um elemento é inserido).

Você pode contornar isso facilmente inserindo um string como raiz "falsa". Eis o código:
public class LivroTreeModel extends AbstractTreeModel {
	// Raiz da nossa árvore, vamos exibir uma lista de livros.
	private List&lt;Livro&gt; livros;
        private String fakeRoot = "Livros";

	public LivroTreeModel(List&lt;Livro&gt; livros) {
		this.livros = livros;
	}

	/**
	 * Com esse método, o Java quem é o objeto que está num determinado índice
	 * do pai. Cada nó de uma árvore pode ser encarado como uma lista. Sendo o
	 * pai a lista e o índice um dos filhos.
	 *
	 * @param parent
	 *            É o pai, que tem os filhos. No caso do Livro, o próprio livro.
	 * @param index
	 *            Índice do filho. No caso do livro, o índice corresponde aos
	 *            autores.
	 */
	public Object getChild(Object parent, int index) {
		if (parent == fakeRoot) // É o nó principal?
			return livros.get(index); // Pegamos da lista de livro

		if (parent instanceof Livro) // O pai é um livro?
		{
			// Devolvemos um autor
			return ((Livro) parent).getAutores().get(index);
		}

		// Se o pai não é nenhum desses. Melhor dar erro.
		throw new IllegalArgumentException("Invalid parent class"
				+ parent.getClass().getSimpleName());
	}

	/**
	 * Retornamos quantos filhos um pai tem. No caso de um livro, é a contagem
	 * de autores. No caso da lista de livros, é a quantidade de livros.
	 */
	public int getChildCount(Object parent) {
		// Mesma lógica.
		if (parent == fakeRoot)
			return livros.size();

		if (parent instanceof Livro) // O pai é um livro?
			return ((Livro) parent).getAutores().size();

		// Se o pai não é nenhum desses. Melhor dar erro.
		throw new IllegalArgumentException("Invalid parent class"
				+ parent.getClass().getSimpleName());
	}

	/**
	 * Dado um pai, indicamos qual é o índice do filho correspondente.
	 */
	public int getIndexOfChild(Object parent, Object child) {
		if (parent == fakeRoot)
			return livros.indexOf(child);
		if (parent instanceof Livro)
			return ((Livro) parent).getAutores().indexOf(child);

		return 0;
	}

	/**
	 * Devemos retornar quem é o nó raiz da árvore. Afinal, a árvore tem que
	 * começar em algum lugar.
	 */
	public Object getRoot() {
		return fakeRoot;
	}

	/**
	 * Indicamos se um nó é ou não uma folha. Isso é, se ele não tem filhos. No
	 * nosso caso, os autores são as folhas da árvore.
	 */
	public boolean isLeaf(Object node) {
		return node instanceof Autor;
	}

        public void adicionarLivro(Livro livro)
        {
            livros.add(livro);
            fireLastPathComponentInserted(fakeRoot, livro);
        }

        public void adicionarAutor(Livro livro, Autor autor)
        {
            livro.addAutor(autor);
            fireLastPathComponentInserted(fakeRoot, livro, autor);
        }

        public void removerLivro(Livro livro)
        {
            if (livros.remove(livro))
            {
                fireLastPathComponentRemoved(fakeRoot, livro);
            }
        }
}

Eu não tinha notado isso antes, pq na minha aplicação eu tenho uma classe que encapsula uma lista, simplesmente para adicionar uma descrição a lista, e essa descrição ser exibida na tree. Mas essa classe não muda o hashCode dela quando novos elementos são inseridos.

A classe é simples assim:

public class Lista&lt;T&gt; {
   private string name;
   public List&lt;T&gt; list;
   public Lista(String nome, Collection&lt;? extends T&gt; collection) {
        list = new ArrayList&lt;T&gt;(collection);
   }

   public String toString() { return name; }

   public List&lt;T&gt; getList() { return list; }
}

Em anexo, um exemplo com isso funcionando.

C

Putz, é isso mesmo :smiley: !!! Mais uma vez, muito obrigado pela atenção, ViniGodoy !!!

V

Que nada, quando eu vi que não funcionava, virou pessoal. :evil:
Pq a vida toda fiz assim, e esse model tava queimando meu filme no GUJ, heheeheh.

M

ViniGodoy:
Mas então, descobri o que é. É que você não pode usar um ArrayList como nó. Isso deve ser algum detalhe do funcionamento interno do JTree (provavelmente tem a ver com o fato do hashCode do ArrayList mudar assim que um elemento é inserido).

Interessante saber… e só complementando: um Map funciona perfeitamente como raiz do JTree.

Criado 7 de março de 2010
Ultima resposta 11 de mar. de 2010
Respostas 14
Participantes 3