JTree: Arrastar nós

Eu preciso de uma ajuda para implementar “drag” numa estrutura JTree.

Eu criei uma JTree, com devidos nós e já consigo recuperar um nó selecionado, “collapsed” ou “expanded”. Agora preciso implementar a ação de clicar e arrastar nos nós (semelhante à estrutura do windows), mudando assim a ordem dos nós.

Eu cheguei a dar um setDragEnabled(true) na JTree criada, agora ela reconhece a ação “Drag”, mas não permite que eu arraste (mostra o icone de “proibido” do mouse). O que devo fazer a partir daí?

Legal Pessoal!!!
Alguém mais ?? :smiley:

Achei um exemplo no site mas tem alguns bugs. Uma delas é o cursor do mouse que indica que não é possivel soltar, mas mesmo assim funciona.

Este site (java2s.com) acaba de entrar para os meus favoritos, eu ainda não o conhecia. Obrigado Furutani, vou realizar os testes aqui e assim que tiver novidades retorno ao post para comentá-las.

Oi chará!

Vamos lá:

O primeiro passo é você criar uma Tree que faça o scrolling automático, caso o usuário posicione o mouse em suas extremidades. Sem isso, seu usuário vai ficar frustrado caso tente arrastar o componente para um nó que não está visivel. A classe abaixo é uma "autoscrolling tree", com esse intuito:

import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JTree;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;

public class JAutoScrollingTree extends JTree implements Autoscroll {
    private static final int MARGIN = 12;
    
    public JAutoScrollingTree() {
        super();
    }

    public JAutoScrollingTree(Object[] value) {
        super(value);
    }

    public JAutoScrollingTree(Vector&lt ? &gt value) {
        super(value);
    }

    public JAutoScrollingTree(Hashtable&lt ? , ? &gt value) {
        super(value);
    }

    public JAutoScrollingTree(TreeNode root) {
        super(root);
    }

    public JAutoScrollingTree(TreeNode root, boolean asksAllowsChildren) {
        super(root, asksAllowsChildren);
    }

    public JAutoScrollingTree(TreeModel newModel) {
        super(newModel);
    }
    
    public void autoscroll(Point p) {
        int realrow = getRowForLocation(p.x, p.y);
        Rectangle outer = getBounds();
        realrow = (p.y + outer.y &lt= MARGIN ? realrow &lt 1 ? 0 : realrow - 1
                : realrow &lt getRowCount() - 1 ? realrow + 1 : realrow);
        scrollRowToVisible(realrow);
    }

    public Insets getAutoscrollInsets() {
        Rectangle outer = getBounds();
        Rectangle inner = getParent().getBounds();
        return new Insets(inner.y - outer.y + MARGIN, inner.x - outer.x
                + MARGIN, outer.height - inner.height - inner.y + outer.y
                + MARGIN, outer.width - inner.width - inner.x + outer.x
                + MARGIN);
    }
}

O segundo passo é definir o seu DragSourceListener. Esse é o cara que vai aceitar o Drag and Drop quando o usuário começar a faze-lo. No meu caso, coloquei uma referência não só à árvore como também ao form que contém a árvore. Assim, posso acessar métodos do form que me sejam úteis.

Esse cara abaixo fará o editor realizar um "cut" em qualquer nó que esteja sendo movido. Quando o usuário soltar o nó, um paste será dado (isso será feita na outra classe, abaixo dela), fazendo assim a movimentação.

Note que eu não desejo cut para o nó raiz. Isso é uma particularidade do meu sistema, que você poderá remover para sua tree.

class TreeDragSource implements DragSourceListener, DragGestureListener {
    private DragSource source;
    protected DragGestureRecognizer recognizer;
    private DefaultMutableTreeNode oldNode;
    private JTree sourceTree;
    private JEditionPanel editor;

    public TreeDragSource(JTree tree, JEditionPanel editor, int actions) {
        sourceTree = tree;
        source = new DragSource();
        recognizer = source.createDefaultDragGestureRecognizer(sourceTree,
                actions, this);

        this.editor = editor;
    }

    /*
     * Drag Gesture Handler
     */
    public void dragGestureRecognized(DragGestureEvent dge) {
        oldNode = (DefaultMutableTreeNode) sourceTree.getLastSelectedPathComponent();
        if (oldNode == sourceTree.getModel().getRoot())
            return;

        this.editor.cut();

        source.startDrag(dge, DragSource.DefaultMoveDrop,
                NullTransferable.getInstance(), this);
    }

    public void dragEnter(DragSourceDragEvent dsde) {}
    public void dragExit(DragSourceEvent dse) {}
    public void dragOver(DragSourceDragEvent dsde) {}
    public void dropActionChanged(DragSourceDragEvent dsde) {}
    public void dragDropEnd(DragSourceDropEvent dsde) {}
}

Agora, você deve implementar o seu DropTargetListener. Esse é o cara que responde quando o mouse é liberado em algum lugar. Como eu disse anteriormente, eu movo os nós dando um "cut" quando a movimentação inicia, e um paste, quando ela termina.

class TreeDropTarget implements DropTargetListener {
    DropTarget target;

    JTree targetTree;

    private JEditionPanel editor;

    public TreeDropTarget(JTree tree, JEditionPanel editor) {
        targetTree = tree;
        target = new DropTarget(targetTree, this);
        this.editor = editor;
    }

    public void dragEnter(DropTargetDragEvent dtde) {
        dtde.acceptDrag(DnDConstants.ACTION_MOVE);
    }

    public void dragOver(DropTargetDragEvent dtde) {
        dtde.acceptDrag(DnDConstants.ACTION_MOVE);
    }

    public void drop(DropTargetDropEvent dtde) {
        //Gets the node where the user released the mouse in parent.
        Point pt = dtde.getLocation();
        JTree tree = (JTree) dtde.getDropTargetContext().getComponent();
        TreePath parentpath = tree.getClosestPathForLocation(pt.x, pt.y);
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) parentpath.getLastPathComponent();

        try {
            dtde.acceptDrop(dtde.getDropAction());
            editor.pasteInto(parent); //Paste the cutted node into parent
            dtde.dropComplete(true);
        } catch (Exception e) {
            throw new RuntimeException(e);
            try {
                dtde.rejectDrop();
            } catch (Exception e1) {
                throw new RuntimeException(e);
            }
        }
    }

    public void dragExit(DropTargetEvent dte) {}
    public void dropActionChanged(DropTargetDragEvent dtde) {}
}

Finalmente, falta colocar tudo para trabalhar junto.
Na construção da sua tree faça:

private JTree getTreeTests() {
        if (treeTests == null) {
            treeTests = new JAutoScrollingTree(getRoot()); 
            ds = new TreeDragSource(treeTests, this, DnDConstants.ACTION_MOVE);
            dt = new TreeDropTarget(treeTests, this); 
            //Restante de sua inicialização

As atributos ds e dt são, respectivamente, um TreeDragSource e um TreeDropTarget.

Furutani: Estou estudando as classes e interface envolvidas para implementar esse recurso. Não é uma coisa muito fácil para um iniciante :D, mas já consegui reproduzir o exemplo da java2s.

ViniGodoy: Sua explicação sobre cada parte do código postado como exemplo me ajudou a ter uma boa visão macro do recurso de DnD, porém fiquei com dúvida quanto ao uso da classe JEditionPanel. É uma classe q vc mesmo criou?

[quote=furutani]Achei um exemplo no site mas tem alguns bugs. Uma delas é o cursor do mouse que indica que não é possivel soltar, mas mesmo assim funciona.

[/quote]

Para solucionar o problema com o cursor:

    
    // Métodos da classe TreeDragSource
    
    public void dragEnter(DragSourceDragEvent dsde) {
        sourceTree.setCursor(DragSource.DefaultMoveDrop);
    }

    public void dragExit(DragSourceEvent dse) {
        sourceTree.setCursor(DragSource.DefaultMoveNoDrop);
    }

    public void dragOver(DragSourceDragEvent dsde) {
        sourceTree.setCursor(DragSource.DefaultMoveDrop);
    }

   public void dragDropEnd(DragSourceDropEvent dsde) {
        /*
        * to support move or copy, we have to check which occurred:
        */
        System.out.println("Drop Action: " + dsde.getDropAction());
        if (dsde.getDropSuccess() && (dsde.getDropAction() == DnDConstants.ACTION_MOVE)) {
            ((DefaultTreeModel) sourceTree.getModel()).removeNodeFromParent(oldNode);
        }
        sourceTree.setCursor(Cursor.getDefaultCursor());
        /*
        * to support move only... if (dsde.getDropSuccess()) {
        * ((DefaultTreeModel)sourceTree.getModel()).removeNodeFromParent(oldNode); }
        */
    }

Sim, o JEdition panel é o painel que contém a arvore.

Mas os recursos dele não são importantes. A única coisa relevante é que o método cut() “marca” qual foi o nó selecionado e o método paste move aquele nó para posição indicada.

Muito interessante o tópico, resolveu meu problema.
Mas alguem teria a informação sobre o problema do cursor?
Ainda nao conseguir fazer com que sumisse aquele sinal de proibido do cursor.
Obrigado