Vincent
Vincent

Reputation: 170

Drop nodes in a JTree at different nesting levels

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:

  1. Append a new grandchild to "child A", which will be "grandchild A3", or
  2. Insert a new "child B" between "child A" and "child B".

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

Answers (2)

Rangi Keen
Rangi Keen

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

gcooney
gcooney

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):

  • Interpret a drop directly on "A" as adding the item as the last child of "A"
  • Interpret a drop on a node as adding an element after the node(this is problematic if the node is not a leaf as the expected behavior is to add the element as a child)

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

Related Questions