Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Hello EclipseLink + Explorer Views on the NetBeans Platform

DZone's Guide to

Hello EclipseLink + Explorer Views on the NetBeans Platform

· Java Zone ·
Free Resource

Get the Edge with a Professional Java IDE. 30-day free trial.

In this part we'll take an important step. We'll go from this position (which is where the previous article ended):

...to this one:

Our application will still be a viewer only, i.e., no editing functionality. (That will be a next part, where the big blank area in the screenshot above will become our editor.) However, instead of a JTable we'll have a NetBeans Platform explorer view. In fact, we'll have two: the tree view in the top left is a BeanTreeView, while the view in the lower left is the NetBeans Platform Properties window. One of the advantages of using these two views rather than the JTable is that they are automatically synchronized. Without needing to provide any code at all, we will let the Properties window reflect whatever is selected in the BeanTreeView.

Another important change is that we'll not be using Beans Binding to map the JPA query result to the JTable. Instead, we'll use a typical NetBeans Platform approach: the NetBeans Node class. NetBeans Nodes are pervasive throughout NetBeans Platform applications. They are generic hierarchical display objects, which we will use to represent our JPA entity class. By having a NetBeans Node represent our JPA entity, we enable the Customer data to be displayed in the explorer views. Why/how? Because the explorer views (e.g., the BeanTreeView) exists for no other reason than to display NetBeans Nodes. In addition, the NetBeans Node will create a layer between our entity class and the explorer view, which is also a good thing, further separating (and formalizing the separation of) our data from our view.

Let's get started.

  1. Do Everything Instructed in the Previous Article. Go through all the steps in Hello EclipseLink + BeansBinding on the NetBeans Platform. At the end, you'll have an application with 4 modules, displaying data from a database in a JTable, with JTextFields showing the details. The JTable and JTextFields use Beans Binding to connect them to the data.

  2. Create a Node. Now let's get right down to business. We need a NetBeans Node that will (1) represent our Customer entity class and (2) provide UI-level display features (such as a display name, icon, right-click contextual menu items, and Property Sheet synchronization). The first thing we need to do is set a dependency on the Nodes API (right-click the "Viewer-UI" module, choose Properties, then choose Libraries and browse to Nodes API). Hurray, you can now create your first Node because we now have the module that provides the Node class on our modulés classpath. Here is exactly how we will create our Nodes, via the NetBeans ChildFactory class (sọ, create the class that you see below, in your application's "Viewer-UI" module):
    public class CustomerChildFactory extends ChildFactory<Customer> {

    private List<Customer> list;

    CustomerChildFactory(List<Customer> list) {
    this.list = list;
    }

    @Override
    protected boolean createKeys(List<Customer> keys) {
    for (Customer c : list) {
    keys.add(c);
    }
    return true;
    }

    @Override
    protected Node createNodeForKey(Customer c) {
    return new CustomerNode(c.getName());
    }

    private class CustomerNode extends AbstractNode {
    private CustomerNode(String customerName) {
    super(Children.LEAF);
    setDisplayName(customerName);
    }
    }

    }

    There it is. What's going on here? Firstly, when the class is created, we pass in a List of Customers. ("Customer" is our entity class, located within a different module, but seamlessly available via dependency management between the modules.) We then create a class variable so that the List of Customers can be used elsewhere in the class. The "createKeys" is overridden. Whenever "keys.add" is called, "createNodeForKey" is invoked, which in turn creates a new node for the current customer. Each node only has a display name. In addition, we can see that each node is a child node, rather than being a node that contains its own children. That's all. That's how our nodes are created.

  3. Create a Context Controller. Now we need to somehow instantiate the above class. How to do this? Open the "ViewerTopComponent" in the Source view. Change the class signature to the following:
    final class ViewerTopComponent extends TopComponent implements ExplorerManager.Provider

    You should now see an error message because you need to set another dependency. This dependency (in the Project Properties dialog as before) must be on "Explorer and Property Sheet API". Once you've set that dependency, you can let the IDE add the required import statement. In addition, you'll be prompted to let the IDE implement this method:

    public ExplorerManager getExplorerManager() {
    return em;
    }

    Declare and initialize the Explorer Manager at the top of the class, like this:

    private ExplorerManager em = new ExplorerManager();

    Now, what have you done? You have implemented the NetBeans ExplorerManager class. This class handles the context of explorer views for you. So, for example, if you have multiple explorer views (such as the BeanTreeView and the Property Sheet, as seen in the screenshots above), the Explorer Manager will keep the two synchronized, so that a selected Customer in one view will also be selected in all other views that share the same Explorer Manager. In other words, the Explorer Manager is your friendly context controller, taking care of the low level plumbing that is involved in keeping the selection synchronized across explorer views. Handy, right?

  4. Display the Node. Now, how do we display our Node and, thereby, the entity class that the Node represents? Like this, in the constructor of the "ViewerTopComponent":
    em.setRootContext(new AbstractNode(Children.create(new CustomerNode(list), true)));
    em.getRootContext().setDisplayName("All Customers");

    So, we let the Explorer Manager handle the display of our Node. The Explorer Manager will look up the component hierarchy for any Swing component that it can interact with. Are any of those Swing components present in our application? No, none. So, if you were to run the application right now, nothing would be displayed.

    Therefore, we need some of these special Swing components, called "explorer views". Right-click in the Palette, choose Palette Manager, click "Add from JAR", and then browse to "org-openide-explorer.jar", which is in your NetBeans IDE distribution (in "platform9/modules"). Then click Next and select "BeanTreeView". Add it to any of the categories in the Palette, then drag it from the Palette onto the TopComponent. Run the application and then you'll see the BeanTreeView displaying your Nodes, via the Explorer Manager which mediates between Node and BeanTreeView.

  5. Synchronize with the Properties Window. If you run the application and then open the Properties window, you'll see nothing special. However, add this small bit of magic somewhere to the TopComponent constructor and then things will look a lot different:
    ActionMap map = getActionMap();
    associateLookup(ExplorerUtils.createLookup(em, map));

    Run the application again, open the Properties window, and notice that the bottom of the tab displays the same name as currently selected in the tree view.

    Now, within the definition of your "CustomerNode", define a few properties and add them to the Properties window for the current node:

    @Override
    protected Sheet createSheet() {

    Sheet sheet = Sheet.createDefault();

    Sheet.Set propertiesSet = Sheet.createPropertiesSet();
    Sheet.Set expertSet = Sheet.createExpertSet();

    Property normalProperty;
    Property expertProperty;

    normalProperty = new PropertySupport.ReadOnly<java.lang.String>(key.getCity(), java.lang.String.class, "City", "The city of " + key.getCity()) {

    public java.lang.String getValue() {
    return key.getCity();
    }
    @Override
    public boolean canWrite() {
    return false;
    }
    };
    propertiesSet.put(normalProperty);

    normalProperty = new PropertySupport.ReadOnly<java.lang.String>(key.getZip(), java.lang.String.class, "ZIP", "The ZIP of " + key.getCity()) {

    public java.lang.String getValue() {
    return key.getZip();
    }
    @Override
    public boolean canWrite() {
    return false;
    }
    };
    propertiesSet.put(normalProperty);

    expertProperty = new PropertySupport.ReadOnly<java.lang.String>(key.getState(), java.lang.String.class, "State", "The state of " + key.getState()) {

    public java.lang.String getValue() {
    return key.getState();
    }
    @Override
    public boolean canWrite() {
    return false;
    }
    };

    propertiesSet.put(normalProperty);
    expertSet.put(expertProperty);

    sheet.put(propertiesSet);
    sheet.put(expertSet);

    return sheet;

    }

    Again run the application. Open the Properties window (or right-click on a Node and choose Properties, which is a menu item that all Nodes have by default), and you'll see the properties you defined above displayed in the Properties window. If you like, move the Properties window to a different position in the application, as done for the screenshot at the start of this article. Notice that the custom position is restored upon restart, instead of the default.

  6. Set Icons. Now add an icon to your package and then define a String like this, in the Node class:
    private static final String IMAGE_NODE_BASE = "org/viewer/ui/iconNode.png";

    Then add the icon to the constructor of the CustomerNode, as shown at the end of the constructor below:
    private class PropNode extends AbstractNode {

    private Customer key;

    private PropNode(Customer key) {
    super(Children.LEAF, Lookups.singleton(key));
    this.key = key;
    setDisplayName(key.getName());
    setIconBaseWithExtension(IMAGE_NODE_BASE);
    }

    ...
    ...
    ...

    Next, maybe you'd like the root node to have its own icon too? Nothing simpler. Back in the TopComponent constructor, change this line:
    em.setRootContext(new AbstractNode(Children.create(new CustomerNode(list), true)));

    ...to this line:
    em.setRootContext(new RootNode(Children.create(new CustomerNode(list), true)));
    Notice that instead of "AbstractNode", which is one of the convenience classes that are part of the Nodes API, you now have your own custom Node class instead. It could be quite simple, as follows, for example:
    class RootNode extends AbstractNode {

    private static final String IMAGE_MAIN_BASE = "org/viewer/ui/iconMain.png";

    public RootNode(Children kids) {
    super(kids);
    setIconBaseWithExtension(IMAGE_MAIN_BASE);
    }

    }

And that's it. You now already have a much more powerful application than before. No longer a frail little JTable: instead, you have a whole range of views at your disposal that are all automatically synchronized with each other. Plus, you're now following the NetBeans Platform's idioms so that you can slowly begin using more and more of them, while your application moves deeper and deeper into the NetBeans Platform.

Finally, looking back at the code above... that wasn't all that much work, was it? Next time, we'll add a 5th module, for displaying and editing the selected customer. In that part, without very much work, we'll end up with an application that looks like this:

And the Editor window that you see above will be in a different module to where the tree hierarchy is found. The why and how of all that and more will be discussed next time.

 

Get the Java IDE that understands code & makes developing enjoyable. Level up your code with IntelliJ IDEA. Download the free trial.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}