NetBeans Selection Management Tutorial I-Using a TopComponent’s Lookup

Last reviewed on 2025-10-28

This tutorial shows how to build a NetBeans Platform application with losely coupled modules: one that provides a selected object, and others that update components as the selection changes.

This tutorial is part 1 of a series and covers using a TopComponent’s Lookup. Part 2 explains selection for a single item (for example, one element in a tree view).

Introduction to Selection

Selection is crucial for complex applications. NetBeans has two basic concepts of selection:

This tutorial covers only the Lookup portion of selection.

Selection allows for context-sensitive actions (enabled/disabled depending on selection), and palette windows such as the Property Sheet or Navigator components in the IDE, which display details of whatever is selected.

Each TopComponent exposes a Lookup — a Map from class to objects of that type, that other components can query. This permits decoupling the components that provide some objects and the components that consume them, so they can be implemented in separate modules. New editors for existing objects can be provided without changing the rest\ of the system.

Creating the NetBeans Platform Application Project

The example used in this tutorial will contain three modules constituting a NetBeans Platform application, as illustrated below:

selection 1 nb27 module structure

Lets start by creating the NetBeans Platform application that will contain all three modules.

  1. Go to File > New Project. Under Categories, expand Java with Ant and select NetBeans Modules. Then select NetBeans Platform Application as Project type:

    selection 1 nb27 new project1
  2. In the Name and Location panel, use EventManager as the Project Name. Choose the location for the project on your computer:

    selection 1 nb27 new project2

    You now have a NetBeans Platform application project, which is the container for the modules that you will be creating throughout this tutorial:

    selection 1 nb27 new project3

    Via the configuration files shown in the screenshot above, the application has access to a window system and an action system. The configuration files also set up the application to be built via the Ant build system.

    You will be adding three modules to the application. The first module will provide an API class, while the other two will both depend on that API module. That will ensure that the other two modules do not depend on each other, making them loosely coupled.

  3. Go to File > New Project again. If not already open, under Categories, expand Java with Ant, and select NetBeans Modules. Under Projects, select Module:

    selection 1 nb27 new project4
    You could also do this by expanding the EventManager project node, right-click the Modules node, and then choose Add New.
  4. In the Name and Location panel, use MyAPI as Project Name. Now look at the Project Location field and the Project Folder field. The default behavior is to create the module inside the directory of the EventManager application, which means that the module’s sources will be organized within the folder where the application is defined. That is the standard way to organize the source code in NetBeans Platform application projects.

    selection 1 nb27 new project5
  5. In the Basic Module Configuration panel, set the following values:

    • Code Name Base. The code name base is a string that uniquely identifies a module. By convention, the value of the code name base is the main package of the module. In this case, set the code name base to org.myorg.myapi. That will be the string used to identify the module by other modules that will need to make use of code within this module.

    • Module Display Name. Set the module display name to My API. That is the text you will see displayed for the module in the Projects window in the IDE.

    • Localizing Bundle. Leave the location of the localizing bundle with the default value, so that localization key/values will be stored in the main package, with the name org/myorg/myapi.

    • Generate OSGi Bundle. Leave this unchecked to use the NetBeans module system. There are two module systems are supported by the NetBeans Platform, the NetBeans module system and the OSGi framework. You can use OSGi if required to meet your business requirements, although we use the NetBeans module system in this tutorial series.

      The result should be as follows:

    selection 1 nb27 new project6
  6. Create another two modules (repeat steps 3–5), using the following parameters:

    • Project Name: MyEditor, Code Name Base: org.myorg.myeditor, Module Display Name: My Editor

    • Project Name: MyViewer, Code Name Base: org.myorg.myviewer, Module Display Name: My Viewer

      At the end of this step, the structure of the application should be as follows:

      selection 1 nb27 new project7

      We split the project into three modules so the viewer and editor depend only on a shared API. This makes the viewer and editor losely coupled, which has many advantages.

Creating an API and Setting Up Dependencies

Now we’ll create a simple API class. In real applications, this API might represent files or other data. For this tutorial, we’ll create a simple "Event" object with a unique ID and a date.

  1. Right click the org.myorg.myapi package and choose New > Java Class.

    selection 1 nb27 api1

    Name the class Event:

    selection 1 nb27 api2

    Replace the default code with the following:

    package org.myorg.myapi;
    
    import java.time.ZonedDateTime;
    
    public final class Event {
    
        private final ZonedDateTime date = ZonedDateTime.now();
        private static int count = 0;
        private final int index;
    
        public Event() {
            index = count++;
        }
    
        public ZonedDateTime getDate() {
            return date;
        }
    
        public int getIndex() {
            return index;
        }
    
        @Override
        public String toString() {
            return index + " - " + date.toString();
        }
    }

    This is all the code for this module. Each time you create a new Event, a counter is incremented to give each event a unique ID.

  2. Next, make your API module export the org.myorg.myapi package so other modules can see the Event class. By default, all packages are hidden from other modules. Right click the My API project and choose Properties. In the API Versioning page, check the checkbox for org.myorg.myapi in the Public Packages list:

    selection 1 nb27 api3

    Now expand the Important Files node of the My API project and open the Project Metadata file (named project.xml on disk). Notice this section was added when you clicked OK:

    <public-packages>
        <package>org.myorg.myapi</package>
    </public-packages>

    When you compile the module, this information from project.xml is added to the module’s manifest file.

  3. Now set up dependencies between your modules. Both My Editor and My Viewer will use the Event class, so they need to depend on the API module. Right-click the My Editor project and choose Properties. Select the Libraries tab.

    selection 1 nb27 api4

    Click Add Dependency…​ and type MyAPI in the filter. You’ll see the module appear:

    selection 1 nb27 api5
    the Cluster combo-box can be used for filtering as well.

    You’ll see the module listed under Module Dependencies. Click OK to close the dialog.

    Open the Project Metadata file in the Important Files node of the My Editor module. You’ll see this section was added:

<module-dependencies>
    <dependency>
        <code-name-base>org.myorg.myapi</code-name-base>
        <build-prerequisite/>
        <compile-dependency/>
        <run-dependency>
            <specification-version>1.0</specification-version>
        </run-dependency>
    </dependency>
</module-dependencies>

Notice the code name base identifies the MyAPI module. When you compile the module, this information from project.xml goes into the module’s manifest file.

Add the same dependency for the My Viewer module. Right-click the My Viewer project and select Properties. When finished, the module dependencies look like this in the Project window:

selection 1 nb27 api7

Creating the Viewer Component

Now you’ll create a singleton component that tracks if there’s an Event in the global selection. If there is, it will display information about it. This is commonly used for master/detail views.

Generating a Singleton TopComponent

A "singleton component" is like the Projects window in NetBeans IDE - there’s only one of them in the system. The Window wizard generates all the code needed for a singleton component. You just need to provide the contents.

  1. Right click the org.myorg.myviewer package and choose New > Window to create a TopComponent for this module.

    selection 1 nb27 viewer1
  2. On the "Basic Settings" wizard, select explorer as the window location and check "Open on Application Start" to open the window at startup:

    selection 1 nb27 viewer3
  3. On the "Name, Icon and Location" page, set MyViewer as the class name prefix:

    selection 1 nb27 viewer4
  4. Click Finish and you should see the following:

    selection 1 nb27 viewer5

    You now have a skeleton TopComponent called MyViewerTopComponent. The wizard created the Java class and added the required module dependencies (under Libraries).

  5. Open the MyViewerTopComponent file and click the Source tab. The annotations at the top of the file register MyViewerTopComponent in the layer file and create an Action for opening it from the Window menu:

    @ConvertAsProperties(
        dtd = "-//org.myorg.myviewer//MyViewer//EN",
        autostore = false
    )
    @TopComponent.Description(
        preferredID = "MyViewerTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE",
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
    )
    @TopComponent.Registration(mode = "explorer", openAtStartup = true)
    @ActionID(category = "Window", id = "org.myorg.myviewer.MyViewerTopComponent")
    @ActionReference(path = "Menu/Window" /*, position = 333 */)
    @TopComponent.OpenActionRegistration(
        displayName = "#CTL_MyViewerAction",
        preferredID = "MyViewerTopComponent"
    )
    @Messages({
        "CTL_MyViewerAction=MyViewer",
        "CTL_MyViewerTopComponent=MyViewer Window",
        "HINT_MyViewerTopComponent=This is a MyViewer window"
    })

Creating a Context Sensitive TopComponent

Click the Design tab to open the "Matisse" GUI Builder (the form editor). You’ll add two labels to display information about the selected Event.

  1. Drag two Labels (javax.swing.JLabel) from the Palette to the form, one below the other:

    selection 1 nb27 viewer6

    Select the first label and press F2, change the text to [Nothing selected] as shown above.

  2. Click the Source button to switch to the code editor. Change the class signature so MyViewerTopComponent implements LookupListener:

    public final class MyViewerTopComponent extends TopComponent implements LookupListener

    Right-click in the editor and choose Fix Imports to import LookupListener.

    A lightbulb icon will appear in the editor margin. Click it to see the popup:

    selection 1 nb27 viewer8

    Select the text "Implement all abstract methods" on the context menu that appears. Now you have a class that implements LookupListener (it implements the resultChanged method). You need something to listen to.

    There’s a global Lookup object that proxies the Lookup of whatever component has focus - you can get it by calling Utilities.actionsGlobalContext(). Instead of tracking focus yourself, you can listen to this global selection Lookup, which fires changes whenever focus changes.

  3. Edit the MyViewerTopComponent source to add a lookup result member and implement the componentOpened, componentClosed, and resultChanged methods:

    private Lookup.Result<Event> result = null;
    
    @Override
    public void componentOpened() {
        result = Utilities.actionsGlobalContext().lookupResult(Event.class);
        result.addLookupListener (this);
    }
    
    @Override
    public void componentClosed() {
        result.removeLookupListener(this);
    }
    
    @Override
    public void resultChanged(LookupEvent lookupEvent) {
        Collection<? extends Event> allEvents = result.allInstances();
        if (!allEvents.isEmpty()) {
            Event event = allEvents.iterator().next();
            jLabel1.setText(Integer.toString(event.getIndex()));
            jLabel2.setText(event.getDate().toString());
        } else {
            jLabel1.setText("[Nothing selected]");
            jLabel2.setText("");
        }
    }
    • componentOpened() is called whenever the component is made visible by the window system; componentClosed() is called whenever the user clicks the X button on its tab to close it. So whenever the component is showing, you want it to be tracking the selection - which is what the above code does.

    • The resultChanged() method is your implementation of LookupListener. Whenever the selected Event changes, it will update the two `JLabel`s you put on the form.

      The required import statements for the MyViewerTopComponent are as follows:

    import java.util.Collection;
    import org.myorg.myapi.Event;
    import org.netbeans.api.settings.ConvertAsProperties;
    import org.openide.awt.ActionID;
    import org.openide.awt.ActionReference;
    import org.openide.util.Lookup;
    import org.openide.util.LookupEvent;
    import org.openide.util.LookupListener;
    import org.openide.windows.TopComponent;
    import org.openide.util.NbBundle.Messages;
    import org.openide.util.Utilities;

Creating the Editor Component

Now you need something to provide Event instances for the viewer. You’ll do this in the My Editor module, keeping with the goal of loose coupling between components.

You’ll create another TopComponent that opens in the editor area and provides an Event from its Lookup. The TopComponents are singletons by default but changing that is easy.

  1. Add dependencies to the My Editor module so it can find the classes you’ll use.

    Right click the My Editor project and choose Properties. On the Library page of the Project Properties dialog box, click the Add Dependency button, and type TopComponent in the Filter textbox. The dialog should automatically suggest setting a dependency on the Window System API. Do the same thing for Lookups (Lookup API). Also set a dependency on the Utilities API, Base Utilities API, and UI Utilities API, which provide various helpful supporting classes that are made available by the NetBeans Platform.

    You can select more than one dependency at a time using Ctrl + left-click. For example, you could select both Utilities API and UI Utilities API based off a filtered search for "Utilities".

    You’ll have six total dependencies (MyAPI from earlier, plus the five you just added):

    selection 1 nb27 editor1

    You can also see these in the Libraries node of the My Editor project.

  2. Right-click the org.myorg.myeditor package and choose New > Window like you did for MyViewer. Choose editor for Windows Position and check Open on Application Start. Use MyEditor for Class Name Prefix.

    selection 1 nb27 editor2
  3. When the form editor opens, add two Text Fields (javax.swing.JTextField), one above the other.

    selection 1 nb27 editor4

    In the property sheet, set both text fields' "editable" property to false.

  4. We want to allow the user to open more than one window. Click the Source tab to switch to the code editor and delete the preferredID from the @TopComponent.Description annotation on top of the class.

    @ConvertAsProperties(
        dtd = "-//org.myorg.myeditor//MyEditor//EN",
        autostore = false
    )
    @TopComponent.Description(
        //"preferredID" deleted here!
        //iconBase="SET/PATH/TO/ICON/HERE",
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
    )
    @TopComponent.Registration(mode = "editor", openAtStartup = true)
    @ActionID(category = "Window", id = "org.myorg.myeditor.MyEditorTopComponent")
    @ActionReference(path = "Menu/Window" /*, position = 333 */)
    @TopComponent.OpenActionRegistration(
        displayName = "#CTL_MyEditorAction",
        preferredID = "MyEditorTopComponent"
    )
    @Messages(
        {
            "CTL_MyEditorAction=MyEditor",
            "CTL_MyEditorTopComponent=MyEditor Window",
            "HINT_MyEditorTopComponent=This is a MyEditor window"
        })
    public final class MyEditorTopComponent extends TopComponent
  5. Add this code to the constructor after the initComponents() statement:

    Event obj = new Event();
    associateLookup(Lookups.singleton(obj));
    jTextField1.setText ("Event #" + obj.getIndex());
    jTextField2.setText ("Created: " + obj.getDate());
    setDisplayName ("MyEditor " + obj.getIndex());
  6. Right-click in the editor and choose Fix Imports. The imports should look like this:

    import org.myorg.myapi.Event;
    import org.openide.awt.ActionID;
    import org.openide.awt.ActionReference;
    import org.openide.util.NbBundle;
    import org.openide.util.lookup.Lookups;
    import org.openide.windows.TopComponent;

    The line associateLookup(Lookups.singleton(obj)); creates a Lookup containing one object - the new Event instance. This becomes what MyEditor.getLookup() returns. While this is a simple example, you can imagine Event representing a file, database entity, or anything else you want to edit or view. You could also have a component that lets you select or edit multiple Event instances, which is covered in the next tutorial.

    To make the editor component interesting (though it doesn’t actually edit anything), you set the text fields to display values from the Event.

Running the Code

Now you’re ready to run the tutorial. Right click EventManager and choose Run. When the IDE opens, choose Window > Open Editor to invoke your action. Do this a few times to open several editor components. Your MyViewer window should also be open. Notice how the MyViewer window content changes as you click different tabs:

selection 1 nb27 result1

If you click in the Viewer window or close all editor windows, the text changes to "[no selection]".

If you don’t see the MyViewer window, you probably didn’t check the "open on system start" checkbox in the wizard. Go to the Window menu and choose MyViewer to display it.

So, What’s the Point?

The key point is how the code is split into three modules: My Viewer knows nothing about My Editor, and either can run independently. They only share a dependency on My API. This means:

  1. My Viewer and My Editor can be developed and shipped independently

  2. Any module can provide a different editor and the viewer will work with it, as long as the new editor offers an Event from its Lookup

To understand the value, imagine Event is something complex - say MyEditor is an image editor and Event represents an image being edited. You could replace MyEditor with an SVG vector editor, and the viewer (showing image attributes) would work transparently with the new editor. This is why you can add tools for Java files to NetBeans — they remain compatible across NetBeans versions, and the same components and actions continue to work with alternative editors for Java (like the form editor).

This is how NetBeans works with source files. The editor’s Lookup contains a DataObject, and components like Navigator and Property Sheet watch what object the focused TopComponent provides.

This approach is also valuable when migrating existing applications to the NetBeans Platform. The data model object is probably existing, working code that shouldn’t change for NetBeans integration. By keeping the data model API in a separate module, NetBeans integration stays separate from core business logic.

Changing Selected Objects on the Fly

To show how powerful this approach is, you’ll add a button to your editor that replaces the Event with a new one on the fly.

  1. Open MyEditor in the form editor (click the Design button) and drag a Button (javax.swing.JButton) to it.

  2. Set the button’s text property to "Replace".

  3. Right click the JButton and choose Events > Action > actionPerformed.

    This opens the code editor with the cursor in an event handler method. Make the method call updateContent():

        private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
            updateContent();
        }

    Add the missing method:

    private void updateContent() {
        Event obj = new Event();
        jTextField1.setText("Event #" + obj.getIndex());
        jTextField2.setText("Created: " + obj.getDate());
        setDisplayName("MyEditor " + obj.getIndex());
        content.set(Collections.singleton (obj), null);
    }

    This is the same as the constructor code, except for the last line.

  4. Add this field at the top of the class:

    public class MyEditor extends TopComponent {
    
        private final InstanceContent content = new InstanceContent();

    InstanceContent lets you modify a Lookup’s content (specifically an AbstractLookup) on the fly.

  5. Remove the constructor lines you added earlier, except for the call to associateLookup(…​). Change that line to:

        associateLookup (new AbstractLookup (content));
    Keep the standard initComponents() call
  6. Add a call to updateContent() in the constructor, after the call to associateLookup().

  7. Right-click in the editor and choose Fix Imports. The imports should now look like:

    import java.util.Collections;
    import org.myorg.myapi.Event;
    import org.netbeans.api.settings.ConvertAsProperties;
    import org.openide.awt.ActionID;
    import org.openide.awt.ActionReference;
    import org.openide.util.NbBundle.Messages;
    import org.openide.util.lookup.AbstractLookup;
    import org.openide.util.lookup.InstanceContent;
    import org.openide.windows.TopComponent;

    You’re now ready to run the Event Manager again. Right click EventManager again and choose Run.

    When you click the Replace button, all components update, including the MyViewer instance:

    selection 1 nb27 replace3

Providing More Than One Object

This works well for decoupling, but providing just one object from your component is like having a Map with only one key and value. This technique becomes more powerful when you provide multiple objects from multiple APIs.

For example, NetBeans commonly uses context sensitive actions. The built-in SaveAction listens for a SaveCookie on the global context - the same way your viewer listens for Event. When a SaveCookie appears (editors add one when file content is modified but not saved), the action becomes enabled, so Save toolbar buttons and menu items become enabled. When Save is invoked, it calls SaveCookie.save(), which causes the SaveCookie to disappear, disabling the Save action until a new one appears.

The pattern is to provide more than one object from your component’s Lookup - different components and actions are interested in different aspects of the object being edited. These aspects can be cleanly separated into interfaces that those components and actions can depend on and listen for.

Next Steps

You may have noticed that some components have more detailed selection logic and even support multiple selection. The next tutorial covers how to use the Nodes API to handle that.