Hello all.
I'm currrently working on a GUI that needs to support dragging some application-specific objects from a selection area onto a work area (JPanel). It's currently working in a "click where to place the object, then click the Add button" mode, but that's not good enough.
The obvious approach is to use D&D (Java 6 or later) with a custom DataFlavor for the objects, but I'm struggling to find and decent documentation or examples of how to fit the whole thing together. The Oracle tutorials do a decent job of listing all the classs and methods involved, but not how to fit it all into an application. There are many code samples on the web, but many are pre-Java6, and the remainder use Swing's built-in support for some specific data flavors.
If you have any experience of doing this, or know of any useful info source, I'd love to hear from you.
Thanks
James
ps: This is not homework ;)

Recommended Answers

All 7 Replies

To clarify, you would like to be able to drag a component from one JPanel to another or the smae panel?

Also are you looking for DnD option only? Or custom too?

Hi David, thanks for getting back so quickly.
There's a "chooser" area where the user can select and preview different objects, and there's a "work area"(actually a JPanel subclass with custom paintComponent) where those objects can be placed. I want the user to be able to drag the current object from the chooser and drop it onto the work area. The objects in question are application-specific classes.
I can't discuss the exact application here, but it would be exactly the same problem if I were to be (for example) dragging different electronic components onto a circuit board. Sounds like it should be straight forward, given Java's support for D&D, and it probably is simple, once you have thge whole picture in your head.

I've never been able to find a really good guide on the subject of drag and drop, but in my experience it all tends to come together pretty painlessly if you just start by making your Transferable, then a TransferHandler for each of your components that needs one. After that, drag and drop just tends to work the way you'd expect.

I always find that the amount of Javadoc that I have to read is daunting, but once I take the time to write each little bit that is required, it all works beautifully and the total effort was tiny. Then I can't resist adding more stuff to it, like copy and paste, and allowing the object to be dropped in more forms and more places. Almost anything can be dropped into a text editor as plain text if you really think about it, and what if someone drags the text back to your component? Then you get to really have fun with converting it back into a usable object.

Thanks for that. I have defined a custom DataFlavor, built a Transferrable, and am in the middle of the TransferHandler. Am I right in thinking that you don't need to write any mouse handling code, and just setting the transfer handler for the JComponents is enough?

Hmm I found this code somewhere on the net as a question on a forum (lost the link now), but I patched it up and altered it (still not the best) but hopefully it will give you the basic idea:

dfgf

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;

public class DndExample {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }

            private void createAndShowGUI() {

                final JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                DropPanel dropPanel = new DropPanel();

                Rectangle2D rectangleA = new Rectangle2D.Double(20, 20, 120, 50);
                Rectangle2D rectangleB = new Rectangle2D.Double(60, 90, 120, 50);

                MyLabel label1 = new MyLabel("gray", rectangleA, Color.GRAY);
                MyLabel label2 = new MyLabel("green", rectangleB, Color.GREEN);

                JPanel dp = new JPanel();

                dp.add(label1);
                dp.add(label2);

                JLabel label = new JLabel("Drag text here");
                dropPanel.add(label);

                JSeparator js = new JSeparator(JSeparator.VERTICAL);

                new MyDropTargetListener(dropPanel);

                MyDragGestureListener dlistener = new MyDragGestureListener();

                DragSource ds1 = new DragSource();
                ds1.createDefaultDragGestureRecognizer(label1, DnDConstants.ACTION_COPY, dlistener);

                DragSource ds2 = new DragSource();
                ds2.createDefaultDragGestureRecognizer(label2, DnDConstants.ACTION_COPY, dlistener);


                frame.add(dropPanel, BorderLayout.WEST);
                frame.add(js);
                frame.add(dp, BorderLayout.EAST);

                frame.pack();
                frame.setVisible(true);

            }
        });
    }
}

class MyLabel extends JLabel {

    Rectangle2D rect;
    private final Color color;

    public MyLabel(String text, Rectangle2D rect, Color color) {
        super(text);
        this.rect = rect;
        this.color = color;
    }

    public Rectangle2D getRect() {
        return rect;
    }

    public Color getRectColor() {
        return color;
    }
}

class DropPanel extends JPanel {

    ArrayList<MyLabel> myLabels = new ArrayList<>();

    public DropPanel() {
        super();
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        for (MyLabel myLabel : myLabels) {
            Rectangle2D r = myLabel.getRect();
            grphcs.setColor(myLabel.getRectColor());
            grphcs.drawRect((int) r.getX(), (int) r.getY(), (int) r.getWidth(), (int) r.getHeight());
        }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    void addMyLabel(MyLabel l) {
        myLabels.add(l);
    }
}

class MyDropTargetListener extends DropTargetAdapter {

    private DropTarget dropTarget;
    private DropPanel dp;

    public MyDropTargetListener(DropPanel panel) {
        dp = panel;
        dropTarget = new DropTarget(panel, DnDConstants.ACTION_COPY, this, true, null);

    }

    @Override
    public void drop(DropTargetDropEvent event) {
        try {
            DropTarget test = (DropTarget) event.getSource();
            Component ca = (Component) test.getComponent();
            Point dropPoint = ca.getMousePosition();
            Transferable tr = event.getTransferable();

            if (event.isDataFlavorSupported(TransferableShapeInfo.CustomFlavour)) {
                MyLabel myLabel = (MyLabel) tr.getTransferData(TransferableShapeInfo.CustomFlavour);

                if (myLabel != null) {
                    dp.addMyLabel(myLabel);
                    dp.revalidate();
                    dp.repaint();

                    event.dropComplete(true);
                }
            } else {
                event.rejectDrop();
            }
        } catch (HeadlessException | UnsupportedFlavorException | IOException e) {
            e.printStackTrace();
            event.rejectDrop();
        }
    }
}

class TransferableShapeInfo implements Transferable {

    protected static DataFlavor CustomFlavour =
            new DataFlavor(MyLabel.class, "A label object");
    protected static DataFlavor[] supportedFlavors = {DataFlavor.stringFlavor, CustomFlavour};
    private String iconName;
    private MyLabel myrectangle;

    public TransferableShapeInfo(MyLabel label) {
        this.myrectangle = label;
    }

    public TransferableShapeInfo(String text) {
        this.iconName = text;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return supportedFlavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        if (flavor.equals(CustomFlavour)) {
            return true;
        }
        return false;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
        if (flavor.equals(CustomFlavour)) {
            return myrectangle;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
    }
}

class MyDragGestureListener implements DragGestureListener {

    Rectangle2D rectangleA = new Rectangle2D.Double(20, 20, 120, 50);
    Rectangle2D rectangleB = new Rectangle2D.Double(60, 90, 120, 50);
    MyLabel grayRect = new MyLabel("gray", rectangleA, Color.GRAY);
    MyLabel greenRect = new MyLabel("green", rectangleB, Color.GREEN);
    private MyLabel rectangleToDrag;

    @Override
    public void dragGestureRecognized(DragGestureEvent event) {
        Cursor cursor = null;

        MyLabel label = (MyLabel) event.getComponent();
        String text = label.getText();

        if (text.equals("gray")) {
            rectangleToDrag = grayRect;
        }
        if (text.equals("green")) {
            rectangleToDrag = greenRect;
        }

        if (event.getDragAction() == DnDConstants.ACTION_COPY) {
            cursor = DragSource.DefaultCopyDrop;

        } else {
            cursor = DragSource.DefaultCopyNoDrop;
        }

        event.startDrag(cursor, new TransferableShapeInfo(rectangleToDrag));

    }
}

Since you are using custom components, you will probably be needing to call TransferHandler.exportAsDrag yourself to get the drag started because JPanel has no setDragEnabled method. Instead, you will need to listen for mouse motion to detect the gesture that you want to begin the drag. I imagine you'll want it to start from dragging the mouse a short distance after clicking somewhere in the component. After that happens, you call exportAsDrag and then if I recall correctly you won't get any more mouse events until the drag and drop is finished.

Once the drag is going, your mouse handler will be the TransferHandler.canImport method where you get to decide if dropping is allowed from moment to moment and you can change how the component is drawn to reflect how the drag is going, if you want.

David
Thanks for that code - I supect its based on an earlier version of Java than 1.6, or maybe it's just a lower-level implementation, because the current version doesn't need the drop target event handler stuff at all, it just needs the TransferHandler to be set for the target JComponent. bguild may be able to shed more light on that?

bguild
If you are ever in SW France I owe you a beer or two. exportAsDrag was the critical missing piece in my puzzle. It's working now.
Thank you thank you thank you
James

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.