概要

JTree間でのドラッグ&ドロップによるノードの移動を行います。

サンプルコード

class TreeTransferHandler extends TransferHandler {
  private static final DataFlavor FLAVOR = new DataFlavor(
      DefaultMutableTreeNode[].class,
      "Array of DefaultMutableTreeNode");
  private JTree source;

  @Override protected Transferable createTransferable(JComponent c) {
    source = (JTree) c;
    TreePath[] paths = source.getSelectionPaths();
    DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[paths.length];
    for (int i = 0; i < paths.length; i++) {
      nodes[i] = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
    }
    return new Transferable() {
      @Override public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] {FLAVOR};
      }

      @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
        return Objects.equals(FLAVOR, flavor);
      }

      @Override public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException, IOException {
        if (isDataFlavorSupported(flavor)) {
          return nodes;
        } else {
          throw new UnsupportedFlavorException(flavor);
        }
      }
    };
  }

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

  @Override public boolean canImport(TransferHandler.TransferSupport support) {
    if (!support.isDrop()) {
      return false;
    }
    if (!support.isDataFlavorSupported(FLAVOR)) {
      return false;
    }
    JTree tree = (JTree) support.getComponent();
    return !tree.equals(source);
  }

  @Override public boolean importData(TransferHandler.TransferSupport support) {
    DefaultMutableTreeNode[] nodes = null;
    try {
      Transferable t = support.getTransferable();
      nodes = (DefaultMutableTreeNode[]) t.getTransferData(FLAVOR);
    } catch (UnsupportedFlavorException | IOException ex) {
      ex.printStackTrace();
    }
    TransferHandler.DropLocation tdl = support.getDropLocation();
    if (tdl instanceof JTree.DropLocation) {
      JTree.DropLocation dl = (JTree.DropLocation) tdl;
      int childIndex = dl.getChildIndex();
      TreePath dest = dl.getPath();
      DefaultMutableTreeNode parent =
        (DefaultMutableTreeNode) dest.getLastPathComponent();
      JTree tree = (JTree) support.getComponent();
      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
      int idx = childIndex < 0 ? parent.getChildCount() : childIndex;
      // DefaultTreeModel sm = (DefaultTreeModel) source.getModel();
      for (DefaultMutableTreeNode node : nodes) {
        // sm.removeNodeFromParent(node);
        // model.insertNodeInto(node, parent, idx++);
        DefaultMutableTreeNode clone = new DefaultMutableTreeNode(node.getUserObject());
        model.insertNodeInto(deepCopyTreeNode(node, clone), parent, idx++);
      }
      return true;
    }
    return false;
  }

  private static DefaultMutableTreeNode deepCopyTreeNode(
      DefaultMutableTreeNode src, DefaultMutableTreeNode tgt) {
    for (int i = 0; i < src.getChildCount(); i++) {
      DefaultMutableTreeNode node  = (DefaultMutableTreeNode) src.getChildAt(i);
      DefaultMutableTreeNode clone = new DefaultMutableTreeNode(node.getUserObject());
      tgt.add(clone);
      if (!node.isLeaf()) {
        deepCopyTreeNode(node, clone);
      }
    }
    return tgt;
  }

  @Override protected void exportDone(
      JComponent source, Transferable data, int action) {
    if (action == TransferHandler.MOVE) {
      JTree tree = (JTree) source;
      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
      for (TreePath path : tree.getSelectionPaths()) {
        model.removeNodeFromParent((MutableTreeNode) path.getLastPathComponent());
      }
    }
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、JTree間でのノード移動を可能にするTransferHandlerを作成して、JTree#setTransferHandler(...)で設定しています。

  • TransferHandler#importData(...)メソッド内でドラッグして移動するDefaultMutableTreeNodeをドラッグ元のDefaultTreeModelから削除せずにDefaultTreeModel#insertNodeInto(...)メソッドでドロップ先のJTreeDefaultTreeModelに挿入すると、ノードの親子関係がおかしくなってドラッグ元のJTreeからDefaultTreeModel#removeNodeFromParent(...)メソッドでノードを削除できなくなる
    • DefaultTreeModel.reload()ですべて再評価すると正常に表示されるが、ノードがすべて折り畳まれてしまう
    • 回避方法は移動するDefaultMutableTreeNodeのクローン(親ノードは未設定)を作成してドロップ元に追加し、ドラッグ元からの削除はTransferHandler#exportDone(...)で実行する
  • 制限:
    • 同一JTree内でのノード入れ替えには未対応
    • TreeSelectionModel.SINGLE_TREE_SELECTIONで選択、移動できるのは1ノードのみに制限
    • TransferHandler.MOVEで移動のみに対応
    • ルートノードは、JTree#setRootVisible(false)で非表示にして移動禁止

参考リンク

コメント