在Swing drop回调期间是否禁止组件层次结构更改?

时间:2017-11-21 09:28:41

标签: java swing drag-and-drop

我注意到,如果我在处理丢弃DropTargetListener.drop()时执行removeAll()add()revalidate(),则丢弃将成功,更改将生效但我赢了能够开始第二次拖动。任何后续拖动尝试都将抛出java.awt.dnd.InvalidDnDOperationException: Drag and drop in progress

似乎如果对层次结构进行更改,则不会清除拖动,并且Swing认为仍有一个正在进行的拖动等待完成。即使在调用DropTargetDropEvent.acceptDrop()DropTargetDropEvent.dropComplete()

之后更改层次结构,也会发生这种情况
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.dnd.*;
import javax.activation.*;
import java.awt.datatransfer.*;
import java.util.TooManyListenersException;


class MyWidget extends JComponent {

    MyWidget() {
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        updateState();
    }

    void updateState() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // System.out.println("Hello World on " + Thread.currentThread());
                removeAll();
                add(newButton("aaa"));
                add(newButton("bbb"));
                revalidate();

            }
        };
        if (SwingUtilities.isEventDispatchThread()) {
            runnable.run();
        } else {
           SwingUtilities.invokeLater(runnable);
        }

    }

    JButton newButton(String text) {
        JButton theButton = new JButton(text);

        DragSource ds = new DragSource();
        DragGestureRecognizer dgr = ds.createDefaultDragGestureRecognizer(theButton, DnDConstants.ACTION_COPY, new DragGestureListener() {
            @Override
            public void dragGestureRecognized(DragGestureEvent dge) {
                Transferable transferable = new DataHandler("my text", "text/plain");
                dge.startDrag(DragSource.DefaultCopyDrop, transferable);
            }
        });

        DropTarget dt = new DropTarget();
        dt.setComponent(theButton);
        try {
        dt.addDropTargetListener(new DropTargetAdapter() {
          @Override
          public void drop(DropTargetDropEvent dtde) {
            System.out.println("drop accepted");
            dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
            dtde.dropComplete(true);
            updateState();
          }
        });
        } catch (TooManyListenersException e) {}
        return theButton;
    }

}

public class App 
{
    static void createGUI() {
        JFrame frame = new JFrame();
        frame.setTitle("my app");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new MyWidget(), BorderLayout.CENTER);
        frame.setSize(500,500);
        // frame.pack();
        frame.setVisible(true);
    }

    public static void main( String[] args )
    {
        SwingUtilities.invokeLater(new Runnable() { 
            @Override
            public void run() {
                System.out.println("Hello World on " + Thread.currentThread());
                createGUI();
            }
        });
    }
}

1 个答案:

答案 0 :(得分:1)

问题是在拖动尚未完成时发生组件层次结构更改。调用DropTargetDropEvent.dropComplete(true)后,可能会完成删除但不会拖动部分。在当前DropTargetListener.drop()(当前正在运行的EDT)将控制权返回到EDT主循环之后,拖拽侧可能会在EDT中运行回调。

因此必须延迟组件层次结构更改,以便在拖动操作完成后运行它们。

所以而不是

if (SwingUtilities.isEventDispatchThread()) {
    runnable.run();
} else {
   SwingUtilities.invokeLater(runnable);
}

运行

SwingUtilities.invokeLater(runnable); // unconditionally 

将调度runnable在拖动后运行,因为dropComplete()已经在EDT事件队列中安排了一个事件来处理拖动完成。因此,invokeLater()将在将事件标记为已完成的事件之后放置更改组件层次结构的代码。