views:

270

answers:

1

I have two child windows that I want to accept drops from drag+drop objects in two different ways.

One is a JPanel that I want to accept a list of files. It works fine.

The other is a JTable that I want to accept a list of strings. It works fine if I don't enable the JPanel as a drop target.

When I try to enable both, the JPanel drop target seems to mask the JTable drop target, even though the JTable is not a child window of the JPanel and the two components are in nonoverlapping areas.

Also the JFrame that contains both of them seems to get the drop icon... not sure why.

Any advice? This is hard to debug.


Sample app, which is similar, follows, here I can't get the JTable to accept drops at all. :/

There is another question I would like to understand: if I drag from "drag source 1" onto "drop target 1" but do not release the mouse, my cursor flickers every time the drag source 1 label changes (once per second in this test app). Why does it do this? Is there a way to stop it from flickering?

package com.example.test.gui;

import java.awt.Component;
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.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
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.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

public class DragAndDropQuestion {
    public enum Color { 
        RED, ORANGE, YELLOW, GREEN, BLUE, VIOLET, WHITE, BLACK;
        @Override public String toString() 
            { return this.name().toLowerCase(); }
        static public Color randomColor(Random r)
        {
            Color[] colors = Color.values();
            return colors[r.nextInt(colors.length)];
        }
    }
    public static class SampleBean
    {
        final private Color color;
        final private char letter;
        final static public DataFlavor dataFlavor = 
            new DataFlavor(SampleBean.class, "SampleBean");
        public Color getColor() { return this.color; }
        public char getLetter() { return this.letter; }
        private SampleBean(Color color, char letter)
        {
            this.color = color;
            this.letter = letter;
        }
        static public SampleBean randomBean(Random r)
        {
            String letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            return new SampleBean(Color.randomColor(r),
                    letters.charAt(r.nextInt(letters.length())));
        }
        @Override public String toString() {
            return this.color.toString()+" "+this.letter; 
        }
    }
    public static class BorderPanel extends JPanel {
        public BorderPanel(String title)
        {
            setBorder(BorderFactory.createTitledBorder(title));
        }
    }
    public static class BeanTransferable implements Transferable
    {
        final private SampleBean value;
        static final private List<DataFlavor> flavors = Arrays.asList(
                DataFlavor.stringFlavor,
                SampleBean.dataFlavor
            );
        public BeanTransferable(SampleBean x) { this.value=x; }
        @Override public Object getTransferData(DataFlavor flavor) 
        throws UnsupportedFlavorException, IOException {
            if (flavors.get(0).equals(flavor))
            {
                return this.value.toString(); 
            }
            else if (flavors.get(1).equals(flavor))
            {
                return this.value;
            }
            else
            {
                throw new UnsupportedFlavorException(flavor);
            }
        }

        @Override public DataFlavor[] getTransferDataFlavors() {
            return flavors.toArray(new DataFlavor[0]);
        }
        @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavors.contains(flavor);
        }
    }
    public abstract static class SimpleDropTarget 
       implements DropTargetListener
    {
        final private String debugString;
        public SimpleDropTarget(String debugString) {
            this.debugString=debugString; 
        }
        @Override public void dragEnter(DropTargetDragEvent event) 
            { System.out.println(this.debugString+":dragEnter"); }
        @Override public void dragExit(DropTargetEvent event) {}
            { System.out.println(this.debugString+":dragExit"); }
        @Override public void dragOver(DropTargetDragEvent event) {}    
        @Override public void dropActionChanged(DropTargetDragEvent event) {}
        public void install(JComponent component) {
            new DropTarget(component, this);
        }
    }
    public abstract static class SimpleDragSource 
        implements DragSourceListener, DragGestureListener
    {
        final private String debugString;
        final private DragSource ds = new DragSource();
        public SimpleDragSource(String debugString) { 
            this.debugString=debugString; 
        }
        @Override public void dragDropEnd(DragSourceDropEvent event) {}
        @Override public void dragEnter(DragSourceDragEvent event) 
        { System.out.println(this.debugString+":dragEnter"); }
        @Override public void dragExit(DragSourceEvent event) 
        { System.out.println(this.debugString+":dragExit"); }
        @Override public void dragOver(DragSourceDragEvent event) {}
        @Override public void dropActionChanged(DragSourceDragEvent event) {}   
        public void install(JComponent component) {
            DragGestureRecognizer dgr = 
                this.ds.createDefaultDragGestureRecognizer(component,
                        DnDConstants.ACTION_COPY, this);
        }
        abstract public Transferable prepareTransferable();
        @Override public void dragGestureRecognized(DragGestureEvent dge)
        {
            this.ds.startDrag(dge,
                    DragSource.DefaultCopyDrop, prepareTransferable(), this);
        }
    }
    public static class BeanListModel implements TableModel {
        final private List<SampleBean> list = new ArrayList<SampleBean>();
        enum ColumnType {
            COLOR(Color.class, "color") {
                @Override public Color extractValue(SampleBean sampleBean) {
                    return sampleBean.getColor();
                }
            },
            LETTER(String.class, "letter") {
                @Override public String extractValue(SampleBean sampleBean) {
                    return Character.toString(sampleBean.getLetter());
                }
            },
            ;
            final private Class<?> cl;
            final private String name;
            public Class<?> getColumnClass() { return this.cl; }
            public String getColumnName() { return this.name; }
            ColumnType(Class<?> cl, String name) 
            { this.cl=cl; this.name=name; }
            abstract public Object extractValue(SampleBean sampleBean);
        }
        final static private ColumnType[] columns 
            = new ColumnType[]{ColumnType.COLOR, ColumnType.LETTER};

        @Override 
            public void addTableModelListener(TableModelListener arg0) {}
        @Override 
            public void removeTableModelListener(TableModelListener arg0) {}

        @Override public Class<?> getColumnClass(int column) {
            return columns[column].getColumnClass();
        }
        @Override public int getColumnCount() { 
            return columns.length; 
        }
        @Override public String getColumnName(int column) {
            return columns[column].getColumnName();
        }
        @Override public int getRowCount() { return list.size(); }
        @Override public Object getValueAt(int row, int column) {
            return columns[column].extractValue(list.get(row));
        }

        @Override public boolean isCellEditable(int row, int column) {
            return false;
        }

        @Override public void setValueAt(Object obj, int row, int column) {
            throw new UnsupportedOperationException();
        }       

        public void addBean(SampleBean bean)
        {
            this.list.add(bean);
        }
    }
    public static class BeanTablePanel extends BorderPanel {
        final private JTable table;
        final private BeanListModel tableModel;

        public BeanTablePanel(String title)
        {
            super(title);
            this.table = new JTable();
            this.tableModel = new BeanListModel();
            this.table.setModel(this.tableModel);
            add(new JScrollPane(this.table));
            SimpleDropTarget dt = new SimpleDropTarget("BeanTable"){
                @Override public void drop(DropTargetDropEvent dtde) {
                    Transferable tr = dtde.getTransferable();
                    if (tr.isDataFlavorSupported(SampleBean.dataFlavor))
                    {
                        try {
                            SampleBean b = (SampleBean) 
                                tr.getTransferData(SampleBean.dataFlavor);
                            addBean(b);
                        }
                        catch (UnsupportedFlavorException e) {
                            e.printStackTrace();
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            dt.install(this.table);
            // !!! This doesn't seem to work...
        }
        void addBean(SampleBean b) {
            this.tableModel.addBean(b);         
        }
    }
    public static class BeanLabelPanel extends BorderPanel {
        final private JLabel label;
        public BeanLabelPanel(String title)
        {
            super(title);
            this.label = new JLabel("drop item here");
            add(this.label);
            SimpleDropTarget dt = new SimpleDropTarget("BeanLabel"){
                @Override public void drop(DropTargetDropEvent dtde) {
                    Transferable tr = dtde.getTransferable();
                    if (tr.isDataFlavorSupported(DataFlavor.stringFlavor))
                    {
                        try {
                            String s = (String) 
                                tr.getTransferData(DataFlavor.stringFlavor);
                            setLabel(s);
                        }
                        catch (UnsupportedFlavorException e) {
                            e.printStackTrace();
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            dt.install(this.label);
        }       
        void setLabel(String s) { this.label.setText(s); }
    }
    public static class BeanSourcePanel extends BorderPanel {
        final private JLabel label;
        final private Random randomizer;
        private SampleBean x;

        public BeanSourcePanel(
         String title, ScheduledExecutorService scheduler)
        {
            super(title);
            this.label = new JLabel(" ");
            this.randomizer = new Random();
            add(this.label);

            scheduler.scheduleAtFixedRate(new Runnable()
            { public void run() { changeBean(); } }, 
            0, 1000, TimeUnit.MILLISECONDS);

            (new SimpleDragSource("RandomBean"){
                @Override public Transferable prepareTransferable() {
                    return new BeanTransferable(getBean());
                }
            }).install(this.label);
        }       
        public SampleBean getBean() { return this.x; }
        void changeBean()
        {
            this.x = SampleBean.randomBean(this.randomizer);
            this.label.setText(this.x.toString());
        }       
    }

    public static class DNDQFrame extends JFrame
    {
        public DNDQFrame(String title, ScheduledExecutorService scheduler)
        {
            setTitle(title);
            getContentPane().setLayout(
                    new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)
            );
            add(new JLabel("Drag and Drop Question"));
            add(new BeanSourcePanel("drag source 1", scheduler));
            add(new BeanLabelPanel("drop target 1"));
            add(new BeanTablePanel("drop target 2"));
        }
        @Override public Component add(Component component)
        {
            if (component instanceof JComponent)
                ((JComponent) component).setAlignmentX(0.0f);
            return super.add(component);            
        }       
    }

    static public void main(String[] args)
    {
        ScheduledExecutorService scheduler = 
            new ScheduledThreadPoolExecutor(1);
        DNDQFrame frame = new DNDQFrame("DragAndDropQuestion", scheduler);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
+2  A: 

Add the drop listener to the JScrollPane instead. You've got the table nested, so it's not receiving the events. Also, in your addBean() method you should add a table.revalidate() or it won't display the updated data in your tablemodel.

Kylar
Also, I don't experience the flickering you're seeing.
Kylar
Thanks. I used the JPanel containing the table + scrollpane instead, also switched from implementing TableModel to extending AbstractTableModel and using a call to fireTableRowsInserted().
Jason S