Reputation: 170
I am implementing drag and drop in a JTree. I want the user to be able to drop a node at different levels in the tree.
In the example below, imagine that the user inserts an item between "grandchild A2" and "child C":
root
child A
grandchild A1
grandchild A2
child C
grandchild C1
Now there are two options:
In SWT this is possible by moving the node a bit in the vertical direction. A horizontal line indicator will show at which nesting level the tree node is inserted.
Is this possible at all in Swing? I cannot seem to find information on this. The line indicator in Swing is always shown at one level only.
And if not, is there a workaround?
Upvotes: 4
Views: 1191
Reputation: 945
This is a bug in the java drop location handling and is difficult to resolve without modifying the JDK source. It is still unresolved, but there is also a related enhancement request with possible impractical workarounds that was closed as won't fix.
The only way I've been able to do this is by adding a dummy node as the last child of any container node. This is complicated and adds a row that may not be desirable, but allows the user to drop as the last child of a container node.
public class TreeDragAndDrop {
private static final int CONTAINER_ROW = 0;
private static final int PLACEHOLDER_ROW = 3;
private JScrollPane getContent() {
JTree tree = new JTree(getTreeModel()) {
// Overridden to prevent placeholder selection via the keyboad
@Override
public void setSelectionInterval(int index0, int index1) {
int index = index0;
// Probably would use a better check for placeholder row in production
// and use a while loop in case the new index is also a placeholder
if (index == PLACEHOLDER_ROW) {
int currentSelection = getSelectionCount() > 0 ? getSelectionRows()[0] : -1;
if (currentSelection < index) {
index++;
} else {
index--;
}
}
super.setSelectionInterval(index, index);
}
// Overridden to prevent placeholder selection via the mouse
@Override
public void setSelectionPath(TreePath path) {
if (path != null && getRowForPath(path) != PLACEHOLDER_ROW) {
super.setSelectionPath(path);
}
}
};
tree.setRootVisible(false);
tree.setDragEnabled(true);
tree.setDropMode(DropMode.INSERT);
tree.setTransferHandler(...);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.expandRow(CONTAINER_ROW);
return new JScrollPane(tree);
}
protected static TreeModel getTreeModel() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode a;
a = new DefaultMutableTreeNode("A");
root.add(a);
a.add(new DefaultMutableTreeNode("X"));
a.add(new DefaultMutableTreeNode("Y"));
a.add(new DefaultMutableTreeNode("")); // Placeholder node
root.add(new DefaultMutableTreeNode("B"));
root.add(new DefaultMutableTreeNode("C"));
return new DefaultTreeModel(root);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TreeDragAndDrop().getContent());
f.setSize(400, 400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
// TransferHandler code omitted
}
You may want to have a custom renderer to change the look of the placeholder rows (e.g. hide the icon, reduce the height (although you cannot make it 0 height), etc).
Upvotes: 1
Reputation: 1699
I don't believe it's possible to accomplish exactly the behavior you want using Swing's built in drag and drop.
A potential workaround is to set your drop mode to ON_OR_INSERT as follows: tree.setDropMode(DropMode.ON_OR_INSERT);
ON_OR_INSERT supports dropping either ON a node directly or between nodes. The INSERT part supports dropping between "A" and "B". You can then allow users to add a new grandchild of "A" after "A3" in one of two ways(or both):
If you need exactly the behavior you describe, you'd probably need to write a custom DropTarget for the table and draw the effects you want(line showing where the drop will happen) yourself. I'd recommend avoiding this if at all possible.
Upvotes: 2