NetBeans Selection Management Tutorial II - Using Nodes

Last reviewed on 2025-11-04

Part 2 of the selection management series. It builds up on part 1, have its source code at hand as we will be modify it here.

This tutorial introduces the Nodes API for granular selection management beyond component-level handling. While custom Lookup implementations are possible, the Nodes API provides significant advantages with minimal code.

For troubleshooting, you can download the completed source for reference.

Introduction to the Nodes API

The Nodes API provides two key benefits:

The Presentation Layer decouples data models from UI components, enabling multiple view representations of the same data.

The Explorer API provides ready-made components (trees, lists, tree tables) that render Node hierarchies.

A Node is a generic hierarchical object with:

  • Children - Child nodes that can also be displayed

  • Actions - Context menu actions

  • Display Name - Localized display text

  • Icon - Visual representation

Nodes fire change events that automatically update Explorer UI components. They also have a getLookup() method that can be proxied by the TopComponent displaying the node. The IDE’s Projects tab demonstrates this - it’s a TopComponent that proxies selected nodes' Lookup objects, just like Utilities.actionsGlobalContext() proxies focused components.

Explorer components handle this proxying automatically. Your existing MyViewer component from part 1 will respond to Explorer selection changes without modification.

Creating an Explorer View

Modify the MyEditor module from part 1 to use the Explorer API.

  1. Right-click My Editor project → Properties → Libraries → Add Dependency. Search for "BeanTreeView" and select Explorer & Property Sheet API.

    selection 2 nb27 explorer1

    Click OK to add the dependency.

  2. Open MyEditorTopComponent.java, switch to form designer and delete all components (both text fields and button).

  3. Replace the constructor with:

    public MyEditorTopComponent() {
        initComponents();
        Event obj = new Event();
        associateLookup(new AbstractLookup(content));
    
        setLayout(new BorderLayout());
        add(new BeanTreeView(), BorderLayout.CENTER);
    
        setDisplayName("MyEditor " + obj.getIndex());
    }

    BeanTreeView is a tree-based view with built-in popup menus and search. Press Ctrl+Shift+I to import it.

  4. Remove the unused updateContent() method.

  5. Explorer components locate their data source by searching up the component hierarchy for an ExplorerManager.Provider. Update the class signature MyEditorTopComponent to implement that interface:

    public class MyEditorTopComponent extends TopComponent implements ExplorerManager.Provider {

    Use Alt+Enter to implement the required method:

    private final ExplorerManager mgr = new ExplorerManager();
    
    @Override
    public ExplorerManager getExplorerManager() {
        return mgr;
    }
  6. Add the following line to the constructor to set the root node:

    mgr.setRootContext(new AbstractNode(Children.create(new EventChildFactory(), true)));

    The true parameter enables asynchronous child creation.

  7. Add the Nodes API dependency: Right-click My Editor project → Properties → Libraries → Add Dependency. Search for "AbstractNode" and select Nodes API.

  8. Press Ctrl+Shift+I to fix imports. EventChildFactory does not existe yet, so it will show as unresolved - we’ll create it next.

Implementing Nodes and Node Children

AbstractNode is a utility implementation of Node that saves you from implementing the interface directly. It is actually not an abstract class! You provide a Children object, set display properties, and get a working Node without subclassing.

We now need to implement EventChildFactory, so that there are subnodes underneath the initial node:

  1. Right-click the org.myorg.myeditor package and use New > Java Class. Name the new class "EventChildFactory".

  2. Modify the signature of the class so it extends ChildFactory:

    public class EventChildFactory extends ChildFactory<Event> {

    Press Ctrl+Shift+I to Fix Imports.

  3. Position the cursor on the class signature and use Alt+Enter to implement abstract methods. This adds the createKeys method where you’ll create keys for child nodes. The children are created lazily - only when the user expands the parent node in the view. Implement it as follows:

    @Override
    protected boolean createKeys(List<Event> list) {
        Event[] objs = new Event[5];
        for (int i = 0; i < objs.length; i++) {
            objs[i] = new Event();
        }
        list.addAll(Arrays.asList(objs));
        return true;
    }

    ChildFactory creates child nodes on-demand. For each key in the list, createNodeForKey() will be called.

  4. Now you need to implement the code that actually creates Node objects for all of these. Implement createNodeForKey as follows:

    @Override
    protected Node createNodeForKey(Event key) {
        Node result = new AbstractNode(
                Children.create(new EventChildFactory(), true),
                Lookups.singleton(key));
        result.setDisplayName(key.toString());
        return result;
    }

    Each Node gets the Event in its Lookup. When selected, this Lookup is proxied through the TopComponent to the global context, making the Event available to other components.

  5. Wire up the explorer manager to the TopComponent’s lookup. Delete this line from the class:

    private final InstanceContent content = new InstanceContent();
  6. Update the MyEditor constructor to:

    public MyEditor() {
        initComponents();
        Event obj = new Event();
        associateLookup(ExplorerUtils.createLookup(mgr, getActionMap()));
    
        setLayout(new BorderLayout());
        add(new BeanTreeView(), BorderLayout.CENTER);
    
        setDisplayName("MyEditor " + obj.getIndex());
        mgr.setRootContext(new AbstractNode(Children.create(new EventChildFactory(), true)));
    }

    ExplorerUtils.createLookup() automatically proxies the selected node’s Lookup. Press Ctrl+Shift+I to fix the imports.

Running the Tutorial

Passing a new EventChildFactory to each AbstractNode creates an infinitely deep tree - each node has five children, created on-demand when expanded.

Right-click `EventManager` → Clean and Build, then Run the application. You will be able to browse the Events:

selection 2 nb27 result1

Open the property sheet with Window → IDE Tools → Properties to see the viewer and property sheet update with each selected node’s Event:

selection 2 nb27 result2

Exploring Explorer

Experiment with other Explorer components by replacing new BeanTreeView() in the MyEditor constructor with:

  • OutlineView - Tree-table with tree as leftmost column:

    selection 2 nb27 result3
  • IconView - Icon-based layout similar to Windows Explorer:

    selection 2 nb27 result4
  • ListView - Displays nodes in a JList:

    selection 2 nb27 result5
  • ChoiceView - Combo-box view (typically used with other components):

    selection 2 nb27 result6
  • MenuView - JButton that pops up a menu:

    selection 2 nb27 result7

Handling Multi-Selection

BeanTreeView supports multi-selection. Update the viewer component to display all selected nodes:

  1. Open org.myorg.myviewer.MyViewerTopComponent in the editor.

  2. Replace the resultChanged() method with:

    @Override
    public void resultChanged(LookupEvent lookupEvent) {
        Collection<? extends Event> allEvents = result.allInstances();
        if (!allEvents.isEmpty()) {
            StringBuilder text1 = new StringBuilder();
            StringBuilder text2 = new StringBuilder();
            for (Iterator i = allEvents.iterator(); i.hasNext();) {
                Event o = (Event) i.next();
                text1.append(o.getIndex());
                text2.append(o.getDate().toString());
                if (i.hasNext()) {
                    text1.append(',');
                    text2.append(',');
                }
            }
            jLabel1.setText(text1.toString());
            jLabel2.setText(text2.toString());
        } else {
            jLabel1.setText("[no selection]");
            jLabel2.setText("");
        }
    }
  3. Clean, Build, and Run. The ExplorerUtils lookup correctly handles multiple selections:

    selection 2 nb27 multiselect1

Review of Concepts

Key concepts covered:

  • A Lookup is a type-safe map where classes are keys and instances are values. Objects can "swim in and out" with change notifications.

  • Utilities.actionsGlobalContext() proxies the Lookup of the focused TopComponent.

  • A Node presents an object with its own Lookup, displayable in Explorer components.

  • ExplorerUtils.createLookup() automatically proxies the Lookup of selected Node(s) in Explorer components.

Next Steps

You now have a view that displays Node`s exposing model objects (`Event). The next tutorial covers enhancing nodes with actions, properties, and display customization.