5

I would like to implement DnD for my application to accept only files of certain extension (eg. wrl). I would like to change the cursor to a drag cursor if the file will be accepted and revert back to normal cursor when the file of wrong extension is drag and dropped.

I have been following the following tutorial/example from http://docs.oracle.com/javase/tutorial/uiswing/dnd/toplevel.html

The change that I make is in the canImport function

    public boolean canImport(TransferHandler.TransferSupport support) 
    {
        if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) 
        {
            return false;
        }

        Transferable t = support.getTransferable();

        try 
        {
          java.util.List<File> l = (java.util.List<File>)t.getTransferData(DataFlavor.javaFileListFlavor);
          if (l.size() != 1)
          {
            return false;
          }

          File f = l.get(0);

          String extension = f.getName().substring(f.getName().lastIndexOf("."));

          return extension.equalsIgnoreCase(".wrl");
        } 

        catch (UnsupportedFlavorException e) 
        {
            return false;
        } 
        catch (IOException e) 
        {
            return false;
        }
    }

This behaves correctly when I drag over in different files. However, when I drop the file, I get the following exception

Exception in thread "AWT-EventQueue-0" java.awt.dnd.InvalidDnDOperationException: No drop current
    at sun.awt.dnd.SunDropTargetContextPeer.getTransferData(SunDropTargetContextPeer.java:227)
    at sun.awt.datatransfer.TransferableProxy.getTransferData(TransferableProxy.java:56)
    at java.awt.dnd.DropTargetContext$TransferableProxy.getTransferData(DropTargetContext.java:359)
    at appl.TopLevelTransferHandlerDemo$1.canImport(TopLevelTransferHandlerDemo.java:207)
    at javax.swing.TransferHandler$DropHandler.drop(TransferHandler.java:1454)
    at java.awt.dnd.DropTarget.drop(DropTarget.java:434)
    at javax.swing.TransferHandler$SwingDropTarget.drop(TransferHandler.java:1193)
    at sun.awt.dnd.SunDropTargetContextPeer.processDropMessage(SunDropTargetContextPeer.java:519)
    at sun.awt.dnd.SunDropTargetContextPeer$EventDispatcher.dispatchDropEvent(SunDropTargetContextPeer.java:832)
    at sun.awt.dnd.SunDropTargetContextPeer$EventDispatcher.dispatchEvent(SunDropTargetContextPeer.java:756)
    at sun.awt.dnd.SunDropTargetEvent.dispatch(SunDropTargetEvent.java:30)
    at java.awt.Component.dispatchEventImpl(Component.java:4487)
    at java.awt.Container.dispatchEventImpl(Container.java:2099)
    at java.awt.Component.dispatchEvent(Component.java:4460)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577)
    at java.awt.LightweightDispatcher.processDropTargetEvent(Container.java:4312)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4163)
    at java.awt.Container.dispatchEventImpl(Container.java:2085)
    at java.awt.Window.dispatchEventImpl(Window.java:2478)
    at java.awt.Component.dispatchEvent(Component.java:4460)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

Any help greatly appreciated

Ky -
  • 30,724
  • 51
  • 192
  • 308
Leon
  • 1,141
  • 13
  • 25
  • 1
    Welcome to my world. I don't think you can do this until a drop has actually occurred. I believe this occurs because the JVM won't create an actually transferable data object until the drop has occurred. – MadProgrammer Feb 26 '13 at 02:24
  • @MadProgrammer I agree, but I would like to provide more immediate feedback to the user – Leon Feb 26 '13 at 02:25
  • 1
    So would I, but without the ability to actually check the data in the transferable object, there's nothing more you can do until it's dropped... – MadProgrammer Feb 26 '13 at 02:26
  • @MadProgrammer I tried messing around with implementing my own DropTargetListener but I thought there maybe an easier/more recommended way – Leon Feb 26 '13 at 02:28

1 Answers1

17

The last time I checked, this didn't work on Mac's, but that might have changed with Java 7...

enter image description hereenter image description hereenter image description here

Now, remember, when it comes to drag'n'drop, there is no easy answer that will do everything.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class TestDragNDrop {

    public static void main(String[] args) {
        new TestDragNDrop();
    }

    public TestDragNDrop() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public static class TestPane extends JPanel implements DropTargetListener {

        public enum DragState {

            Waiting,
            Accept,
            Reject
        }
        private DragState state = DragState.Waiting;
        private BufferedImage happy;
        private BufferedImage wait;
        private BufferedImage sad;

        public TestPane() {
            DropTarget dt = new DropTarget(
                    this,
                    DnDConstants.ACTION_COPY_OR_MOVE,
                    this,
                    true);

            setBackground(Color.BLACK);
            try {
                happy = ImageIO.read(getClass().getResource("/Happy.png"));
                wait = ImageIO.read(getClass().getResource("/Wait.png"));
                sad = ImageIO.read(getClass().getResource("/Sad.png"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            BufferedImage bg = null;
            switch (state) {
                case Waiting:
                    bg = wait;
                    break;
                case Accept:
                    bg = happy;
                    break;
                case Reject:
                    bg = sad;
                    break;
            }
            if (bg != null) {
                int x = (getWidth() - bg.getWidth()) / 2;
                int y = (getHeight() - bg.getHeight()) / 2;
                g.drawImage(bg, x, y, this);
            }
        }

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
            state = DragState.Reject;
            Transferable t = dtde.getTransferable();
            if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                try {
                    Object td = t.getTransferData(DataFlavor.javaFileListFlavor);
                    if (td instanceof List) {
                        state = DragState.Accept;
                        for (Object value : ((List) td)) {
                            if (value instanceof File) {
                                File file = (File) value;
                                String name = file.getName().toLowerCase();
                                if (!name.endsWith(".png")) {
                                    state = DragState.Reject;
                                    break;
                                }
                            }
                        }
                    }
                } catch (UnsupportedFlavorException | IOException ex) {
                    ex.printStackTrace();
                }
            }
            if (state == DragState.Accept) {
                dtde.acceptDrag(DnDConstants.ACTION_COPY);
            } else {
                dtde.rejectDrag();
            }
            repaint();
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
        }

        @Override
        public void dragExit(DropTargetEvent dte) {
            state = DragState.Waiting;
            repaint();
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            state = DragState.Waiting;
            repaint();
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • looks like what I need. Just checking and if work, I will upvote and accept – Leon Feb 26 '13 at 04:49
  • 1
    So, I have a demonstrated example, which at the time of written, works. There are no other examples, so I'm left wondering why this question should be downvoted? In what ways does it not answer the question in been asked? In what ways could it be improved? – MadProgrammer Feb 23 '21 at 21:21
  • @MadProgrammer maybe someone who prefer a smiling green and neutral yellow? Anyway, nice piece of code ;) – Matthieu Mar 03 '21 at 14:40
  • I found that on Windows 10 the solution above shows an icon label "MOVE" always on Accept instead of "COPY" or whatever else you want which is misleading to the end user. A fix for this (from https://coderanch.com/t/658528/java/change-icon-label-drag-drop) is in `dragOver()` to also invoke `dtde.acceptDrag(DnDConstants.ACTION_COPY)` – Core Jan 13 '23 at 20:56