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