Elemente im JTree verschieben

Hallo!

Ich haben folgendes Problem: Ich möchte Nodes in einem JTree verschieben können.
Um genau zu sein, sollen nur Nodes unterhalb des gleichen Parents verschiebbar sein, um z.B die Reihenfolge zu verändern.
Das Ganze soll natürlich per Drag & Drop gehen :slight_smile:

Das einfachste MouseListener nehmen und nachsehen wo er gedrückt und losgelassen wurde.
Ansonsten muss Beni mal was dazu sagen er hatte glaube mal sowas gebaut

Die Idee mit dem MouseListener kam mir auch schon. Aber dann sieht
mach ja nicht wie der Node gepackt und verschoben wird, also Drag & Drop.

ich glaub nicht dass sowas automatisch bei DnD gemacht wird dafür wirst du da wohl auch selbst sorgen müssen, wie bei der einfachen Sache mit den Listenern

Also so wirklich einen hilfreichen Kommentar habe ich nicht… aber mit DnD sollte man da schon etwas hinbekommen.
Ich hab mal was ganz einfaches versucht, nur um Knoten allgemein hin und her zu schieben (naja, gelingt mehr oder weniger). In der Drop-Methode könnte man Bedingungen einführen, wann wie wo gedroppt werden soll. Die Chance, dass ein anderes Programm den Knoten klaut, schätze ich jetzt mal als gering ein.

import java.awt.GridLayout;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.io.IOException;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

public class Main extends JTree{
    public static void main( String[] args ) {
        JTree treeA = new Main();
        treeA.setModel( createModel( "Links" ));
        
        JTree treeB = new Main();
        treeB.setModel( createModel( "Rechts" ));
        
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setLayout( new GridLayout( 1, 2 ));
        frame.add( new JScrollPane( treeA ));
        frame.add( new JScrollPane( treeB ));
        frame.pack();
        frame.setVisible( true );
    }
    
    private static DefaultTreeModel createModel( String treeName ){
        MutableTreeNode root = new DefaultMutableTreeNode( treeName + " root" );
        MutableTreeNode childA = new DefaultMutableTreeNode( treeName + " A" );
        MutableTreeNode childB = new DefaultMutableTreeNode( treeName + " B" );
        MutableTreeNode childC = new DefaultMutableTreeNode( treeName + " C" );
        MutableTreeNode childD = new DefaultMutableTreeNode( treeName + " D" );
        MutableTreeNode childE = new DefaultMutableTreeNode( treeName + " E" );
        MutableTreeNode childF = new DefaultMutableTreeNode( treeName + " F" );
        MutableTreeNode childG = new DefaultMutableTreeNode( treeName + " G" );
        
        root.insert( childA, 0 );
        root.insert( childB, 1 );
        root.insert( childC, 2 );
        root.insert( childD, 3 );
        root.insert( childE, 4 );
        root.insert( childF, 5 );
        root.insert( childG, 6 );
        
        DefaultTreeModel model = new DefaultTreeModel( root );
        return model;
    }
    
    
    public Main() {
        DropTarget drop = new DropTarget( this, new DropTargetListener(){
            public void dragEnter( DropTargetDragEvent dtde ) {
                
            }

            public void dragExit( DropTargetEvent dte ) {
                
            }

            public void dragOver( DropTargetDragEvent dtde ) {
                
            }

            public void drop( DropTargetDropEvent dtde ) {
                try {
                    Data data = (Data)dtde.getTransferable().getTransferData( TRANSFERABLE_TREE_NODE );
                    data.model.removeNodeFromParent( data.node );
                    
                    Point location = dtde.getLocation();
                    TreePath path = getClosestPathForLocation( location.x, location.y );
                    DefaultTreeModel model = (DefaultTreeModel)getModel();
                    if( path == null )
                        path = new TreePath( model.getRoot() );
                    
                    model.insertNodeInto( data.node, (MutableTreeNode)path.getLastPathComponent(), 0 );
                    
                    dtde.dropComplete( true );
                } catch (UnsupportedFlavorException e) {
                    dtde.dropComplete( false );
                    e.printStackTrace();
                } catch (IOException e) {
                    dtde.dropComplete( false );
                    e.printStackTrace();
                }
            }

            public void dropActionChanged( DropTargetDragEvent dtde ) {
                // TODO Auto-generated method stub
                
            }
            
        });
        
        
        DragSource source = new DragSource();
        source.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_MOVE, new DragGestureListener(){
            public void dragGestureRecognized( DragGestureEvent dge ) {
                DefaultTreeModel model = (DefaultTreeModel)getModel();
                MutableTreeNode node = (MutableTreeNode)getLastSelectedPathComponent();
                
                if( node != null && model.getRoot() != node ){
                    Transferable transfer = new TransferableTreeNode( model, node );
                    dge.startDrag( DragSource.DefaultMoveDrop, transfer );
                }
            }
        });
    }
    
    public static final DataFlavor TRANSFERABLE_TREE_NODE = new DataFlavor( MutableTreeNode.class, "Element of a tree" );
    private static class TransferableTreeNode implements Transferable{
        private MutableTreeNode node;
        private DefaultTreeModel model;
        
        public TransferableTreeNode( DefaultTreeModel model, MutableTreeNode node ){
            this.node = node;
            this.model = model;
        }

        public Object getTransferData( DataFlavor flavor ) throws UnsupportedFlavorException, IOException {
            if( !isDataFlavorSupported(flavor))
                throw new UnsupportedFlavorException( flavor );

            Data data = new Data();
            data.model = model;
            data.node = node;
            return data;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{ TRANSFERABLE_TREE_NODE };
        }

        public boolean isDataFlavorSupported( DataFlavor flavor ) {
            return flavor.equals( TRANSFERABLE_TREE_NODE );
        }
    }
    
    private static class Data{
        public MutableTreeNode node;
        public DefaultTreeModel model;
    }
}

Das Ganze geht noch etwas schöner, vor allem in Java 6:

    tree.setDropMode(DropMode.INSERT);
    tree.setDragEnabled(true);
    tree.setTransferHandler(new TransferHandler("selectionPath"){
      public boolean canImport(TransferHandler.TransferSupport info) {
          if (!info.isDrop()) {
              return false;
          }

          DataFlavor wanted = null; //sowas natürlich nicht in der Methode machen, und das Ganze eh auch nicht als anonyme Klasse, dann geht das auch gut ;)
          try{
            wanted = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=javax.swing.tree.TreePath");
          }catch(Exception e){
            return false
          }
          if (!info.isDataFlavorSupported(wanted)) {
              return false;
          }

          // fetch the drop location (it's a JTree.DropLocation for JTree)
          JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();

          // we only support drops for valid paths in the tree
          return dl.getPath() != null;
      }
      public boolean importData(TransferHandler.TransferSupport info) {
          if (!canImport(info)) {
              return false;
          }

          // fetch the drop location (it's a JTree.DropLocation for JTree)
          JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();

          // fetch the path and child index from the drop location
          TreePath path = dl.getPath();
          int childIndex = dl.getChildIndex();

          Transferable trans = info.getTransferable();

          // fetch the data, and bail if it fails
          String data = null;
          TreePath dragTreePath = null;
          try{
              DataFlavor wanted = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType + ";class=javax.swing.tree.TreePath");
              dragTreePath = (TreePath)trans.getTransferData(wanted);
              data = dragTreePath.getLastPathComponent().toString();
          } catch (UnsupportedFlavorException e) {
              return false;
          } catch (IOException e) {
              return false;
          }catch(ClassNotFoundException e){
              return false;
          }

          // if the child index is -1, the drop was directly on top of the path,
          // which we'll treat as inserting at the end of the path's child list
          if (childIndex == -1) {
              childIndex = treeModel.getChildCount(path.getLastPathComponent());
          }

          //hier eventuell prüfen, ob das so verschoben werden darf
          DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(data);
          DefaultMutableTreeNode parentNode =
              (DefaultMutableTreeNode)path.getLastPathComponent();
          treeModel.insertNodeInto(newNode, parentNode, childIndex);
          
          //den alten Knoten wieder löschen
          treeModel.removeNodeFromParent((MutableTreeNode)treePath.getLastPathComponent());

          // expand and scroll so that the new node is visible
          TreePath newPath = path.pathByAddingChild(newNode);
          tree.makeVisible(newPath);
          tree.scrollRectToVisible(tree.getPathBounds(newPath));
          return true;
      }
    });

Vielen Dank für die Antworten. Ich werde mich
jetzt in Ruhe hinsetzten und versuchen das Ganze in meinen TRee
einzubauen.