1

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

canImport( TransferHandler.TransferSupport 支持

该方法在拖放操作过程中被重复调用,以允许开发者配置属性,并返回转账的可接受性;返回值 true 表示由给定 TransferSupport 表示的转移(包含转移的所有详细信息)在当前时间是可接受的,值为 false 拒绝转移。

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

这是正常行为还是其中一个 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);
            }
        }        
    }    
}

编辑01

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

4

1 回答 1

1

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

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

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

于 2013-06-11T14:00:09.243 回答