DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Data Engineering
  3. Databases
  4. Drag & Drop with the NetBeans Nodes API

Drag & Drop with the NetBeans Nodes API

Toni Epple user avatar by
Toni Epple
·
Feb. 13, 12 · Interview
Like (1)
Save
Tweet
Share
11.98K Views

Join the DZone community and get the full member experience.

Join For Free

With the Nodes API, the NetBeans Platform provides a presentation layer for your domain objects. You can easily separate UI-specific features like an internationalizable display name, actions, an icon, etc, for your domain object.

This is done via wrapping your domain objects with Nodes. Nodes are then displayed in specialized "explorer views", which are typically Swing components, but could be anything else, such as JavaFX components. Building your Node hierarchy is easy and it gives you a lot of features out of the box.

There are many tutorials out there showing you how you can create your own Node hierarchies, add actions to them, etc.

One thing that is not straightforward, though, is how to enable drag and drop functionality. A typical use case is that you have some kind of "Folder" Nodes that accept drops and some kind of "Leaf" Nodes that can be dragged from one folder to another.

In this article, you'll learn how to do that via an example.

Let's assume you've got a domain object like this:

public class Customer  {

    static int ids = 0;
    String name;
    int id;

    public Customer(String name) {
        this.name = name;
        id = ids++;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Let's create a DataFlavor that our accepting Node can later check to test if it's OK to accept the drop:

public class CustomerFlavor extends DataFlavor{

    public static final DataFlavor CUSTOMER_FLAVOR = new CustomerFlavor();

    public CustomerFlavor() {
         super(Customer.class, "Customer");
    }

} 

Now we need some Nodes. So we first create a ChildFactory that can produce CustomerNodes:

import java.awt.datatransfer.Transferable;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.util.lookup.Lookups;

class CustomerFactory extends ChildFactory {

    List customers;

    public CustomerFactory(List customerNames) {
        this.customers = customerNames;
    }

    public void addCustomer(Customer c) {
        customers.add(c);
        refresh(true);
    }

    private void removeChild(Customer c) {
        customers.remove(c);
        refresh(true);
    }

    @Override
    protected boolean createKeys(List list) {
        list.addAll(customers);
        return true;
    }

    @Override
    protected Node createNodeForKey(Customer name) {
        Node node = new CustomerNode(Children.LEAF, Lookups.fixed(name));
        return node;
    }

    public void reorder(int[] perm) {
        Customer[] reordered = new Customer[customers.size()];
        for (int i = 0; i < perm.length; i++) {
            int j = perm[i];

            Customer c = customers.get(i);
            reordered[j] = c;
        }
        customers.clear();
        customers.addAll(Arrays.asList(reordered));
        refresh(true);

    }

    private class CustomerNode extends AbstractNode
    {

        public CustomerNode(Children children, Lookup lookup) {
            super(children, lookup);
        }

        @Override
        public boolean canCut() {
            return true;
        }

        @Override
        public boolean canCopy() {
            return true;
        }

        @Override
        public String getDisplayName() {
            return getLookup().lookup(Customer.class).getName();
        }

        @Override
        public boolean canDestroy() {
            return true;
        }

        @Override
        public void destroy() throws IOException {
            removeChild(getLookup().lookup(Customer.class));
            refresh(true);
        }

        @Override
        public Transferable clipboardCut() throws IOException {
            Transferable deflt = super.clipboardCut();
            ExTransferable added = ExTransferable.create(deflt);
            added.put(new ExTransferable.Single(CustomerFlavor.CUSTOMER_FLAVOR) {
                protected Customer getData() {
                    return getLookup().lookup(Customer.class);
                }
            });
            return added;
        }
    }
}

The CustomerFactory manages an initial List of Customers. It provides additional methods for adding, removing and reordering Customers. Those will come in very handy later. Other than that it's very simple.

The CustomerNode overrides canCopy() and canCut() to return true, to indicate that we can remove the Node from its parent. When a user selects our Node, the cut Action will be invoked and AbstractNode.clipboardCut() will be called. By default, it creates a transferable supporting only one flavor, so we'll override it to add our own Flavor. We can use that Flavor later to get hold of the actual Customer Object.

When we Drop our Node in its new Folder, we want the original Node to be destroyed. So we need to return true from the canDestroy() method and our Node needs to know how to destroy itself.

This is where our removeChild() method comes in handy. Inside this method, the Customer is removed from the list and Childfactory.refresh(true) is called, telling our Children wrapper and the view to update and call Childfactory.createKeys() again.

That's all we need to do in order for our Nodes to be drag enabled.

Now let's have a look at our container Nodes:

import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Index;
import org.openide.nodes.Node;
import org.openide.nodes.NodeTransfer;
import org.openide.util.Exceptions;
import org.openide.util.datatransfer.PasteType;

/**
 *
 * @author eppleton
 */
public class DNDContainerNode extends AbstractNode {

    CustomerFactory customers;

    public DNDContainerNode(final CustomerFactory customers) {
        super(Children.create(customers, true));
        this.customers = customers;
        getCookieSet().add(new Index.Support() {

            @Override
            public Node[] getNodes() {
                return getChildren().getNodes();
            }

            @Override
            public int getNodesCount() {
                return getNodes().length;
            }

            @Override
            public void reorder(int[] perm) {
                customers.reorder(perm);
            }
        });
    }

    @Override
    public PasteType getDropType(final Transferable t, int arg1, int arg2) {

        if (t.isDataFlavorSupported(CustomerFlavor.CUSTOMER_FLAVOR)) {

            return new PasteType() {

                @Override
                public Transferable paste() throws IOException {
                    try {
                        customers.addCustomer((Customer) t.getTransferData(CustomerFlavor.CUSTOMER_FLAVOR));
                        final Node node = NodeTransfer.node(t, NodeTransfer.DND_MOVE + NodeTransfer.CLIPBOARD_CUT);
                        if (node != null) {
                            node.destroy();
                        }
                    } catch (UnsupportedFlavorException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                    return null;
                }
            };
        } else {
            return null;
        }
    }
}

The important part is the "getDropType" method. Here we can check for our DataFlavor. In case it's supported, we'll return a new PasteType. The NodeTransfer class is the central part will give us the Node that is dragged with no further effort. We'll only use it here, because we want to cut the Node from it's original. We could also use it's Lookup to check if it has a Customer instead of the DataFlavor to accept the drop. That would simplify the code a bit, but I slightly prefer to use the Flavor to check this and get hold of the Customer (even though it's not typesafe), because other Nodes not intended to be dragged and dropped might have a Customer Object too.

The other important part of this class is the Index.Support in the CookieSet. This allows us to drop the Node at a specific spot. Remove it, and you'll see the difference. The implementation delegates to CustomerFactory.reorder in order to do the job.

Now the only thing to do is to test our code in a new TopComponent. To the Constructor add this:

        setLayout(new BorderLayout());
        add(new BeanTreeView(), BorderLayout.CENTER);

        ArrayList names = new ArrayList();
        names.add(new Customer("Tom"));
        names.add(new Customer("Dick"));
        names.add(new Customer("Harry"));
        AbstractNode root1 = new DNDContainerNode(new CustomerFactory(names));
        root1.setDisplayName("D&D 1");

        ArrayList names2 = new ArrayList();
        names2.add(new Customer("Thomas"));
        names2.add(new Customer("Richard"));
        names2.add(new Customer("Harald"));
        AbstractNode root2 = new DNDContainerNode(new CustomerFactory(names2));
        root2.setDisplayName("D&D 2");

        AbstractNode root = new AbstractNode(new Children.Array());
        root.setDisplayName("Root");
        root.getChildren().add(new Node[]{root1, root2});
        em.setRootContext(root); 

That's it, you can now freely reorder your nodes.

 

API Drops (app) NetBeans

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Explainer: Building High Performing Data Product Platform
  • How to Quickly Build an Audio Editor With UI
  • Cloud Native London Meetup: 3 Pitfalls Everyone Should Avoid With Cloud Data
  • Data Mesh vs. Data Fabric: A Tale of Two New Data Paradigms

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: