2
votes

I am using drag and drop in swing to place a button on one of four components. To make it more appealing instead of just the mouse indicating something being dragged (which is working fine) I wanted to show it in real time. This works except if the mouse goes back over the component being dragged the drag listener registers a dragExit event which removes the button which causes the listener to focus back on the parent component and fire a dragEnter causing a flicker. I need to make the button not affect the drag listener somehow. Any ideas?

dragging a DndButton that says hello between four DndLabels

this is the jlabel I'm adding it too (I know jlabel is not a good choice but it is working well and easily can display an image which I need)

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.*;


//and here is the button to drag   
public class DndLabel extends JLabel implements DropTargetListener {

    private DropTarget target;
    private DndButton button;
    public DndLabel last;

    //initialize the JTable with the data
    public DndLabel() {
        super();

        //mark this a DropTarget
        target = new DropTarget(this, this);

        //have it utilize a custom transfer handler
        setTransferHandler(new MyTransferHandler());
    }

    public void dragEnter(DropTargetDragEvent dtde) {
        System.out.println("enter");

        try {
            //get the Point where the drop occurred
            Point loc = dtde.getLocation();

            //get Transfer data
            Transferable t = dtde.getTransferable();

            //get the Data flavors transferred with the Transferable
            DataFlavor[] d = t.getTransferDataFlavors();

            button = (DndButton) t.getTransferData(d[0]);

            button.setBounds(loc.x, 0, 100, 50);

            add(button);

            //and if the DataFlavors match for the DnDTable 
            //(ie., we don't want an ImageFlavor marking an image transfer)
            if (getTransferHandler().canImport(this, d)) {

                //then import the Draggable JComponent and repaint() the JTable
                ((MyTransferHandler) getTransferHandler()).importData(this, (DndButton) t.getTransferData(d[0]), loc);
                repaint();
            } else {
                return;
            }
        } catch (UnsupportedFlavorException  ex) {
            ex.printStackTrace();
        } catch(IOException ex){
            ex.printStackTrace();
        }
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
        button.setLocation(dtde.getLocation().x, 0);
    }

    public void dragExit(DropTargetEvent dte) {
        System.out.println("remove");

        last = null;
        remove(button);
        repaint();

    }

    //This is what happens when a Drop occurs
    public void drop(DropTargetDropEvent dtde) {
        System.out.println("drop!");
        try {
            //get the Point where the drop occurred
            Point loc = dtde.getLocation();

            //get Transfer data
            Transferable t = dtde.getTransferable();

            //get the Data flavors transferred with the Transferable
            DataFlavor[] d = t.getTransferDataFlavors();

            DndButton tempButton = (DndButton) t.getTransferData(d[0]);

            tempButton.setBounds(loc.x, 0, 100, 50);

            add(tempButton);

            //and if the DataFlavors match for the DnDTable 
            //(ie., we don't want an ImageFlavor marking an image transfer)
            if (getTransferHandler().canImport(this, d)) {

                //then import the Draggable JComponent and repaint() the JTable
                ((MyTransferHandler) getTransferHandler()).importData(this, (DndButton) t.getTransferData(d[0]), loc);
                repaint();
            } else {
                return;
            }

        } catch (UnsupportedFlavorException ex) {
            ex.printStackTrace();
        }catch(IOException ex){

        }finally {
            dtde.dropComplete(true);
        }
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    class MyTransferHandler extends TransferHandler {

        //tests for a valid JButton DataFlavor
        public boolean canImport(JComponent c, DataFlavor[] f) {
            DataFlavor temp = new DataFlavor(DndButton.class, "JButton");
            for (DataFlavor d : f) {
                if (d.equals(temp)) {
                    return true;
                }

            }
            return false;
        }

        //add the data into the JTable
        public boolean importData(JComponent comp, Transferable t, Point p) {
            try {
                DndButton tempButton = (DndButton) t.getTransferData(new DataFlavor(DndButton.class, "JButton"));

            } catch (UnsupportedFlavorException | IOException ex) {
                System.err.println(ex);
            }
            return true;
        }
    }
}
class DndButton extends JButton implements Transferable, DragSourceListener, DragGestureListener {

    //marks this JButton as the source of the Drag
    private DragSource source;
    private TransferHandler t;

    public DndButton() {
        this("");
    }

    public DndButton(String message) {
        super(message);

        //The TransferHandler returns a new DnDButton
        //to be transferred in the Drag
        t = new TransferHandler() {

            public Transferable createTransferable(JComponent c) {
                return new DndButton(getText());
            }
        };
        setTransferHandler(t);

        //The Drag will copy the DnDButton rather than moving it
        source = new DragSource();
        source.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);
    }

    //The DataFlavor is a marker to let the DropTarget know how to
    //handle the Transferable
    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[]{new DataFlavor(DndButton.class, "JButton")};
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return true;
    }

    public Object getTransferData(DataFlavor flavor) {
        return this;
    }

    public void dragEnter(DragSourceDragEvent dsde) {
    }

    public void dragOver(DragSourceDragEvent dsde) {
        //this.setLocation(dsde.getX()-150,0);
    }

    public void dropActionchanged(DragSourceDragEvent dsde) {
    }

    public void dragExit(DragSourceEvent dse) {
    }

    //when the drag finishes, then repaint the DnDButton
    //so it doesn't look like it has still been pressed down
    public void dragDropEnd(DragSourceDropEvent dsde) {
        // JComponent c = (JComponent) this.getParent();
        //c.remove(this);
        //c.repaint();
    }

    //when a DragGesture is recognized, initiate the Drag
    public void dragGestureRecognized(DragGestureEvent dge) {
        source.startDrag(dge, DragSource.DefaultMoveDrop, DndButton.this, this);
    }

    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
    }
}

class Main {

    public static void main(String[] args) {
        JFrame f = new JFrame("sscce");
        f.setBounds(0, 0, 500, 80);
        f.setVisible(true);
        DndLabel trackOne = new DndLabel();
        trackOne.setBounds(0, 0, 500, 50);
        f.add(trackOne);
        DndButton b = new DndButton("hello");
        b.setBounds(0, 0, 100, trackOne.getHeight());

        trackOne.add(b);
        trackOne.revalidate();
        trackOne.repaint();
    }
}
1
For better help sooner, post an SSCCE.Andrew Thompson
@Andrew Thompson OK I changed the code to SSCCEghostbust555

1 Answers

4
votes

I, personally, would probably resist doing it this way. The problem is, each time you add the component, the component is interfering with the mouse listeners in the hierarchy, causing the container to be notified of an exit event, which causes the button to be removed and the container to be notified of enter event and the cycle continues.

Personally, I would do one of two things. I would either

  • Paint to the glass pane a representation of the button or
  • Paint a representation of the button on the drag panel

This removes the possibility of the button causing interference with the mouse listeners involved.

Check out My Drag Image is Better than Yours. While you're there dig around a bit, Tim does a number of excellent articles on drag'n'drop which are worth the read