Tutorial: Not So Global Selection

Selection management and nodes are two frequently used aspects of the NetBeans Platform. And these aspects have been well documented in tutorials:

These tutorials (specifically the second one) deal with explorer views that are located on top of TopComponents. And how the selected node can influence global selection. And that is very useful indeed.

However, one day I ran into a requirement that was a little different. I needed a dialog box with an explorer view (a BeanTreeView in that specific case). And selection of one of the nodes in the view needed to only influence other controls on that same dialog box.

Having found a way to accomplish this, I decided to create a sample movie information viewer dialog using the same principles (see Figure 1 below). And write a tutorial for NetBeans 7.1 about this application.

In the final product, when selecting one of the nodes on the left, the release date textbox will be updated according to the selection.

Movie Viewer
Figure 1: Completed movie information viewer screenshot

Note that this tutorial does assume some previous experience with NetBeans Platform application development. See the the NetBeans Platform Learning Trail for more information.

Creating the Project

  1. From the File menu, choose New Project…
  2. Choose the NetBeans Modules category, and the Module project type. Click Next.
  3. On the name and location page, specify a name for the project.
  4. Select the Standalone Module option, and click Next.
  5. Specify a code name base for the project, such as za.co.pellissier.selectiondemo.
  6. Click Finish.

Displaying the Dialog

Displaying a dialog in a NetBeans Platform application is done by using the Dialogs API. This took me a while to find the first time I used it, so I think it is worthwhile mentioning here. 🙂

  1. Create a new JPanel Form and call the class MoviePanel.
  2. Add a module dependency on the Dialogs API.
  3. Create a new always enabled action on the global toolbar. This action will display the dialog.
  4. Add the following code to the generated actionPerformed() method, and fix imports.

MoviePanel f = new MoviePanel();
NotifyDescriptor msg = new DialogDescriptor(f, "Movies!");
Object result = DialogDisplayer.getDefault().notify(msg);

The Data

  1. Create a class to contain the data to be displayed, and call it Movie.

public class Movie
{
    private String title;
    private int releaseYear;

    public Movie(String title, int releaseYear)
    {
        this.title = title;
        this.releaseYear = releaseYear;
    }

    public int getReleaseYear()
    {
        return releaseYear;
    }

    public void setReleaseYear(int releaseYear)
    {
        this.releaseYear = releaseYear;
    }

    public String getTitle()
    {
        return title;
    }

    public void setTitle(String title)
    {
        this.title = title;
    }
}

The Children

  1. Add module dependencies on the Nodes API and Lookup API.
  2. Create a class to create the child nodes and call it MoviesChildren.

public class MoviesChildren extends Children.Keys
{
    ArrayList children = new ArrayList();

    public MoviesChildren()
    {
        children.add(new Movie("The Green Hornet", 2011));
        children.add(new Movie("How to Train Your Dragon", 2010));
        setKeys(children);
    }

    @Override
    protected void addNotify()
    {
        setKeys(children);
    }

    @Override
    protected Node[] createNodes(Object t)
    {
        Node node = new AbstractNode(LEAF, Lookups.singleton(t));
        node.setName(((Movie) t).getTitle());
        return new Node[]
                {
                    node
                };
    }
}

Note that the children class would normally get the actual data from elsewhere. For the purposes of this tutorial, however, it is the simplest solution to just have the data hardcoded there since the data is not the focus of the tutorial.

Form Design

  1. Add a module dependency on the Explorer & Property Sheet API.
  2. Open the MoviePanel in Design view, and drag a Swing Scroll Pane onto the panel. Call it mMoviesPane.
  3. Change the scroll pane’s custom creation code to be:
    new BeanTreeView()
  4. Change the scroll pane’s post creation code to be: ((BeanTreeView)mMoviesPane).setRootVisible(false);
  5. Switch to source view and fix imports.
  6. Switch back to Design view, and create the label and text field for the release date. Call the text field mDate.

The Magic

So far, we haven’t done anything out of the ordinary. However, now we get to the interesting part.

Firstly, it is worthwhile mentioning how an explorer view such as the BeanTreeView finds the explorer manager where it gets its data from. It finds is parent component in the GUI component hierarchy, and checks whether it implements ExplorerManager.Provider. If not, it continues up the hierarchy until it finds one. So we want our MoviePanel to implement ExplorerManager.Provider, just like the TopComponent in the Selection Management tutorial.

  1. Change the MoviePanel class to implement ExplorerManager.Provider
  2. Add the following code to the class to implement the required method:

private ExplorerManager mManager = new ExplorerManager();

@Override
public ExplorerManager getExplorerManager()
{
    return mManager;
}

Now we need to create the root node and its children.

  1. Add the following code to the constructor, after the initComponents() call:

mManager.setRootContext(new AbstractNode(new MoviesChildren()));

Now the nodes will be displayed when you run the module. The only part that remains is the selection management. The MoviePanel itself will be listening for the selection events.

  1. Change the MoviePanel class to also implement LookupListener
  2. Add the following code to the class to implement the required method:

@Override
public void resultChanged(LookupEvent Event)
{
    Collection results = ((Lookup.Result) Event.getSource()).allInstances();
    for (Object object : results)
    {
        if (object instanceof Movie)
        {
            mDate.setText(((Movie)object).getReleaseYear() + "");
        }
    }
}

The very last piece of the puzzle is how to register for the result changed events.

Declare the lookup result variable in the class:

private Lookup.Result mResult = null;

And add the following to the constructor of the MoviePanel:

Lookup actionLookup = ExplorerUtils.createLookup(mManager, getActionMap());
mResult = actionLookup.lookupResult(Movie.class);
mResult.addLookupListener(this);
mResult.allInstances();

Compare this to the code from NetBeans Selection Management Tutorial I—Using a TopComponent’s Lookup:

result = Utilities.actionsGlobalContext().lookupResult(Event.class);
result.addLookupListener (this);

The important change is which lookup gets used to register for the events.

Concluding Remarks

No associateLookup() call is required since the objects are not put onto the global lookup. And in fact, that method is only available to TopComponents.

3 thoughts on “Tutorial: Not So Global Selection”

  1. You mention at the end that “No associateLookup() call is required since the objects are not put onto the global lookup. And in fact, that method is only available to TopComponents.”. Agreed, but suppose I wanted selection of nodes inside this dialog to influence global selection. Is there an analogue of associateLookup() for this scenario to have the selection influence global selection ( Utilities.actionsGlobalContext() ) without requiring the global context provider to be overridden?

    • It depends where the panel gets created. If you have a TopComponent that you can use for this purpose (for example with a button that launches the panel), you can create a ProxyLookup that proxies both its own lookup as well as that of the panel. And associate that ProxyLookup using the TopComponent’s associateLookup().

      But launching the panel from a TopComponent is a corner case. So far I haven’t found any other solution except overriding the global context provider, and that is fairly extreme. 🙂

Leave a Comment