X

Geertjan's Blog

  • October 6, 2011

Pluggable Swing View Component

Geertjan Wielenga
Product Manager

This may appear to be a standard Swing tree component:

However, it's not. It is pluggable. Each of the root nodes shown above comes from a different module, while the view component is loosely coupled from its data sources:

The view module needs only the GenericNodeProvider module, because it will load implementations of the GenericNodeProvider interface via the Java extension mechanism, i.e., via META-INF/service registration entries.

So, the starting point is to create a module containing a GenericNodeProvider:

package my.node.provider;
import java.util.List;
public interface GenericNodeProvider {
    List<String> getData();
}

Then, in the, in this case, three provider modules, we depend on the module providing the above generic provider, by implementing the interface, e.g:

package org.provider.city;
import java.util.ArrayList;
import java.util.List;
import my.node.provider.GenericNodeProvider;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service=GenericNodeProvider.class)
public class City implements GenericNodeProvider{
    @Override
    public List<String> getData() {
        List list = new ArrayList();
        list.add("Toronto");
        list.add("San Francisco");
        list.add("London");
        return list;
    }
    @Override
    public String toString() {
        return "City";
    }
}

The @ServiceProvider annotation registers the provider in the META-INF/services folder.

Next, we create a TopComponent containing a BeanTreeView and an ExplorerManager, as always in NetBeans Platform applications. This module, i.e., the view module, depends on the module containing the GenericNodeProvider, but not on any of the other modules.

Then, here's the code. Note especially the usage of Lookup.getDefault to retrieve the registered providers:

private ExplorerManager em = new ExplorerManager();

public PluggableNodeExplorerTopComponent() {

    initComponents();

    setName(NbBundle.getMessage(PluggableNodeExplorerTopComponent.class,
            "CTL_PluggableNodeExplorerTopComponent"));
    setToolTipText(NbBundle.getMessage(PluggableNodeExplorerTopComponent.class,
            "HINT_PluggableNodeExplorerTopComponent"));

    setLayout(new BorderLayout());

    BeanTreeView btv = new BeanTreeView();
    btv.setRootVisible(false);

    em.setRootContext(new AbstractNode(
            Children.create(new FirstLevelChildFactory(), true)));

    add(btv, BorderLayout.CENTER);

    associateLookup(ExplorerUtils.createLookup(em, getActionMap()));

}

private class FirstLevelChildFactory extends ChildFactory<GenericNodeProvider> {

    @Override
    protected boolean createKeys(List list) {
        list.addAll(Lookup.getDefault().lookupAll(GenericNodeProvider.class));
        return true;
    }

    @Override
    protected Node createNodeForKey(GenericNodeProvider key) {
        Node node = new AbstractNode(
                Children.create(new SecondLevelChildFactory(key.getData()), true));
        node.setDisplayName(key.toString());
        return node;
    }

}

private static class SecondLevelChildFactory extends ChildFactory<String> {

    private final List key;

    public SecondLevelChildFactory(List key) {
        this.key = key;
    }

    @Override
    protected boolean createKeys(List<String> list) {
        list.addAll(key);
        return true;
    }

    @Override
    protected Node createNodeForKey(String key) {
        Node node = new AbstractNode(Children.LEAF);
        node.setDisplayName(key);
        return node;
    }

}

And that's all.

Join the discussion

Comments ( 4 )
  • Ernest Thursday, October 6, 2011

    This feels very similar to the registration style of the Services Tab being loosely coupled from registered root notes using @ServicesTabNodeRegistration .


  • Henk Friday, October 7, 2011

    Would the same thing be possible for a menu?


  • Geertjan Friday, October 7, 2011

    Henk, menus are provided by actions registered in the "Actions" folder. So, this functionality already works out of the box for Actions and there's nothing you need to do for that. If you live somewhere in the Netherlands, let me know, and I'll come and introduce you to the NetBeans Platform via a few demos.


  • Jesse Glick Tuesday, October 11, 2011

    SecondLevelChildFactory should take a GenericNodeProvider, not its List<String> data - so that the data is retrieved only if and when the node is expanded.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.