Over a million developers have joined DZone.

How to Create a Web Service Client with Groovy and NetBeans Platform 6.8

DZone 's Guide to

How to Create a Web Service Client with Groovy and NetBeans Platform 6.8

In this article you will learn how to leverage the best of Groovy together with the best of the NetBeans Platform.

· Java Zone ·
Free Resource

In this article you will learn how to leverage the best of Groovy together with the best of the NetBeans Platform. Groovy will be used to access a Shakespeare quotation web service and parse its payload. The NetBeans Platform will be used to create a modular GUI in Swing for interacting via Groovy with the web service.

During the course of this article, you will be introduced to many NetBeans API classes, such as TopComponent, Node (specifically, BeanNode and AbstractNode), BeanTreeView, ExplorerManager, ChildFactory, ProgressHandleFactory, and ModuleInstall. Of central concern throughout the scenario that follows will be the NetBeans API classes Lookup, LookupListener, and, in particular, InstanceContent. While by no means a requirement, it will be of benefit to you to watch the screencast series Top 10 NetBeans APIs before continuing with the rest of this article.


Note: If, for some reason, the Shakespeare quotation service is unavailable at the time you're working through this article, simply find a different web service somewhere on-line and then use that instead, adapting the instructions below where necessary.

At the end of this article, you will have an application that looks like this:

And, by downloading and adding one new NetBeans module, the OfficeLAF module, and without any configuration or coding on your part, you'll be able to deliver an application that looks like this:

If you get stuck during the course of this article, the source code for the application can be found here:


Creating the Groovy Client

In this section, we create a Java application that uses Groovy to access and parse our web service. The Java application will have this structure:

Take the following steps:

  1. Create a new Java application, named "ShakesGroovy". Use the New File dialog in the IDE to add a new Groovy class named "ShakesWsClient.groovy" to the application. When you do this, the IDE adds the "groovy-all.jar" to the classpath of the application and updates the build script to enable the Groovy class to be compiled to Java when you build the application.

  2. Download the full GroovyWS JAR ("groovyws-standalone-xxx.jar") here and add it to the application's "lib" folder.

  3. Define the Groovy class as follows:
    package shakesgroovyimport groovyx.net.ws.WSClientclass ShakesWsClient {    String play, speaker, words    void findQuote(searchString){        def proxy = new WSClient("http://www.xmlme.com/WSShakespeare.asmx?WSDL",              ShakesWsClient.class.classLoader)        proxy.initialize()        def speech = new XmlParser().parseText(proxy.GetSpeech(searchString))        play = speech.PLAY.text()        speaker = speech.SPEAKER.text()        words = speech.text()    }}
    In the IDE's Groovy editor, the above should look as follows: Note: For information about GroovyWS, see http://groovy.codehaus.org/GroovyWS for details.
  4. Define the "Main.java" class (in the image above, it is named "Demo.java") like this, so that you can try out the Groovy web service client above:
    package shakesgroovy;public class Demo {    public static void main(String[] args) {        ShakesWsClient client = new ShakesWsClient();        client.findQuote("fair is foul");        System.out.println(client.getPlay());        System.out.println(client.getSpeaker());        System.out.println(client.getWords());    }}
  5. Run the application. You should see something like this as the application's output:
    12 Dec 2009 6:45:47 PM org.apache.cxf.endpoint.dynamic.DynamicClientFactory outputDebugINFO: Created classes: com.xmlme.webservices.GetSpeech, com.xmlme.webservices.GetSpeechResponse, com.xmlme.webservices.ObjectFactory12 Dec 2009 6:45:49 PM groovyx.net.ws.AbstractCXFWSClient getBindingOperationInfoWARNING:  Using SOAP version: 1.1MACBETHALLFair is foul, and foul is fair: Hover through the fog and filthy air.
  6. Look in the Files window, specifically in the "dist" folder, which you can see in NetBeans IDE's Files window. You should see the two Groovy JARs, as well as the JAR containing the Java classes produced by the Groovy commpile process:

As will be explained in the next sections, the three JARs that you see above will need to be wrapped in a NetBeans module, which you'll include in the NetBeans Platform application that you'll create in the next sections. Then you'll create a new module, which will provide a new window in the application, which will access the Java code (compiled from Groovy above) to interact with the Shakespeare web service. That will be possible because of the dependencies you will set between the two modules.

Setting up the Application

Let's start by creating a new NetBeans Platform application.

  1. Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. Under Projects, select NetBeans Platform Application. Click Next.

  2. In the Name and Location panel, type "ShakespeareSearch" in the Project Name field. Click Finish.

The IDE creates the ShakespeareSearch project. The project is a container for all the other modules you will create.

NetBeans Platform container

Run the application and notice that you have quite a few features out of the box already. Open some of the windows, undock them, and get to know the basic components that the NetBeans Platform provides without you doing any work whatsoever:

Note: Everything you see in the screenshot above can be hidden, if superfluous to your business needs, or enabled, e.g., the "Save" menu item can be enabled as described in How to Create a Swing CRUD Application on NetBeans Platform 6.8.

Once you've familiarized yourself with the framework of your application (by looking through the menus and clicking the buttons shown in the screenshot above), continue with the next section, where you'll wrap the three Groovy JARs into a NetBeans module and add it to the application.

Wrapping the Groovy JARs in a NetBeans Module

In this section, you add your first module to your application. The new NetBeans module will wrap the Groovy JAR files you worked on in the first section, above.

  1. To simplify the next step, put the three JARs ("groovy-all.jar", "groovyws-standalone-xxx.jar", and "ShakesGroovy.jar") into one folder somewhere on your hard disk. Once they are in the same folder folder, you'll be able to select them more easily, via Ctrl-Click in the wizard in the next step, when wrapping them into a NetBeans module.

  2. Right-click the ShakespeareSearch "Modules" node in the Projects window and choose "Add New Library". Select the three JARs ("groovy-all.jar", "groovyws-standalone-xxx.jar", and "ShakesGroovy.jar") by clicking "Browse" and then "Ctrl-Click" to select all three JARs in the folder where you put them above. Then complete the wizard, specifying any values you like, such as by filling in the values below:

You now have your first custom module in your new application, wrapping the JARs containing the Groovy-related classes. Take a look in your new NetBeans module's "Libraries" node in the Projects window and you will see your three JARs:

Right-click the new NetBeans module, choose Properties, and in the "API Versioning" node you will see that all the packages of the three JARs in the module are available to the rest of the applications. I.e., these are application-wide packages. That's the default. However, the only package you need to expose is the last one, i.e., "shakesgroovy", because that's the package providing the Java code compiled from your Groovy class, i.e., for interacting with the web service:

Therefore, if you like, you can uncheck the checkmarks for all the other packages listed in the dialog above, because all you need to expose to the rest of the application is the "shakesgroovy" package.

Now that the Groovy JARs are available in your application, the next section will show you how you can create a new NetBeans module that will access the Groovy class to retrieve Shakespeare quotations from the web service. Then we'll spend some time making a nice UI, using the many advanced Swing components that the NetBeans Platform makes available for free out of the box.

Creating a Simple Prototype

Our simple prototype will consist of an application containing two custom modules, in addition to the 5 mandatory NetBeans Platform modules making up the runtime container, as well as, optionally, any of the other NetBeans Platform modules you will be making use of. The first you created in the previous section: it contains the three Groovy-related JARs. In this section, you create the second module. It will provide a window via the NetBeans API "TopComponent" class, which is a JPanel-like component that provides windows in NetBeans Platform applications. Within the TopComponent, you will have a JLabel, a JTextField, a JButton, and a JTextPane. When the button is clicked, the text in the text field will be sent to the web service... and the response will be displayed in the text pane.

  1. Right-click the ShakespeareSearch "Modules" node in the Projects window and choose "Add New". Give your new NetBeans module a name......and, in the next step, specify a unique identifier for the new NetBeans module:
  2. Click Finish and you should see that you have two NetBeans modules within your application:
  3. Now we'll let our "ShakesViewer" module provide a window. Right-click the "ShakesViewer" project and choose New | Other | Module Development | Window Component. Click Next. Select "editor" as the "Window Position" and check the "Open on Application Start" checkbox. Click Next. Type "ShakesViewer" in "Class Name Prefix" and then click Finish.

  4. Now use the Palette in the IDE to drag and drop a JLabel, JTextField ("searchField"), JButton ("findButton"), and JTextPane ("resultPane") onto the TopComponent in "Design" mode. Rearrange the components until you have designed a window that looks like this:
  5. Right-click the application and choose Run. The application starts up, installing all the NetBeans Platform modules that have been included (to see which these are, right-click the application node in the Projects window, choose Properties, and look in the Libraries tab), together with the two custom modules you created thus far. Don't continue with the rest of this article unless you see this:
  6. Before our second module can use the Java code provided by the first module, we need to set a dependency on the first module from the second module. I.e., not only must the first module make packages available to the whole application, but any application ALSO has to set a dependency on that first module. That means, we have a bi-directional contract between the modules in our application. Right-click the ShakesViewer module, choose Properties, and click "Add Dependency" in the Libraries tab. Then find the first module's unique ID and click OK:
  7. To enable us to integrate with the NetBeans Platform's Progress bar (thus handling the scenario where we don't want the UI to be blocked while interaction is done with the web service), set another dependency, this time on the "Progress API":
    Now we're ready to start adding some code!
  8. In the IDE, double-click the "findButton" so that the source editor opens, with the cursor in the "findButtonActionPerformed" method. There, add this very simple piece of code, for retrieving a quotation from the web service and displaying it in the text pane:
    private void findButtonActionPerformed(java.awt.event.ActionEvent evt) {                                               Thread t = new Thread(new Runnable() {        @Override        public void run() {            //Use the NetBeans Progress API to define a new progress handle:            ProgressHandle p = ProgressHandleFactory.createHandle(                    "Fetching the Shakespeare quote for "                    + "'" + searchField.getText() + "'...");            //Start the progress bar:            p.start();            //Find a new Shakespeare quote via the Groovy-based client:            ShakesWsClient client = new ShakesWsClient();            client.findQuote(searchField.getText());            //Populate the UI components with the current object's values:            resultPane.setText(client.getWords());            //Populate the status bar with the Shakespeare play returned:            StatusDisplayer.getDefault().setStatusText("Play: "                     + client.getPlay());            //Finish the progress bar:            p.finish();            //Note: The progress bar takes 3 lines to include,            //and helps us with all UI-blocking activities!        }    });    t.start();}
    Note: Read more about the NetBeans Progress API here.
  9. Run the application and then type a search string, such as "love is like" in the text field:  Click the Find button. Notice that the UI is still usable, i.e., not blocked, and that the progress bar gives feedback to the user about the processing that's taking place. Once the quotation is retrieved, it appears in the text pane, as shown below:

Congratulations, your simple prototype is working as expected. Now let's include a lot of the interesting & powerful NetBeans API classes, to display the retrieved results in a JTree!

Using NetBeans Platform Swing Components

In this section, we add a Swing "JTree" to the application. However, this will be a special "JTree", one that will integrate better with the NetBeans Platform than the standard "JTree" is able to do. This "JTree" is the NetBeans API "BeanTreeView" class. At the end of this section, you will have a "BeanTreeView" that will list the speakers retrieved from the web service, while being synchronized with the Properties window, though no properties will be shown in the Properties window since, at that point, you will be using the "AbstractNode" instead of the "BeanNode" with which this section will begin.

The "BeanTreeView" is more powerful than the "JTree", and better suited to a modular application, because it includes an understanding of "context sensitivity", which is helpful in modular applications, where many different modules may be available at the same time. Knowing the current context (selection management) is crucial in modular scenarios... and the standard Swing "JTree" doesn't have this knowledge built into it.

Also, a "BeanTreeView" displays a hierarchy of "Nodes", which are NetBeans Platform objects for visualizing business objects. Typically, in standard Swing, you need to create a different model object for each type of Swing component you're working with (JTree, JTable, JList, etc), while the Node is a generic model object that works with any (NetBeans version) of these, such as the "BeanTreeView". So, you create your model once and display it anywhere.

These topics are illustrated in detail in the example code and texts that follows below.

  1. Switch to the TopComponent Design view, right-click in the Palette, choose Palette Manager | Add from JAR. Then browse to "org-openide-explorer.jar", which is in "platform11/modules" folder, within the NetBeans IDE installation directory. Click Next and choose "BeanTreeView" and complete the wizard. You should now see BeanTreeView in the Palette. Drag it from the Palette and drop it on the window, to the left of the text pane. Move the components around and make sure you end up with something like this:
  2. Right-click the "ShakesViewer" Libraries node, choose "Add Module Dependency", and then set dependencies on "Nodes API" and "Explorer & Property Sheet API".

  3. Run the application and make sure you see the following when it starts up:
  4. Next, in the source code of the "ShakesViewerTopComponent" class, change the class signature to implement "ExplorerManager.Provider":
    final class CustomerTopComponent extends TopComponent implements ExplorerManager.Provider
    You will need to override "getExplorerManager", to return the current "ExplorerManager":
    @Overridepublic ExplorerManager getExplorerManager() {    return em;}
    At the top of the class, declare and initialize the ExplorerManager:
    private static ExplorerManager em = new ExplorerManager();
    Note: Watch Top 10 NetBeans APIs for details on the above code, especially the screencast dealing with the Nodes API and the Explorer & Property Sheet API.
  5. Now let's define a business object, which we will create whenever a new "ShakesWsClient" object is found in the global "Lookup".
    public class QuoteBean {        private String name;    private String play;    private String speech;    public String getName() {return name;}    public void setName(String name) {this.name = name;}    public String getPlay() {return play;}    public void setPlay(String play) {this.play = play;}    public String getSpeech() {return speech;}    public void setSpeech(String speech) {this.speech = speech;}}
    Now we will create new instances of this business object whenever a new quotation is retrieved from our Shakespeare search and added to the global Lookup!
  6. It is at this point that we need to define a "ChildFactory" object, which is a NetBeans API class that will generate the nodes that will be displayed in the "BeanTreeView". So, create a new class named "QuoteChildFactory", which extends "ChildFactory", and define it as follows, while making sure you understand all the comments in the code below:
    class QuoteChildFactory extends ChildFactory<QuoteBean> implements LookupListener {    private Result<ShakesWsClient> result;    private List<ShakesWsClient> quotes;    public QuoteChildFactory() {        //Listen for ShakesWsClient objects in the global Lookup:        result = Utilities.actionsGlobalContext().lookupResult(ShakesWsClient.class);        result.addLookupListener(this);        //Call once to 'resultChanged' to activate the listener:        resultChanged(new LookupEvent(result));    }    @Override    public void resultChanged(LookupEvent le) {        //Create a new ArrayList:        quotes = new ArrayList();        //Put all the instances of ShakesWsClient found in the global Lookup into a collection:        Collection<? extends ShakesWsClient> coll = result.allInstances();        //Iterate through the collection of found ShakesWsClient objects:        for (ShakesWsClient client : coll) {            //Add all the ShakesWsClient objects in the global Lookup to the ArrayList:            quotes.add(client);            //The list of objects has changed and the Nodes should be updated,            //causing 'createKeys' to be invoked immediately, because of the 'true':            refresh(true);        }    }    @Override    //Whenever a QuoteBean is added to the "quoteBeanList" in this method,    //a call to "createNodeForKey" is triggered, which will receive the QuoteBean:    protected boolean createKeys(List<QuoteBean> quoteBeanList) {        if (!quotes.isEmpty()) {            for (ShakesWsClient client : quotes) {                QuoteBean s = new QuoteBean();                s.setName(client.getSpeaker());                s.setPlay(client.getPlay());                s.setSpeech(client.getWords());                quoteBeanList.add(s);            }        }        return true;    }    //Each time "createNodeForKey" is called,    //which happens when a new QuoteBean is added to the quoteBeanList above,    //a new "Node" is created (see below),    //which wraps the business object and displays it in the explorer view:    @Override    protected Node createNodeForKey(QuoteBean quoteBean) {        try {            return new BeanNode(quoteBean);        } catch (IntrospectionException ex) {            Exceptions.printStackTrace(ex);            return null;        }    }}
  7. In line 7 above, notice this comment: "Listen for ShakesWsClient objects in the global Lookup". So, the question to be addressed now is: "How do we add new ShakesWsClient objects to the global Lookup?" We do that in the "ShakesViewerTopComponent" class. Whenever a new ShakesWsClient is created, i.e., this happens when a new quotation is retrieved from the web service, we need to add it to the global Lookup. That enables other parts of the application to do something, where necessary, i.e., that gives a context to other parts of the application. In this case, the introduction of new ShakesWsClients in the global Lookup enables the "ChildFactory" class to create new nodes in the "BeanTreeView" because it has a "LookupListener" that is defined to listen to the presence of "ShakesWsClient" objects. So how is this done? In "ShakesViewerTopComponent", instantiate a new "InstanceContent":
    private InstanceContent content = new InstanceContent();
    Next, in the constructor of "ShakesViewerTopComponent", instantiate the "QuoteChildFactory" and also populate the "Lookup" (i.e., the context) of the "ShakesViewerTopComponent":
    //Let the ExplorerManager display new child nodes asynchronously (because of 'true')//in its explorer views (e.g., the BeanTreeView that is defined in this class):em.setRootContext(new AbstractNode(Children.create(new QuoteChildFactory(), true)));//Add the ExplorerManager and the ActionMap to the Lookup of the TopComponent,//together with the InstanceContent:associateLookup(new ProxyLookup(        ExplorerUtils.createLookup(em, getActionMap()),        new AbstractLookup(content)));    
    Now that the global Lookup of the "ShakesViewerTopComponent" is populated with (line 8) synchronization code enabling the Properties window to interact with the explorer views and (line 9) the "InstanceContent", we're ready to add objects dynamically to the "InstanceContent" object. So, finally, add one line ("content.add(client)" in line 14 below) to the "actionPerformed" of the "findButton":
    private void findButtonActionPerformed(java.awt.event.ActionEvent evt) {                                               Thread t = new Thread(new Runnable() {        @Override        public void run() {            ProgressHandle p = ProgressHandleFactory.createHandle(                    "Fetching the Shakespeare quote for "                    + "'" + searchField.getText() + "'...");            p.start();            ShakesWsClient client = new ShakesWsClient();            client.findQuote(searchField.getText());            resultPane.setText(client.getWords());            StatusDisplayer.getDefault().setStatusText("Play: "                    + client.getPlay());            content.add(client);            p.finish();        }    });    t.start();} 
  8. Run the application and you should see the following:

    Switch between two returned nodes in the "BeanTreeView" and notice that the Properties window is automatically updated, i.e., the two explorer view are synchronized. However, the "JTextPane" is not an explorer view, hence it is not automatically synchronized with the explorer views. (And now you see the power of the explorer views in action!) In the next step, you will learn how you can synchronize a standard Swing component with the explorer views in your application.

  9. To synchronize the "JTextPane" with the explorer views, we first need to detect when a node in the explorer view is selected (by mouse and keyboard). At that time, we need to get the current "QuoteBean" in the local "Lookup" of the "Node" and use that instance of the "QuoteBean" to populate the text field, text pane, and status bar.

    • To do this, we need to create our own "BeanTreeView", i.e., you need to create a class (an inner class in the "ShakesViewerTopComponent") that extends "BeanTreeView". Within that class, get the events (such as the "MouseClicked" below) called on the "JTree" within the "BeanTreeView". You can use the global variable "tree" for this purpose, as done below. Then get the "ExplorerManager" via the explorer view and identify the "QuoteBean" assigned to the local Lookup (i.e., the local context) of the selected Node and use that "QuoteBean" to populate the text field, text pane, and status bar:
      public class MyBTV extends BeanTreeView {    public MyBTV() {        tree.addMouseListener(new MouseAdapter() {            @Override            public void mouseClicked(MouseEvent e) {                //Find the ExplorerManager for this explorer view:                ExplorerManager mgr = ExplorerManager.find(MyBTV.this);                //Get the QuoteBean in the Lookup of the selected Node:                QuoteBean quoteBean = mgr.getSelectedNodes()[0].getLookup().lookup(QuoteBean.class);                if (quoteBean != null) {                    //Update the UI with the current QuoteBean's data:                    resultPane.setText(quoteBean.getSpeech());                    StatusDisplayer.getDefault().setStatusText("Play: " + quoteBean.getPlay());                }            }        });    }}
      However, right now the Lookup of the Node does not contain a "QuoteBean". That's because you're using "BeanNode" to define the Node. "BeanNode" is the simplest of the Node classes. It is useful for quick prototyping but can't really be used for more complex scenarios. It does not, for example, let you define a context (i.e., a "Lookup"). Therefore, open the "QuoteChildFactory" and create a custom node, extending "AbstractNode", where you pass a new "Lookup" object, containing the current "QuoteBean" to the superclass:
      public class QuoteNode extends AbstractNode {    protected QuoteNode(QuoteBean quoteBean) {        super(Children.LEAF, Lookups.fixed(quoteBean));        setDisplayName(quoteBean.getName());    }}
      Now change "createNodeForKey" to the following (i.e., as shown below, comment out the "BeanNode" code and return the new "QuoteNode" that you defined above):
      //Each time "createNodeForKey" is called, a new "Node" is created,//which wraps the business object and displays it in the explorer view:@Overrideprotected Node createNodeForKey(QuoteBean quoteBean) {    return new QuoteNode(quoteBean);//        try {//            return new BeanNode(quoteBean);//        } catch (IntrospectionException ex) {//            Exceptions.printStackTrace(ex);//            return null;//        }}
    • Finally, make sure that your application is using "MyBTV" (defined above) rather than the default "BeanTreeView" that you worked with earlier. Open the "ShakesViewerTopComponent" in "Design" mode (i.e., in the Matisse GUI Builder), right-click the text pane, choose "Customize Code", change the first line as shown below, so that "MyBTV" is created when the "ShakesViewerTopComponent" is created, and then click OK: Open the blue block in the source editor and make sure that you see this, i.e., note the "beanTreeView1 = new MyBTV()" in the final line below:
      private void initComponents() {    jLabel1 = new javax.swing.JLabel();    searchField = new javax.swing.JTextField();    jScrollPane1 = new javax.swing.JScrollPane();    resultPane = new javax.swing.JTextPane();    findButton = new javax.swing.JButton();    beanTreeView1 = new MyBTV();    ...    ...    ...        

Run the application again and you will not experience any problems when selecting a different node in the "BeanTreeView", i.e., whenever you choose a new "Node", new content will be added to the standard Swing "JTextPane". (However, since you are now using "BeanNode" instead of "AbstractNode", you need to provide your own icons and properties, both of which are done automatically by "BeanNode", via the NetBeans Nodes API Tutorial.) Run the application again and you should see something like this: 

Congratulations, at this point you have used some of the most important NetBeans API classes. In the next (and final) section, we will add some additional code that will create a custom root node (by subclassing "AbstractNode"), as well as a "Delete" menu item on each of the child nodes. The latter will enable items in the list to be removed, which is typical functionality in this type of application.

Wrapping Up

In this section, you bind a "Delete" action to the quotation nodes. You also create a new "root node", which will replace the default "AbstractNode" that you're currently using to define the node that contains the child nodes (i.e., the quotation nodes).

  1. To define the "Delete" menu item on the quotation nodes, you need to delete the currently selected node in the "BeanTreeView". Below, if OK is clicked in the confirmation dialog, the "QuoteBean" is found in the local "Lookup" of the currently selected "Node". We then use the speech as the unique ID of the "QuoteBean" (not a great approach, but we don't have real unique IDs in this example), to identify the corresponding "ShakesWsClient" object, which we remove from the "InstanceContent". Therefore, you need to pass the "InstanceContent" into your "ChildFactory" and set it as a class variable. From then onwards, you can use the "InstanceContent" within your code, as done below to remove the "ShakesWsClient" object from the "InstanceContent", after which the node hierarchy is recreated.
    private class DeleteAction extends AbstractAction {    private final Node node;    public DeleteAction(String name, Node node) {        super(name);        this.node = node;    }    @Override    public void actionPerformed(ActionEvent e) {        NotifyDescriptor.Confirmation msg = new NotifyDescriptor.Confirmation(                "Are you sure?",                "Delete Quote Item",                NotifyDescriptor.OK_CANCEL_OPTION);        Object result = DialogDisplayer.getDefault().notify(msg);        if (NotifyDescriptor.YES_OPTION.equals(result)) {            QuoteBean quoteBean = node.getLookup().lookup(QuoteBean.class);            for (ShakesWsClient client : quotes) {                if (client.getWords().equals(quoteBean.getSpeech())) {                    content.remove(client);                    refresh(false);                }            }        }    }}
    Note: You need to set a dependency on the NetBeans Platform "Dialogs API", which provides a number of standard dialogs, such as "NotifyDescriptor.Confirmation", which you're using in the code above, which will create this dialog:
  2. Now we hook the action above into our Node, where we pass in the label of the menu item together with the current Node so that the correct object can be removed from the "InstanceContent":
    public class QuoteNode extends AbstractNode {    protected QuoteNode(QuoteBean quoteBean) {        super(Children.LEAF, Lookups.fixed(quoteBean));        setDisplayName(quoteBean.getName());    }    @Override    public Action[] getActions(boolean bln) {        Action[] actions = new Action[]{            new DeleteAction("Delete", this),};        return actions;    }}
  3. Next, let's create a custom root node, which will replace the default "AbstractNode" we're currently using. Within your "TopComponent", define the root node as an inner class, making sure that you have a 16x16 image at "/org/shakes/viewer/shakes.png":
    class RootNode extends AbstractNode {    public RootNode(Children chldrn) {        super(chldrn);        setDisplayName("Speakers");        setIconBaseWithExtension("/org/shakes/viewer/shakes.png");    }}
    And then use the "RootNode" defined above when creating the child nodes. The "ExplorerManager.setRootContext" should now be as follows:
    em.setRootContext(new RootNode(Children.create(new QuoteChildFactory(content), true)));
    Note: Not only are we using the "RootNode" instead of the "AbstractNode", above we are now also passing the "content" (i.e., the "InstanceContent" defined in the "TopComponent") into the "ChildFactory" object, so that we can delete it there, when applicable.
  4. Next remove the tab in the "TopComponent", since here we're not dealing with a multi-document interface, hence no tabs are needed. Follow the related instructions provided here.

  5. Optionally, as an exercise, enable the Undo/Redo buttons, as well as the Save functionality, as described in How to Create a Swing CRUD Application on NetBeans Platform 6.8.

When you run the application, you should now see something that looks like this:

Notice that a display name and image are displayed with your root node and that, when you right-click a child node, the "Delete" menu item is available.

Congratulations, you have completed this article. For more tutorials, see the NetBeans Platform Learning Trail.



Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}