JTreeのノードをドラッグ&ドロップで並べ替える
Total: 378
, Today: 3
, Yesterday: 2
Posted by aterai at
Last-modified:
概要
JTree
のノードをドラッグ&ドロップで並べ替え可能なTransferHandler
を作成します。
Screenshot
Advertisement
サンプルコード
class TreeTransferHandler extends TransferHandler {
private final DataFlavor nodesFlavor = new DataFlavor(
List.class, "List of TreeNode");
@Override public int getSourceActions(JComponent c) {
return c instanceof JTree
&& TreeUtils.canStartDrag((JTree) c) ? COPY_OR_MOVE : NONE;
}
@Override protected Transferable createTransferable(JComponent c) {
Transferable transferable = null;
if (c instanceof JTree && ((JTree) c).getSelectionPaths() != null) {
List<MutableTreeNode> copies = new ArrayList<>();
Arrays.stream(((JTree) c).getSelectionPaths()).forEach(path -> {
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) path.getLastPathComponent();
DefaultMutableTreeNode clone =
new DefaultMutableTreeNode(node.getUserObject());
copies.add(TreeUtils.deepCopy(node, clone));
});
transferable = new Transferable() {
@Override public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] {nodesFlavor};
}
@Override public boolean isDataFlavorSupported(
DataFlavor flavor) {
return Objects.equals(nodesFlavor, flavor);
}
@Override public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException {
if (isDataFlavorSupported(flavor)) {
return copies;
} else {
throw new UnsupportedFlavorException(flavor);
}
}
};
}
return transferable;
}
@Override public boolean canImport(TransferSupport support) {
DropLocation dl = support.getDropLocation();
Component c = support.getComponent();
return support.isDrop()
&& support.isDataFlavorSupported(nodesFlavor)
&& c instanceof JTree
&& dl instanceof JTree.DropLocation
&& TreeUtils.canImportDropLocation(
(JTree) c, (JTree.DropLocation) dl);
}
@Override public boolean importData(TransferSupport support) {
Component c = support.getComponent();
DropLocation dl = support.getDropLocation();
Transferable transferable = support.getTransferable();
return canImport(support)
&& c instanceof JTree
&& dl instanceof JTree.DropLocation
&& insertNode((JTree) c, (JTree.DropLocation) dl, transferable);
}
private boolean insertNode(
JTree tree, JTree.DropLocation dl, Transferable transferable) {
TreePath path = dl.getPath();
Object p = path.getLastPathComponent();
TreeModel m = tree.getModel();
List<?> nodes = getTransferData(transferable);
if (p instanceof MutableTreeNode && m instanceof DefaultTreeModel) {
MutableTreeNode parent = (MutableTreeNode) p;
DefaultTreeModel model = (DefaultTreeModel) m;
int childIndex = dl.getChildIndex();
AtomicInteger index = new AtomicInteger(
getDropIndex(parent, childIndex));
nodes.stream()
.filter(MutableTreeNode.class::isInstance)
.map(MutableTreeNode.class::cast)
.forEach(n -> model.insertNodeInto(
n, parent, index.getAndIncrement()));
}
return !nodes.isEmpty();
}
private static int getDropIndex(
MutableTreeNode parent, int childIndex) {
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
return index;
}
private List<?> getTransferData(Transferable t) {
List<?> nodes;
try {
nodes = (List<?>) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException | IOException ex) {
nodes = Collections.emptyList();
}
return nodes;
}
@Override protected void exportDone(
JComponent src, Transferable data, int action) {
if (src instanceof JTree && (action & MOVE) == MOVE) {
cleanup((JTree) src);
}
}
private void cleanup(JTree tree) {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths != null) {
for (TreePath path : selectionPaths) {
model.removeNodeFromParent(
(MutableTreeNode) path.getLastPathComponent());
}
}
}
}
View in GitHub: Java, Kotlin解説
TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
を設定して複数の連続パス(選択範囲の項目は連続している)を選択可能に変更TransferHandler.getCutAction().getValue(Action.NAME)
キーで実行するアクションを空アクションに変更して、Ctrl-Xキーでのノードカットを無効化TransferHandler#getSourceActions(...)
をオーバーライドしてルートノード(ノードレベルが0
)ではない、かつ選択されているノードはすべて兄弟ノード(ノードレベルが同じ)となる場合はドラッグ開始可能に設定
@Override public int getSourceActions(JComponent c) {
return c instanceof JTree && TreeUtils.canStartDrag((JTree) c) ? MOVE : NONE;
}
public static boolean canStartDrag(JTree tree) {
TreePath[] paths = tree.getSelectionPaths();
return paths != null && canStartDragPaths(paths);
}
public static boolean canStartDragPaths(TreePath... paths) {
return Arrays.stream(paths)
.map(TreePath::getLastPathComponent)
.filter(DefaultMutableTreeNode.class::isInstance)
.map(DefaultMutableTreeNode.class::cast)
.map(DefaultMutableTreeNode::getLevel)
.distinct()
.filter(level -> level != 0)
.count() == 1;
}
TransferHandler#canImport(...)
をオーバーライドしてドロップ先がドラッグ元の子孫ノードではない、かつドラッグ元の選択ノード範囲外の場合ドロップ可能に設定
@Override public boolean canImport(TransferSupport support) {
DropLocation dl = support.getDropLocation();
Component c = support.getComponent();
return support.isDrop()
&& support.isDataFlavorSupported(nodesFlavor)
&& c instanceof JTree
&& dl instanceof JTree.DropLocation
&& TreeUtils.canImportDropLocation(
(JTree) c, (JTree.DropLocation) dl);
}
public static boolean canImportDropLocation(
JTree tree, JTree.DropLocation dl) {
// Do not allow drop to descendant and drag-source selections
// int dropRow = tree.getRowForPath(dl.getPath());
Point pt = dl.getDropPoint();
int dropRow = tree.getRowForLocation(pt.x, pt.y);
int[] selRows = tree.getSelectionRows();
return selRows != null && IntStream
.of(selRows)
.noneMatch(r -> r == dropRow || isDescendant(tree, r, dropRow));
}
private static boolean isDescendant(JTree tree, int selRow, int dropRow) {
Object node = tree.getPathForRow(selRow).getLastPathComponent();
return node instanceof DefaultMutableTreeNode
&& isDescendant2(tree, dropRow, (DefaultMutableTreeNode) node);
}
private static boolean isDescendant2(
JTree tree, int dropRow, DefaultMutableTreeNode node) {
Enumeration<?> e = node.depthFirstEnumeration();
return Collections.list(e)
.stream()
.filter(DefaultMutableTreeNode.class::isInstance)
.map(DefaultMutableTreeNode.class::cast)
.map(DefaultMutableTreeNode::getPath)
.map(TreePath::new)
.map(tree::getRowForPath)
.anyMatch(row -> row == dropRow);
}
TransferHandler#importData(...)
をオーバーライドしてDefaultTreeModel#insertNodeInto()
でドラッグされた選択ノードをコピー- JTreeのノードをドラッグ&ドロップ
JTree.DropLocation#getChildIndex()
が-1
になる場合はノード上にドロップ、それ以外の場合はノード間にドロップされる
@Override public boolean importData(TransferSupport support) {
Component c = support.getComponent();
DropLocation dl = support.getDropLocation();
Transferable transferable = support.getTransferable();
return canImport(support)
&& c instanceof JTree
&& dl instanceof JTree.DropLocation
&& insertNode((JTree) c, (JTree.DropLocation) dl, transferable);
}
private boolean insertNode(
JTree tree, JTree.DropLocation dl, Transferable transferable) {
TreePath path = dl.getPath();
Object p = path.getLastPathComponent();
TreeModel m = tree.getModel();
List<?> nodes = getTransferData(transferable);
if (p instanceof MutableTreeNode && m instanceof DefaultTreeModel) {
MutableTreeNode parent = (MutableTreeNode) p;
DefaultTreeModel model = (DefaultTreeModel) m;
int childIndex = dl.getChildIndex();
AtomicInteger index = new AtomicInteger(
getDropIndex(parent, childIndex));
nodes.stream()
.filter(MutableTreeNode.class::isInstance)
.map(MutableTreeNode.class::cast)
.forEach(n -> model.insertNodeInto(
n, parent, index.getAndIncrement()));
}
return !nodes.isEmpty();
}
private static int getDropIndex(MutableTreeNode parent, int childIndex) {
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
return index;
}
private List<?> getTransferData(Transferable t) {
List<?> nodes;
try {
nodes = (List<?>) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException | IOException ex) {
nodes = Collections.emptyList();
}
return nodes;
}
TransferHandler#exportDone(...)
をオーバーライドしてドロップアクションがMOVE
の場合はドロップ元の選択ノードを削除- Ctrlキーが押下されてドロップアクションが
COPY
の場合はなにも実行しない
- Ctrlキーが押下されてドロップアクションが
@Override protected void exportDone(
JComponent src, Transferable data, int action) {
if (src instanceof JTree && (action & MOVE) == MOVE) {
cleanup((JTree) src);
}
}
private void cleanup(JTree tree) {
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
TreePath[] selectionPaths = tree.getSelectionPaths();
if (selectionPaths != null) {
for (TreePath path : selectionPaths) {
model.removeNodeFromParent(
(MutableTreeNode) path.getLastPathComponent());
}
}
}
参考リンク
- JTree - drag and drop inside one tree - Java 1.6 (Swing / AWT / SWT forum at Coderanch)
- java - Drag and Drop nodes in JTree - Stack Overflow
- JTreeのノードをドラッグ&ドロップ