3

我已经按照这个例子的形式实现了一组可拖动的选项卡: 如何使用 Java Swing 实现可拖动的选项卡?

一切似乎都按我的意愿工作,但是,当我拖动到主面板之外时,桌面将成为有效的放置目标(结果放置被接受并标记为成功)。

有没有办法拦截这个下降以对我们的根窗格之外的下降做出反应?检测起来很简单,但我不清楚如何在外界之前实际捕捉到下降。

在调用 DragSourceListener 的 dragDropEnd 时,拖放已经执行,并且似乎没有一个好的方法来结束在 dragOver/Exit/Whatever 中的拖动。

哎呀,如果这样的工作会很好:

@Override
public void dragOver(DragSourceDragEvent dragEvent)
{
    DragEnabledTabTransferData data = getTabTransferData(dragEvent);
    DragSourceContext dragSourceContext = dragEvent.getDragSourceContext();
    if (data == null)
    {
        dragSourceContext.setCursor(DragSource.DefaultMoveNoDrop);
        return;
    }
    if (!data.getTabbedPane().getRootPane().getBounds().contains(dragEvent.getLocation()))
    {
        dragSourceContext.dragDropEnd(new DragSourceDropEvent(dragSourceContext, 999, true));
    }
}

相反,阻力继续拖动。我有,但是为我的麻烦找了一个 DragDropEnd。

有任何想法吗?听到唯一的解决方案是让一些隐藏的最大化全局窗格仅充当捕获窗口外事件的放置目标,我会感到非常难过。

这是一个工作示例。如果您将选项卡拖到 Linux 中的桌面,它会尝试将传输数据转换为 Serializable 并且不高兴。如果您想直接跳到我上面指出的内容,我正在玩的拖拽评论为“这是我假设我们能够拦截东西的地方”。

/** "Simple" example of DnD tabbed panes. Sourced from Eugene Yokota:
 * http:stackoverflow.com/questions/60269/how-to-implement-draggable-tab-using-java-swing */
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import javax.swing.*;

public class DnDTabbedPane extends JTabbedPane {
    private static final String NAME = "TabTransferData";
    private final DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME);

    public DnDTabbedPane() {
        super();
        final DragSourceListener dsl = new DragSourceListener() {
            public void dragEnter(DragSourceDragEvent e) {
                e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
            }

            public void dragExit(DragSourceEvent e) {
                e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
            }

            /**
             * This is where I'd assume we'd be able to intercept stuff 
             * so drops don't happen where we don't want them to.
             */
            public void dragOver(DragSourceDragEvent e) {
                TabTransferData data = getTabTransferData(e);
                if (data == null) {
                    e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
                    return;
                }
                //This is where I ended up robokilling the drag via hackery
                e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
            }
            public void dragDropEnd(DragSourceDropEvent e) {}
            public void dropActionChanged(DragSourceDragEvent e) {}
        };

        final DragGestureListener dgl = new DragGestureListener() {
            public void dragGestureRecognized(DragGestureEvent e) {

                Point tabPt = e.getDragOrigin();
                int dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
                if (dragTabIndex < 0) {
                    return;
                }
                e.startDrag(DragSource.DefaultMoveDrop,new TabTransferable(DnDTabbedPane.this, dragTabIndex), dsl);
            }
        };

        new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new CDropTargetListener(), true);
        new DragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl);
    }

    private TabTransferData getTabTransferData(DropTargetDropEvent a_event) {       
        try {
            return (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);             
        } catch (Exception e) {}

        return null;
    }

    private TabTransferData getTabTransferData(DropTargetDragEvent a_event) {
        try {
            return  (TabTransferData) a_event.getTransferable().getTransferData(FLAVOR);                
        } catch (Exception e) {}

        return null;
    }

    private TabTransferData getTabTransferData(DragSourceDragEvent a_event) {
        try {
            return (TabTransferData) a_event.getDragSourceContext().getTransferable().getTransferData(FLAVOR);              
        } catch (Exception e) {}

        return null;        
    }

    class TabTransferable implements Transferable {
        private TabTransferData m_data = null;
        private DataFlavor[] flavors = {FLAVOR};
        public TabTransferable(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
            m_data = new TabTransferData(DnDTabbedPane.this, a_tabIndex);
        }

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

        public DataFlavor[] getTransferDataFlavors() {
            return flavors;
        }

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavor.getHumanPresentableName().equals(NAME);
        }       
    }

    class TabTransferData {
        DnDTabbedPane m_tabbedPane = null;
        int m_tabIndex = -1;

        public TabTransferData(DnDTabbedPane a_tabbedPane, int a_tabIndex) {
            m_tabbedPane = a_tabbedPane;
            m_tabIndex = a_tabIndex;
        }
    }

    class CDropTargetListener implements DropTargetListener {
        public void dragEnter(DropTargetDragEvent e) {
            if (isDragAcceptable(e)) {
                e.acceptDrag(e.getDropAction());
            } else {
                e.rejectDrag();
            }
        }

        public void drop(DropTargetDropEvent a_event) {
            if (isDropAcceptable(a_event)) {
                convertTab(getTabTransferData(a_event),
                getTargetTabIndex(a_event.getLocation()));
                a_event.dropComplete(true);
            } else {
                a_event.dropComplete(false);
            }
        }

        private boolean isTransferableGood(Transferable t, DataFlavor flavor)
        {
            return t == null || t.isDataFlavorSupported(flavor);
        }

        private boolean isDataGood(TabTransferData data)
        {
            if (DnDTabbedPane.this == data.m_tabbedPane && data.m_tabIndex >= 0) {
                return true;
            }
            return false;
        }

        public boolean isDragAcceptable(DropTargetDragEvent e) {
            Transferable t = e.getTransferable();
            if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
                return false;
            }
            return isDataGood(getTabTransferData(e));
        }

        public boolean isDropAcceptable(DropTargetDropEvent e) {
            Transferable t = e.getTransferable();
            if (!isTransferableGood(t, e.getCurrentDataFlavors()[0])) {
                return false;
            }
            return isDataGood(getTabTransferData(e));
        }

        public void dragExit(DropTargetEvent e) {}
        public void dropActionChanged(DropTargetDragEvent e) {}
        public void dragOver(final DropTargetDragEvent e) {}
    }

    private int getTargetTabIndex(Point a_point) {
        for (int i = 0; i < getTabCount(); i++) {
            Rectangle r = getBoundsAt(i);
            r.setRect(r.x - r.width / 2, r.y, r.width, r.height);
            if (r.contains(a_point)) {
                return i;
            }  
        }
        return -1;
    }

    private void convertTab(TabTransferData a_data, int a_targetIndex) {
        DnDTabbedPane source = a_data.m_tabbedPane;
        int sourceIndex = a_data.m_tabIndex;
        if (sourceIndex < 0) {
            return;
        }  

        Component cmp = source.getComponentAt(sourceIndex);
        String str = source.getTitleAt(sourceIndex);

        if (a_targetIndex < 0 || sourceIndex == a_targetIndex) {
            return;
        } 
        source.remove(sourceIndex);
        if (a_targetIndex == getTabCount()) {
            addTab(str, cmp);
        } else if (sourceIndex > a_targetIndex) {
            insertTab(str, null, cmp, null, a_targetIndex);
        } else {
            insertTab(str, null, cmp, null, a_targetIndex - 1);
        }
    }

    public static void main(String[] args)
    {
        JFrame window = new JFrame();
        DnDTabbedPane tabbedPane = new DnDTabbedPane();
        for(int i=0; i< 5; i++)
        {
            tabbedPane.addTab("I'm tab "+i, new JLabel("I'm tab "+i));
        }
        window.add(tabbedPane);
        window.setSize(400, 200);
        window.setVisible(true);
    }
}

到目前为止,我能做的最好的事情就是在我们跳出父母时调用这种效果。

    Component rootPane = SwingUtilities.getRoot(component);
    Rectangle bounds = rootPane.getBounds();
    if (!bounds.contains(location))
    {
        Robot robot = null;
        try
        {
            robot = new Robot();
        } catch (AWTException e)
        {
            return;
        }
        robot.keyPress(KeyEvent.VK_ESCAPE);
        robot.keyRelease(KeyEvent.VK_ESCAPE);
    }

这是一个彻底的黑客,并没有解决我的问题。我想拦截最终的 drop 事件,看看它是否在框架之外并在它自己的 JFrame 中生成选项卡。

如果我使用的是 NetBeans、MyDoggy 或 Eclipse 框架,我想这一切都会为我神奇地处理。唉。

4

3 回答 3

1

没有办法直接取消拖动。见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4502185

我希望通过更改光标向用户显示不允许在桌面上放置。

您的 DragSourceListenerdsl在 dragOver 方法中有一个 DragSourceDragEvent ,它告诉您目标操作在桌面上是 NONE。

改成这样:

public void dragOver(DragSourceDragEvent e) {

    TabTransferData data = getTabTransferData(e);

    if( data == null || e.getTargetActions() == DnDConstants.ACTION_NONE ) {
        e.getDragSourceContext().setCursor( DragSource.DefaultMoveNoDrop );
        return;
    }

    e.getDragSourceContext().setCursor( DragSource.DefaultMoveDrop);
}

如果你真的想取消,那么你必须使用你的 ESC 解决方案或类似的东西:

    try {
        new Robot().mouseRelease( InputEvent.BUTTON1_MASK ); // if Button1 was the only Button to start a Drag
    } catch( AWTException e1 ) {
    }
于 2013-01-11T08:17:52.393 回答
0

正如@oliholz 所证实的那样,没有办法做到这一点,而不必通过击键强制取消。

但是,出于创建可撕下选项卡的需要,我发现创建一个浮动窗格,它本身就是一个放置目标侦听器,感觉像是最干净的解决方案:

package com.amish.whatever;

import java.awt.MouseInfo;
import java.awt.Point;
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.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JLabel;
import javax.swing.JWindow;
import javax.swing.Timer;

public class TearAwayTab extends JWindow {
    MousePoller mousePoller = new MousePoller();
    public TearAwayTab() {
        this.add(new JLabel("FLONT"));
        this.pack();
        new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, new EasyDropTarget(), true);
        this.setVisible(false);
    }

    private void center(Point location)
    {
        Point center = new Point();
        center.setLocation(location.x-this.getWidth()/2, location.y-this.getHeight()/2);
        TearAwayTab.this.setLocation(center);
    }

    public void attach(Point location)
    {
        center(location);
        mousePoller.start();
        this.setVisible(true);
    }

    public void detach()
    {
        mousePoller.stop();
        this.setVisible(false);
    }

    private int DELAY = 10;
    private class MousePoller extends Timer{
        public MousePoller(){
            super(DELAY, new ActionListener() {
                private Point lastPoint = MouseInfo.getPointerInfo().getLocation();
                @Override
                public void actionPerformed(ActionEvent e) {
                    Point point = MouseInfo.getPointerInfo().getLocation();

                    if (!point.equals(lastPoint)) {
                        center(point);
                    }

                    lastPoint = point;
                }
            });
        }
    }

    private class EasyDropTarget implements DropTargetListener
    {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
            dtde.acceptDrag(dtde.getDropAction());
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {}

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {}

        @Override
        public void dragExit(DropTargetEvent dte) {}

        @Override
        public void drop(DropTargetDropEvent dtde) {
            dtde.dropComplete(true);
            detach();
            System.out.println("DROP Intercepted");
        }
    }
}

带有 MousePoller 的位可以解决鼠标太快地擦洗鼠标侦听器无法可靠地更新位置的问题。我尝试过使用运动监听器,并且能够很容易地逃离漂浮物的界限。

回到第一个示例,我现在将撕开选项卡包含为选项卡式窗格的私有成员,并在退出或进入我的放置区域时调用附加和分离:

        final DragSourceListener dsl = new DragSourceListener() {
            public void dragEnter(DragSourceDragEvent e) {
                e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
                Rectangle bounds = SwingUtilities.getRoot(DnDTabbedPane.this).getBounds();

                if(bounds.contains(e.getLocation())){
                    tearTab.detach(); 
                }
            }

            public void dragExit(DragSourceEvent e) {
                e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
                tearTab.attach(e.getLocation());
            }
            ...

这还有一个额外的好处,那就是在拖出然后重新进入的情况下保留 DnD 操作。

感谢您的输入。如果您有任何其他想法/意见,我会全力以赴。

于 2013-01-14T19:26:59.197 回答
0

这与选项卡没有直接关系,但阻止拖动到桌面的一种方法是将拖动的任何内容包装在自定义包装器类中。然后,当您制作 TransferHandler 时,制作一个 DataFlavor localFlavor = new ActivationDataFlavor(YourWrapperClass.class, DataFlavor.javaJVMLocalObjectMimeType, "description"); 接下来,重写 createTransferable 方法以拥有 new DataHandler(passedInComponent, localFlavor.getMimeType()); 并返回一个新的 Transferable,您在其中重写了所有方法以仅具有您的 localFlavor。最后,在 importData 方法中,确保将数据导入为 localFlavor 类型。这将防止拖到 deaktop,因为您定义的风味是 JVM 本地的。

于 2013-01-17T19:00:31.487 回答