1

JTree 間の DnD で問題や混乱が発生しています。のドキュメントを読み、次のTransferHandlerことを見つけた後

canImport( TransferHandler.TransferSupport サポート)

このメソッドは、ドラッグ アンド ドロップ操作中に繰り返し呼び出され、開発者がプロ​​パティを構成し、転送の受け入れ可否を返すことができるようにします。指定された TransferSupport (転送のすべての詳細を含む) によって表される転送が現時点で受け入れられることを示す true の戻り値と、転送を拒否する false の値を使用します。

以下のクラスを実装しました(長いコード、お詫びします)。アイデアは、ツリーの横に情報ツールチップを表示して、ドロップが可能または不可能な理由を説明することです。実装は繰り返し呼び出されることに依存しcanImportており、私の開発プラットフォーム (Windows) でうまく動作します。ただし、Linux/Mac でテストする場合、使用しているタイマーがリセットされないため、機能しません (canImportこれは、mouseMoved イベントでのみ呼び出されますが、これは論理的に聞こえます)。

これは通常の動作ですか、それとも Java 実装の 1 つ (または私のもの) のバグですか? 現在のウィンドウと同じようにコードを変更する方法に関する提案はありますか (ツリー コンポーネントに一時的にマウス リスナーを追加し、ツールチップを非表示にすることを考えています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