TransferHandler.canImport(TransferSupport)在Windows上重复调用,但在Linux / Mac上没有?

时间:2013-06-11 09:32:08

标签: java swing drag-and-drop jtree

我在JTrees之间遇到了DnD的一些问题/困惑。阅读TransferHandler的文档并找到以下内容后

  

canImport( TransferHandler.TransferSupport支持

     

在拖放操作期间反复调用此方法,以允许开发人员配置属性,并返回传输的可接受性;返回值为true表示由给定的TransferSupport表示的传输(包含传输的所有细节)在当前时间是可接受的,并且值为false拒绝传输。

我实现了下面的类(长代码,道歉)。我们的想法是在树旁边显示一个信息工具提示,它解释了为什么丢弃或不可能。实现依赖于重复调用canImport,它在我的开发平台(windows)上运行良好。然而,当在Linux / Mac上进行测试时,它不起作用,因为我正在使用的计时器没有被重置(仅在mouseMoved事件上调用canImport,我承认,这听起来很合理。)

这是正常行为还是其中一个java实现(或我的)中的错误?有关如何更改我的代码的任何建议,以便它可以像现在在Windows上一样工作(我正在考虑暂时向树组件添加鼠标侦听器并隐藏mouseExited上的工具提示)?

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
import javax.swing.tree.*;

public class DndDemo extends JFrame {

    private JSplitPane jsppMain;
    private JScrollPane jscpTarget;
    private JTree jtTarget;
    private JScrollPane jscpSource;
    private JTree jtSource;

    public DndDemo() {
        initComponents();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                DndDemo demo = new DndDemo();
                demo.setVisible(true);
            }
        });
    }

    private void initComponents() {
        setLayout(new BorderLayout());

        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Drag here");
        DefaultTreeModel model = new DefaultTreeModel(root);
        for (int i = 0; i < 10; i++) {
            DefaultMutableTreeNode child = new DefaultMutableTreeNode("Member" + i);
            model.insertNodeInto(child, root, i);
        }        
        jtTarget = new JTree(model);                
        jscpTarget = new JScrollPane(jtTarget);

        root = new DefaultMutableTreeNode("Drag from here");
        model = new DefaultTreeModel(root);
        for (int i = 0; i < 10; i++) {
            DefaultMutableTreeNode child = new DefaultMutableTreeNode("Option" + i);
            model.insertNodeInto(child, root, i);
        }        
        jtSource = new JTree(model);        
        jscpSource = new JScrollPane(jtSource);

        jsppMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jscpTarget, jscpSource);
        jsppMain.setDividerLocation(150);
        add(jsppMain);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jtTarget.setDragEnabled(true);
        jtTarget.setTransferHandler(new TreeTransferHandler());
        jtTarget.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        jtSource.setDragEnabled(true);
        jtSource.setTransferHandler(new TreeTransferHandler());
        jtSource.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

        setBounds(0, 0, 300, 300);
        setLocationRelativeTo(null);
    }

    /**
     * This is the meat of my code. Everything else is just support code. 
     */
    private class TreeTransferHandler extends TransferHandler {

        private Popup tipWindow;
        private TreePath tipPath;
        private Timer tooltipTimer;

        public TreeTransferHandler() {
            // after showing the tip close it when the timer ends
            tooltipTimer = new Timer(100, new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    hideDropTooltip();
                }
            });
            tooltipTimer.setRepeats(false);
        }

        @Override
        public int getSourceActions(JComponent c) {
            return TransferHandler.MOVE;
        }

        @Override
        protected Transferable createTransferable(JComponent c) {
            JTree tree = (JTree) c;
            if (tree == jtTarget) {
                return null;
            }
            TreePath selectionPath = tree.getSelectionPath();
            if (selectionPath == null) {
                return null;
            }
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
            return new TreeTransferable(node, TreeTransferable.SOURCE);
        }

        @Override
        public boolean canImport(TransferHandler.TransferSupport support) {
            // this method is supposed to get called repeatedly during DnD
            // according to it's doc. On windows it is, but linux/mac only
            // call it on mouse moves (presumably).
            DefaultMutableTreeNode node;
            int src;
            try {
                node = (DefaultMutableTreeNode) support.getTransferable().getTransferData(TreeTransferable.NODE_FLAVOR);
                src = (Integer) support.getTransferable().getTransferData(TreeTransferable.SRC_FLAVOR);
            } catch (UnsupportedFlavorException ex) {
                updateDropTooltip(support, "Unsupported DnD object", false);
                return false;
            } catch (IOException ex) {
                updateDropTooltip(support, "Unsupported DnD object", false);
                return false;
            }

            JTree tree = (JTree) support.getComponent();
            TreePath path;
            if (support.isDrop()) {
                JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
                path = dl.getPath();
            } else {
                path = tree.getSelectionPath();
            }

            DefaultMutableTreeNode target = (DefaultMutableTreeNode) path.getLastPathComponent();            

            String nodeName = (String) node.getUserObject();
            String targetName = (String) target.getUserObject();

            if (targetName.endsWith(nodeName.substring(nodeName.length() - 1))) {
                updateDropTooltip(support, "Drop here to add option", true);
                return true;
            } else {
                updateDropTooltip(support, "Unsupported option", false);
                return false;
            }
        }

        @Override
        public boolean importData(TransferHandler.TransferSupport support) {
            return true;
        }

        private void hideDropTooltip() {
            tooltipTimer.stop();
            if (tipWindow != null) {
                tipWindow.hide();
                tipWindow = null;
            }
        }

        private void updateDropTooltip(TransferHandler.TransferSupport support, String message, boolean allowed) {
            if (message != null) {
                JTree tree = (JTree) support.getComponent();
                TreePath path;
                if (support.isDrop()) {
                    JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
                    path = dl.getPath();
                } else {
                    path = tree.getSelectionPath();
                }
                if (tipWindow != null) {
                    if (tipPath == null || !tipPath.equals(path)) {
                        hideDropTooltip();
                    }
                }
                if (tipWindow == null) {
                    tipPath = path;
                    JToolTip tip = tree.createToolTip();
                    tip.setTipText(
                            "<html>("
                            + (allowed
                            ? "yes"
                            : "no")
                            + ")" + message + "</html>");
                    PopupFactory popupFactory = PopupFactory.getSharedInstance();
                    Rectangle cellRect = tree.getPathBounds(path);
                    Point location = tree.getLocationOnScreen();
                    location.x += cellRect.x;
                    location.y += cellRect.y;
                    tipWindow = popupFactory.getPopup(tree, tip, location.x + cellRect.width, location.y);
                    tipWindow.show();
                    tooltipTimer.restart();
                } else {
                    tooltipTimer.restart();
                }
            } else {
                hideDropTooltip();
            }
        }
    }

    private static class TreeTransferable implements Transferable {
        public static final int SOURCE = 0;
        public static final int DESTINATION = 0;

        public static final DataFlavor NODE_FLAVOR = new DataFlavor(DefaultMutableTreeNode.class, "Tree Node");
        public static final DataFlavor SRC_FLAVOR = new DataFlavor(Integer.class, "Source");

        private DefaultMutableTreeNode node;
        private int src;

        private DataFlavor[] flavors = new DataFlavor[] {
            NODE_FLAVOR, SRC_FLAVOR
        };

        public TreeTransferable(DefaultMutableTreeNode node, int src) {
            this.node = node;
            this.src = src;
        }

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

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            for (DataFlavor flv : flavors) {
                if (flavor.equals(flv)) {
                    return true;
                }
            }
            return false;
        }

        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if (flavor.equals(NODE_FLAVOR)) {
                return node;
            } else if (flavor.equals(SRC_FLAVOR)) {
                return src;
            } else {
                throw new UnsupportedFlavorException(flavor);
            }
        }        
    }    
}

Edit01

我刚刚想到在拖动时听鼠标事件不起作用。所以我说我尝试的解决方法并不是一个真正的选择。

1 个答案:

答案 0 :(得分:1)

仍然不知道上述症状是否正常,但我在一个变通方法中入侵了。我更改了计时器的实现,以执行以下操作:

tooltipTimer = new Timer(100, new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        if (currTree.getMousePosition() == null) {
            hideDropTooltip();
        } else {
            tooltipTimer.restart();
        }                    
    }
});

其中currTree字段在updateDropTooltip(...)内设置。我现在检查鼠标是否已经使用getMousePosition()离开了我的目标树,如果没有,则重新启动计时器。似乎可以在我希望支持的所有平台上运行。