目录

我的学习分享

记录精彩的程序人生

X

【Netbeans Platform】NetBeans Platform on standalone Swing applications

1 Introduction

The NetBeans Platform offers a great open-source architecture to build enterprise-strength applications. Many companies use the NetBeans Platform as a sound basis to build all sort of applications.

Becoming proficient in the intrisics of the NetBeans Platform is often hard, despite of the good tutorials and learning courses available, because the Platform offers a vast set of features that, naturally, have to be learned. That's why I'm writing these notes, to smooth the learning curve, to make it easier for developers experienced with Swing technologies to get started with some NetBeans APIs!

1.1. Objectives. Pros and cons.

These notes introduce you to some of the basics of user interface design: the Nodes API, the Explorer API, the Property Sheet API and some of the most important UI components in the NetBeans Platform. Along the way we'll see some idioms and patterns you may find interesting as well.

The main objective of these notes is to introduce you to all these NetBeans Platform APIs so that you can run them in your own standalone Swing applications, this is, without the whole NetBeans Platform.

Not using the whole NetBeans Platform has, of course, some drawbacks: you won't be able to exploit the whole set of features. It has, nevertheless, some important advantages:

  • The development cycle is shorter. You can modify, build, run and test your GUI faster, as you don't have to compile a whole suite of modules. This is usually good if you want to polish some details in your GUI.

  • It makes migration easier. If you already have a Swing application and want to move it to the NetBeans Platform you can start by adapting it to the NetBeans GUI elements first. Once you have the basic UI working you may want to move to a full fledged NetBeans Platform application, exploiting some more advanced features (such as editors, update centers, java-source code analysis, refactoring, PHP or Ruby support, etc.).

  • Makes learning easier. By reducing the list of APIs to learn we make things easier, so that once you're proficient with these you can move to more advanced topics.

1.2. What we'll be doing

In order to keep things simple we'll be building a networked two-tier application but that doesn't involve databases, as setting the tables and installing database servers is an unneccessary complication. We'll be using the Twitter Search API instead, which doesn't require authentication, so the application will allow us to search stuff in Twitter.

We'll be architecting the application so that it's easy for you to add database support, if you want, or add authorization/authentication frameworks that allow you to log-in into Twitter (or any other web site) to see your tweets. Let's keep things simple: our objective is to learn the NetBeans APIs, not to build a database client.

1.3. Getting started

This is a list of things you will need to follow these notes:

  • A Java IDE, of course. This ranges from plain vi+command line to a full fledged IDE. Of course, I'm using the NetBeans IDE. You can use whatever you want for this, as fas as you're confortable with it.

  • Some jar files from the NetBeans Platform (see below for the whole list), version 7.0 Beta2 or above. You can get these by installing the NetBeans IDE and looking for them. Or you can just download the platform zip files.

  • The source code. I will be adding the source code for the application at Kenai.com. You may want to import it into your IDE, or use it as a basis for building your own applications. The source code is released under both the GPL V2.0 with the Classpath exception and the CCDL v1.0 licenses, at your choice.

The list of jar files you'll need is as follows:

org-openide-util.jar

A great set of platform-wide utility functions (WeakListeners, RequestProcessors, and much more).

org-openide-util-lookup.jar

This is the NetBeans Lookup Library, the most important library on the NetBeans Platform.

org-openide-nodes.jar

This is the NetBeans Nodes API, probably the second most important API.

org-openide-filesystems.jar

We'll need this for compilation purposes only (we need some annotations within this file). This jar file contains APIs for different kind of filesystems (yes, you can create your own filesystems too).

org-openide-explorer.jar

The NetBeans Platform Explorer API, we'll be using this to make UI development a snap.

org-openide-dialogs.jar

The NetBeans Dialog API. We won't be using this directly, but it's needed because of dependencies.

org-openide-awt.jar

The NetBeans AWT API. We won't be using this directly, but it's needed because of dependencies.

org-netbeans-swing-outline.jar

A great deal of UI components, including a TreeTable (Outline), advanced list and trees and more.

These jar files are relased under both the GPL v2.0 license (with the classpath exception) and the CDDL 1.0 license. This means that you can use the jar files in your own applications, even in commercial applications. Note that if you modify the files somehow (by adding a patch, for instance) you are required to make the modifications public too. Please adhere to any of the two licenses and be sure you understand them before using the jar files.

In order to use these jars you have to create a project in your favorite IDE and add the jars in the classpath of your project. How to do this depends, of couse, on your IDE. You're expected to do this yourself.

1.4. About these notes. Acknowledgements. Copying.

Note that I will be writing these notes during the following weeks, on my spare time, little by little, so we warned that I won't be doing this full time. I may add corrections or improve the notes as things evolve. Keep tuned.

I appreciate comments on the notes. Comments will be moderated, and all spam will be rejected.

I would like to thank 4G Soluciones for allowing me to write these notes, and for giving me some extra free time to work on them.

Finally I would also like to thank the NetBeans Team for building and improving the NetBeans Platform, a great way to build spiffy Swing applications.

These notes are Copyrighted material by Antonio Vieiro and 4G Soluciones. The notes are released under the Creative Commons CC BY-NC-ND 3.0 license. In order to copy, distribute or transmit these notes you will need a written permission by me and by 4G Soluciones.

You should be able to print the notes directly from here. There's a special css for printing that removes most unnecesary content. 4G Soluciones and I may relese the notes using the "epub" format, so that you can read them in your favourite e-reader. If you're interested in this then just drop me a line and I'll see what I can do.

Now, enough about legal stuff and thanks: let's get started!

2 Preliminaries: models, entities and DAOs

Before talking about the internals of the NetBeans APIs and learning the basics of NetBeans UI design, we'll need to define some basic stuff about our "Twitter Search Application": we'll have to define a client-side data model with some entities. Let's do that quickly.

2.1. Client-side data model, entities

Building a client-side application requires you to model data, of course. This client-side data model may be different from any server-side data model you may have. On the client-side you can mix data from different server-side data models, for instance.

For our Twitter-search application we'll define a quick & dirty model with the following entities:

Tweet

A Tweet will hold information related to, well, a tweet. This includes different attributes such as an URL, the text in the tweet, a timestamp and the author of the tweet.

TweetAuthor

All the information about the author of a Tweet will be kept in a "TweetAuthor" entity, so that we can add atributes to the entity later on (such as the Twitter URL of the author, an image, or whatever).

Query

A Query will hold information about the query we'll be doing to the Twitter Search API. At the moment we'll encapsulate a simple "search term" inside this entity. In the future we may want to extend this entity with logical operators (AND, OR, NOT, etc.). But let's keep things simple first.

model.png

We'll keep all these entities in a Java package called, say, "net.antonioshome.nbtweeting.entities". The entities will be plain old Java objects, this is, "Java Beans" with an empty constructor and getters and setters. Something like this:

package net.antonioshome.nbtweeting.entities;

public class Query
{
  private String keyword;
  private List<Tweet> tweets;

  public Query() 
  {
    tweets = new ArrayList<Tweet>();
  }

  public List<Tweet> getTweets()
  {
    return tweets;
  }

  public void setKeyword( String keyword )
  {
    this.keyword = keyword;
  }

  public String getKeyword()
  {
    return this.keyword;
  }
  

Note that if you're building a database application, these entities may or may not reflect the internals of your database model. In my experience is better not to bring the database structure to the client-side model. You'll be suffering if you do: it is better to have a separate model, even though you have to rewrite some plain Java objects. Anyway this discussion does not belong to these notes, so let's keep going quickly. Next section, please!

2.2. Data Access Objects

We will also need some basic Data Access Objects which will be responsible for searching using the Twitter Search API.

The "TwitterSearchDAO", for instance, will receive a "Query" object and will talk to Twitter to receive some, say, XML. It will then parse this XML and build some "Tweet" and "TweetAuthor" entities, and will update the list of Tweets in the Query entity. The source code could look something like this:

package net.antonioshome.nbtweeting.dao;

public class TwitterSearchDAO
{
  public List<Tweet> search( String keyword ) throws Exception
  {
    // Talk to Twitter, parse XML
    // Create a list of Tweet and return it
    ...

If you're building a two-tier database application then this "dao" package will usually have SQL or JPA related queries. If you're building a three-tier web-service based application then these "DAOs" will be responsible for fetching stuff from remote web services. Note that, as I explained above, these DAOs return client-side entities, and not database entities.

If you're building a database application you may want to add a light transaction-aware layer in between the rest of your client-side application and the DAOs. This "transaction-aware" layer may combine different DAOs in a single client-side transaction. If you build a 3-tiered application then this layer belongs to the server side, of course. Oh, well, I'm talking off-topic again. Let's keep going!

2.3. Entities with abilities

One of the most imporant and powerful idioms in the NetBeans Platform is that you can add capabilities to existing objects dynamically. You do this by using the NetBeans Lookup Library.

You can, for instance, add the "Saveable" capability to an object at runtime, to indicate that the object can be saved. Once the object is saved you can remove that ability from the object.

Some other capabilities could be, for instance: the "Closeable" capability (for objects that can be closed, such as forms, projects or files). The "Openable" capability (for objects that can be opened by the user). Or the "Reloadable" capability (for objects that can be reloaded by the user).

Let's see briefly what we need to make our entities "capability enabled".

2.3.1. Creating an ability

Let's add an ability to one of our entities, just to learn how to do that. Which one? Mmmm... What about a "Reloadable" ability to the "Query" entity? So that we can refresh the list of Tweets in the Query? Yep. Let's do that.

We start by defining our ability using a plain Java interface. We could also use a plain Java class, or an abstract class. But I prefer interfaces:

/**
  * Reloadable is an ability contained in objects that can be reloaded.
  */
public interface Reloadable
{
  /**
    * Invoke this method to reload the entity.
    * @throws Exception if the entity cannot be reloaded.
    */
  public void reload() throws Exception;
}
  

2.3.2. Adding abilities to our entities

Let's modify the "Query" entity so that it can hold "abilities". We do this by marking the entity as "Lookup.Provider", and by creating a "Lookup" object with all the abilities in our entity. In our case we add an implementation of the "Reloadable" ability that uses the "TwitterSearchDAO" to fetch a new list of Tweets; like so:

public final class Query implements Lookup.Provider ①
{
  private String keyword;
  private List<Tweet> tweets;
  private Lookup lookup; 
  private InstanceContent instanceContent; 
  public Query() {
    this.tweets = new ArrayList<Tweet>();
    this.instanceContent = new InstanceContent(); ②
    this.lookup = new AbstractLookup( instanceContent ); ③
    this.instanceContent.add( new Reloadable() { ④

      public void reload() throws Exception {
        TwitterSearchDAO dao = new TwitterSearchDAO();
        tweets.clear();
        tweets.addAll( dao.search( keyword ) );
      }
    } );
  }
  public Lookup getLookup() ⑤
  {
    return lookup;
  }
  ... // Rest of getters and setters

Let me explain briefly the code above:

① We start by implementing the Lookup.Provider interface, indicating that this class has extended abilities.

② Since we're implementing the "Lookup.Provider" interface we must have a "getLookup" method, that is implemented here by returning the "lookup" instance variable.

③ We use an InstanceContent object, responsible for holding all the abilities of our objects.

④ We create an AbstractLookup, a special kind of Lookup, that wraps the InstanceContent object we defined above.

⑤ Finally we "add" an implementation of the "Reloadable" ability to the instance content of our Query entity. The implementation of this "Reloadable" ability just calls the TwitterSearchDAO and updates the list of Tweets in our Query.

This is the NetBeans-way to dynamically add abilities to objects: observe that the "Query" entity does not implement the "Reloadable" ability directly. We add the "Reloadable" ability to the "Query" lookup, instead, so that we can add and remove that ability at runtime. This common NetBeans idiom is extremely powerful [1]

2.3.3. Querying objects for its abilities

Another common idiom in NetBeans Platform programming consists on how to see if an object has a given ability. You do this by "looking-up" the ability in the object's "lookup", like so:

Query query = ... // any object implementing the Lookup.Provider interface

// Get the "lookup" of the object, and lookup for the "Reloadable" ability...
Reloadable reloadable = query.getLookup().lookup( Reloadable.class );

// If the reloadable ability exists then reload the object...
if ( reloadable != null ) 
{
  try
  {
    reloadable.reload();
  }
  catch( Exception e )
  {
    ...
  }
}

This is a very common idiom in the NetBeans Platform, and you'll be seeing it all around the code. Get used to it.

2.4. Summary: model, entities and DAOs

We've defined a set of entities such as Tweet, TweetAuthor and Query, that we'll be using in our client-side application. These are our own entities, and have nothing to do with whatever entities Twitter is using internally. We don't mind that.

We've also encapsulated all business logic (in our case a simple query to the Twitter Search API) inside a DAO.

Finally we've quickly seen how to add abilities to our entities by using the NetBeans Lookup Library. We know how to add an ability to an object, and how to search the abilities of any object.


[1] Many programming languages, such as Javascript or Lua, make it possible for you to add dynamic behaviour to objects at runtime. The NetBeans Lookup Library makes it possible to use this feature in Java applications.

3 The Nodes API (I)

If you want to master the NetBeans Platform you must master the NetBeans Nodes API.

3.1. What is a Node?

A Node is a visual representation of data. In our application we'll use Nodes to visually represent one or more of our entities, and also to represent collections of entities. So, for instance, we'll have a "QueryNode" to visually represent a "Query" entity. We'll also have a "TweetNode" to visually represent the "Tweet" and "TweetAuthor" entities.

Nodes have a hierarchical relationship between them. A Node has always a set of Children nodes, that can be of different types. In our application the "QueryNode" will have a list of "TweetNode" as its children.

Nodes are also used to visually group a subset of entities. We could, for instance, define an "HourRangeNode" that represents a time period (an hour ago, six hours ago and the last day, for instance) and then make these "HourRangeNode"s be the children of our "QueryNode". We could then group our "TweetNodes" as children of hour "HourRangeNode". So the relationship between these nodes would be "QueryNode" > "HourRangeNode" > "TweetNode". We won't be doing this just to keep things simple, though.

nodes1.png

Nodes may have an icon, a display name, or a display name in HTML, among many other different things. Nodes allow us to separate the visual representation of an entity from the entity itself. This is important because you may want to have different representations of the same entity, and because you may modify or rearrange your client-side model while keeping the visual aspects intact. Figure Nodes are visual representation of entities explains graphically the relationship between nodes and entities.

3.2. Creating leaf nodes

The easiest way to create a leaf node is by extending the AbstractNode class and passing it the "Children.LEAF" constant on the construtor. Let's see an example:

public class TweetNode extends AbstractNode ①
{
  private Tweet tweet;
  public TweetNode( Tweet entity ) ②
  {
    super( Children.LEAF, entity.getLookup() ); ③
    this.tweet = entity;
  }

  @Override
  public String getDisplayName() ④
  {
    return tweet.getTweetAuthor().getName() + ": " + tweet.getText();
  }
}

① You extend the AbstractNode class. There're other classes you can extend too, but that's out of scope for these notes.

② You pass the entity (or entities) that you want to visualize in the constructor. You want to keep a reference to them in a node private variable, so you can refer to it to extract visualization information for your entity (or entities).

③ You pass the "Children.LEAF" constant to the super class, indicating that this node has no children at all, and pass the "Lookup" of the node to the constructor of the super class. By doing so all the abilities of the entity are included in the abilities of the node. This is usually handy.

④ You then override any methods you want from the super class. In the example above we override the "getDisplayName" method, which returns a String used to visualize the node. In the example above we include the author name and the text of the tweet.

3.3. Creating a leaf node with abilities

In the example above the TweetNode has all the abilities of the Tweet entity, because we are passing the "Lookup" of the entity to the constructor of the AbstractNode class.

In some cases you may want to have different set of abilities for entities and for nodes, but still combine them within a single lookup. You do this by passing the AbstractNode constructor a ProxyLookup, a special kind of Lookup that combines two or more lookups.

Combining object abilities is a very common and powerful NetBeans Platform idiom, so it deserves an explanation. Let's see an example by improving our previous example:

public class TweetNode extends AbstractNode 
{
  private Tweet tweet;
  private InstanceContent instanceContent;

  public TweetNode( Tweet entity ) 
  {
    this( entity, new InstanceContent() ); ①
  }

  private TweetNode( Tweet entity, InstanceContent ic ) ②
  {
    super( Children.LEAF, new ProxyLookup( entity.getLookup(), new AbstractLookup(ic) ); ③
    this.tweet = entity;
    this.instanceContent = ic;
    // Add abilities to the  node in this instanceContent
    this.instanceContent.add( new WhateverAbility() ); ④
    // Add the entity to the abilities of the node
    this.instanceContent.add( entity ); ⑤
  }

② You create a (usually private) constructor that is passed an entity and an InstanceContent. This InstanceContent is going to keep the abilities of the node, so you want to keep this in a private variable in the node too.

① In the previous constructor you just call the new constructor, and pass it a brand new InstanceContent.

③ In the new (private) constructor you pass the AbstractNode constructor a ProxyLookup, that combines the lookup of the entity with a new AbsctractLookup with the abilities of the instance content.

④ You then override any methods you want from the super class. In the example above we override the "getDisplayName" method, which returns a String used to visualize the node. In the example above we include the author name and the text of the tweet.

④ You then just add the node-specific abilities to the InstanceContent by invoking the "add" method to the instance content. Of course you can remove abilities in the node at any time by invoking the "remove" method on the InstanceContent.

⑤ A handy trick is to add the entity to the abilities of the node, so that we can easily "lookup" the entity from the node.

3.4. Summary

In this section we've get acquaintanced with the NetBeans Nodes API. We've understood that a Node is a representation of an entity (a folder, a file, a database connection, a Tweet and its TweetAuthor, etc.).

We've also seen that Nodes may have all the abilities of the entities they represent, or may enhance those abilities with their own.

We're now ready to explore more about the Nodes API: we'll see how to create nodes with children. That requires a new section.

4 The Nodes API (II)

In this section we'll quickly learn how to create nodes with children asynchronously. I beg you to have patience.

4.1. Creating Nodes with Children, asynchronously

In the NetBeans Platform you can create Nodes with Children in different ways. Luckily none of them requires sex.

Probably the easiest way to create a Node with Children that are fetched asynchronously is by means of a ChildFactory. The NetBeans Platform manages all threading for you, so you don't have to worry about starting background threads, or about SwingWorkers, or about threading issues with the AWT.

nodes2.png

In our example application each Query entity has a list of Tweets, and each Tweet has a TweetAuthor. We also have a QueryNode (a visual representation of a Query) and a TweetNode (that visually represents Tweets and TweetAuthors). What we'll be doing is to make a list of TweetNodes the children nodes of a QueryNode. We'll need a QueryNodeChildFactory object, because we'll be retrieving the list of Tweets asynchronously from the Query entity. Let's start building it little by little. The very first you need is to extend the ChildFactory class, like so:

class QueryNodeChildFactory extends ChildFactory<Tweet> ①
{
  private Query query;

  QueryNodeChildFactory( Query query ) ②
  {
    this.query = query;
  }
}

① We extend a ChildFactory, which is a generic Java class. We specify the type of the entities that will be used to construct the nodes. In our case we will be building TweetNodes from Tweets, so we have to specify "Tweet" (the entity type) here.

② Since we'll need a Query entity to retrieve a list of Tweets, we pass the Query entity in the constructor, and keep a private variable so that we can use it later on.

4.1.1. Actually retrieving the list of Tweets from the Query

The next step is, of course, to retrieve the list of Tweet entities from a Query entity. We do this by implementing the "createKeys" method from the ChildFactory class, like so:

  /**
    * Populate "list" with the Tweet entities corresponding
    * to the Query entity.
    * Note: this method is invoked automatically in a worker thread.
    * @param list the List of Tweet to populate.
    * @return true if we're all set, false to be called again later
    *   to fetch even more results.
    */
 @Override
  protected boolean createKeys(List<Tweet> list) {
    
    // Retrieve the "Reloadable" ability from the query object
    // (now, abilities are cool, aren't they?)
    Reloadable r = query.getLookup().lookup( Reloadable.class ); ①

    if ( r != null )
    {
      try
      {
        r.reload(); ②
      }
      catch( Exception e )
      {
        // Empty
      }
    }

    // Add all the tweets in the query to the list
    list.addAll( query.getTweets() ); ③
    // Indicate that we're done
    return true; ④
  }

① Remember that our Query object has the Reloadable ability? We could use that to retrieve the list of feeds in the Query: just retrieve the "Reloadable" ability here...

② ... and invoke the "reload()" method here. Of course you could use the TwitterSearchDAO here, and invoke appropriate methods to retrieve a collection of Tweet from the Query. If you were building a database application you could use some SQL, or JDBC or JPA here to retrieve some entities. I'm using the Reloadable ability here because it's cool (and illustrative), isn't it?.

③ The "createKeys" method that we're overriding here is passed a list as an argument. The objective of this method is to populate this list with all the entities we want to have. In our case we simply "addAll" the Tweets in the Query to this list.

④ Finally, since we're all set, we just return "true". If you returned "false" here then the NetBeans Platform would keep on calling the "createKeys" method again and again, until true is returned. This is handy if you have a huge number of entities (say one thousand) and you want to retrieve the entities little by little. We could, for instance, retrieve just 100 Tweets here and return false, and keep on adding Tweets until we reach one thousand, in which case we should be returning true. In-between each call to the "createKeys" method, the NetBeans Platform adds nodes to the list of children and repaints them in the UI, that improves the "perceived performance" of your application, allowing the user to "see" the first results of your query quickly, instead of waiting for one thousand Tweets to be retrieved from twitter.

4.1.2. A child at a time

So we have populated a list of Tweets entities from the Query entity. What now? Of course, we need to create visual representation for those, i.e., actually creating the list of children nodes from the list of entities. How do we do this? By overriding one more method of the ChildFactory class, of course, like so:

  @Override
  protected Node createNodeForKey(Tweet key) {
    return new TweetNode(key);
  }

Simple: for each Tweet entity that we added in the list in the "createKeys" method, the NetBeans Platform will invoke the "createNodeForKey" method and pass it the Tweet entity. All we have to do is to create a TweetNode from the Tweet entity, by using the TweetNode constructor that we built in the previous section. Oh, well, it isn't that complex, is it?

4.1.3. Associating the QueryNode and the QueryNodeChildFactory

The final step is to associate the QueryNode with the QueryNodeChildFactory. We do this in the QueryNode constructor, passing it the QueryNodeChildFactory instead of the Children.LEAF constant we used previously for leaf nodes. The code looks like this.

/**
 * QueryNode is a node that represents a query. The children of this
 *   node are the results of the query.
 *
 * @author Antonio Vieiro (antonio@antonioshome.net)
 */
public final class QueryNode extends AbstractNode {

    private Query query;
    private InstanceContent instanceContent;

    /**
     * Public constructor from an entity.
     *
     * @param query The entity that this node represents visually.
     */
    public QueryNode(Query query) { ①
        this(query, new InstanceContent());
    }

    /**
     * Private constructor from an entity and an InstanceContent
     *
     * @param query The entity that this node represents visually.
     * @param ic The InstanceContent object that keeps this node's abilities.
     */
    private QueryNode(Query query, InstanceContent ic) {
        // Invoke the super constructor, passing it a list of children 
        // to be retrieved with a QueryNodeChildFactory, and
        // a ProxyLookup that combines this node's abilities with the entity's
        super(Children.create(new QueryNodeChildFactory(query), true), ②
                new ProxyLookup( // Combination of lookups 
                        query.getLookup(), // The entitie's abilities
                        new AbstractLookup(ic))
        ); // This node's abilities
        // Keep the entity and the instancecontent on member variables
        this.query = query;
        this.instanceContent = ic;
        // Add a new ability for this node to be reloaded
        this.instanceContent.add(new ReloadableNode() { ③

            public void reloadChildren() throws Exception {
                // To reload this node just set a new set of children
                // using a QueryNodeChildFactory object, that retrieves
                // children asynchronously
                setChildren(
                        Children.create(
                                new QueryNodeChildFactory(QueryNode.this.query),
                                true));
            }
        });
    }

① Since we want to mix the abilities of our entity and our node we use the same idiom as for the leaf nodes: we invoke a private constructor with an InstanceContent that will hold the abilities of the node.

② Here comes the important part, instead of passing the super constructor a "Children.LEAF" constant, we're invoking the Children.create method, passing it out QueryNodeChildFactory and a boolean.

The important thing is the boolean. If you pass a "true" value then the children nodes will be created asynchronously on a worker thread: the NetBeans Platform will start that worker thread for us, so we don't have to worry at all about threading issues. All we have to do is to populate the list in the createKeys method!

③ Finally we're adding an ability to our node to be reloaded. We have defined a ReloadableNode ability for our QueryNode, and what we do here is to reload the node. Note that reloading nodes is not the same thing as reloading entities, so we use the ReloadableNode for refreshing nodes and the Reloadable ability for refreshing entities (one calls the other, of course, through our ChildFactory implementation). This "reload ability" for node is easy to implement: we're invoking the "setChildren" method of the AbstractNode with a new ChildFactory, as we did in the constructor.

4.2. Summary

In this section we've learned how to create Nodes with Children, without sex, without threads. The ChildFactory is a great way to build a list of children asynchronously, isn't it?.

We've also learned how to fetch a list of entities from another one (using the Reload ability) and we've seen how to add a reload ability to our Nodes (the ReloadableNode ability) that uses the "setChildren" method to regenerate the children of the node.

Time now to have a rest, download the source code from Kenai.com and take a look at it. Meanwhile I'll be preparing a new section of the tutorial: creating nodes synchronously.

5 The Nodes API (III)

In the previous section we've learned how to asynchronously create a Node with Children by using a ChildFactory. In this section we'll learn how to create a Node when its Children are known in advance.

In our sample application we don't need synchronous children, so the code below is not real. Think of the code in this section as an exercise for you: add multiple-query support to the sample application

5.1. Creating Nodes with Children, synchronously

Let's imagine we have a list of Query entities and that we want these to be the children of a "QueryListNode" we're building.

Of course we can use the ChildFactory method we saw in the previous section to build the Children of our QueryListNode. We could pass it a 'false' argument to the Children.create call to make thins synchronous.

But the NetBeans Platform offers another way to create the children object synchronously: instead of using a ChildFactory you could use the Children.Keys class. You basically extend this class like so:

public class QueryListChildren extends Children.Keys<Query>
{
  private List<Query> queries;
  public QueryListChildren( List<Query> queries )
  {
    super();
    this.queries = queries;
  }

Then you have to override two other methods: addNotify and removeNotify. In these methods you specify the list of entities by using the setKeys method, like so:

  @Override
  protected void addNotify()
  {
    super.addNotify();
    // Set the set of entities that this node represents
    setKeys( queries );
  }
  @Override
  protected void removeNotify()
  {
    // Use an empty set to clean up 
    // the list of entities that this node represents
    setKeys( Collections.<Query>emptySet() );
    super.removeNotify();
  }
  

After that you have to tell the NetBeans Platform what kind of nodes you want to use to represent your child entitites. You do this by overriding the createNodes method, like so:

  @Override
  protected Node[] createNodes( Query query )
  {
    return new Node[] { new QueryNode( query ) };
  }
  

Finally you associate your parent node (the QueryListNode) with its children in its constructor, like this.

  public QueryListNode extends AbstractNode
  {
    private List<Query> entities;
    public QueryListNode( List<Query> entities )
    {
      super( new QueryListChildren(entities) );
      this.entities = entities;
    }

Simple, isn't it? If you want to see real-code examples of Children.Keys you can browse the results of this Google query

5.2. Summary.

In this section we've quickly seen how to use a Children.Keys base object to represent the children of a node for cases where the child entities are known in advance. We could also have used a ChildFactory as in the previous section.

In the next section we'll quickly talk about ExplorerManagers and will then proceed with Swing UI fun. Patience!

6 The ExplorerManager

In the previous sections we've learned about the NetBeans Nodes API. From what we have seen you may think that NetBeans Nodes are similar to Swing's TreeNodes, that are visual representations of "user objects" or entities. Well, if so you are on the right track. NetBeans Nodes are much more powerful, as we'll see all allong the way.

In Swing each UI component has an associated "model" that holds both the data you want to represent and the internal state of the UI component. For instance, a JTree uses a TreeModel, a JList uses a ListModel, a JTable uses a TableModel, a JButton uses a ButtonModel, etc.

explorer.png

Developing UI components in NetBeans is usually easier because most NetBeans UI components use just a single "model": an ExplorerManager. So when you build an user interface with NetBeans you just use a single model: a single model to rule all UI components. This is, a single ExplorerManager works with a ListView, an OutlineView, or a PropertySheetView, for instance. In this section we'll learn about ExplorerManagers.

6.1. ExplorerManager providers

When doing Swing you set the TableModel (TreeModel,ListModel) within the JTable UI Component (JTree,JList) you want to populate with data by invoking the "setModel" method on the component. Of course, since the NetBeans Platform is based on Swing, you still can do this in your NetBeans Platform applications.

Since the NetBeans ExplorerManager is used to rule different UI Components at once you don't want to invoke a "setModel" on each one of them, right? There must be a way for NetBeans UI components to find the model they should render.

Instead of associating your ExplorerManager with each specific UI Component you associate it with a UI Component container. This is, with a JPanel, a JDialog or a JFrame (or a NetBeans specific container called TopComponent, not covered here).

When you add a NetBeans UI component to a component hierarchy, the component will automatically scan all its parent containers seeking for the first one that has an ExplorerManager. It then will use that ExplorerManager as the source of all its data. That automatic behaviour is very useful, because you don't have to remember to "setModel" on each component: they automatically discover the "model" they have to render.

That's the NetBeans way to do things: a single ExplorerManager you set on a container can rule different UI child components at the same time. No need to have different TableModels, and TreeModels and ListModels: you can rule all those components with a single ExplorerManager. Cool, isn't it?

To associate a UI container with an ExplorerManager you must implement the ExplorerManager.Provider interface in your UI Component container. In our example application we have done that in the main JFrame, like this:

/**
 * NBTweetingMainFrame is a plain JFrame that uses different NetBeans components.
 * This class is a ExplorerManager.Provider, so all NB UI Components embeded within it
 * will use this frame's ExplorerManager as the object containing the main nodes.
 * @author Antonio Vieiro (antonio@antonioshome.net)
 */
public class NBTweetingMainFrame
extends javax.swing.JFrame implements ExplorerManager.Provider { 
  
  private ExplorerManager explorerManager = new ExplorerManager();

  public ExplorerManager getExplorerManager() {
    return explorerManager;
  }
    

Now you can add NetBeans UI components to that frame, and those components will use that instance of ExplorerManager as its source for inspiration.

Note that you could add a JPanel with another instance of ExplorerManager if you wanted to. Then all components added to that JPanel will then use that other instance of ExplorerManager. I hope you get the idea.

6.2. Adding data to the ExplorerManager

Now that you have created an ExplorerManager and associated it with a UI Container you have to populate it with data, i.e, with visual representation of data, i.e. with Nodes.

We do this when the user presses the "Search" button in our JFrame, at that moment we create a "Query" entity with the query, and then a QueryNode that visually represents the Query. We then tell the ExplorerManager that we want to render the QueryNode using the setRootContext method, like so:

    private void cmdSearchActionPerformed(java.awt.event.ActionEvent evt) {

      // To perform a query we create a query entity...
      Query query = new Query();
      // ... set its keyword
      query.setKeyword(txtSearchTerm.getText());
      // Create a node from the query...
      QueryNode node = new QueryNode(query);
      // ... and set this explorer's manager root node to it
      explorerManager.setRootContext(node);
      // Now all NetBeans UI Components 
      // using this ExplorerManager will be updated.

    }

6.3. Handling events

When you use Swing components you listen for selection events on each UI Component. So, for instance, you have to add a listener to a JTable or to a JTree or to a JList.

Since we are using a single ExplorerManager to rule different components we have to use the ExplorerManager to listen for selection events. So if the ExplorerManager is related to an OutlineView, and to a ListTableView and to an IconView, you just use a single point to listen for selection events. Much simpler, isn't it?

To listen for selection events you first attach a PropertyChangeListener to the explorer manager, and then listen for the ExplorerManager.PROP_SELECTED_NODES property name, like this:

    // Add a PropertyChangeLister to detect selection 
    explorerManager.addPropertyChangeListener(new PropertyChangeListener()    {

      public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
          setSelectedNodes(explorerManager.getSelectedNodes());
        }
      }
    });

And then you're given an array of selected nodes, that you can use for whatever you like. In our case we get the first selected node and see what abilities the node has, and perform some stuff depending on the abilities of the node (we enable the reload button if the node is reloadable and we set text in a text field if the node has the "HasTextContent" ability).

  /**
   * This method will be invoked whenever a node is selected in the ExplorerManager.
   * @param selectedNodes An array with the selected nodes.
   */
  private void setSelectedNodes(Node[] selectedNodes) {

    if (selectedNodes == null || selectedNodes.length == 0) {
      txtTweet.setText("");
    } else {
      Node firstSelectedNode = selectedNodes[0];
      // If the node "HasTextContent" ability then add the text content to the TextArea
      HasTextContent ctc = firstSelectedNode.getLookup().lookup(HasTextContent.class);
      if (ctc != null) {
        txtTweet.setText(ctc.getText());
      } else {
        txtTweet.setText("No text content");
      }
      // If the node has a "ReloadableNode" ability 
      // then enable the cmdReload button, disable otherwise
      ReloadableNode rn = selectedNodes[0].getLookup().lookup(ReloadableNode.class);
      cmdReload.setEnabled(rn != null);
    }
  }
    

6.4. Summary

In this section we've quickly seen what an ExplorerManager is, how a single ExplorerManager can handle different NetBeans UI components, how to populate it with data and how to listen to selection events.

Note that we've only covered very basic functionalities of the NetBeans Explorer Manager idiom. There are more advanced concepts that you can exploit in standalone Swing applications, such as how to integrate ExplorerManagers with InputMaps or how to make them participate in NetBeans selection model.

We are now ready to start reviewing some of the most important NetBeans UI components, and how to use them in our Swing applications. But that will be just another section.

7 NetBeans UI Components (I)

Since NetBeans is based on Swing you can use all Swing components you have always used in a NetBeans based application, you don't need to do anything special about them. In fact you can use any Swing component library out there (and there're a few!).

Even if you use plain Swing components in your user interface, or if you use commercial or open source libraries, you may benefit of the advanced UI components that the NetBeans Platform has to offer. In this section (and in the next one) we'll be reviewing some of the more important NetBeans UI components.

7.1. The IconView component

The IconView is probably the easier of NetBeans views. This view has been recently (as 2011) updated in the NetBeans sources.

Using this view is very simple: just create a new one ("new IconView()") and add it to your user interface. You're done.

The view will automatically seek for an appropriate ExplorerManager in the component hierarchy, and will show the nodes in the ExplorerManager as a set of visual icons.

7.2. The BeanTreeView component

beantreeview.png

One of the more important views in the NetBeans UI Component library is the BeanTreeView. This works like a JTree in Swing.

To use one of these in your application you just have to add it to your user interface, and you're all set:

    // Add a BeanTreeView
    beanTreeView = new BeanTreeView();
    beanTreeView.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
    pnlBeanTreeViewContainer.add(beanTreeView, BorderLayout.CENTER);

The BeanTreeView will pair itself with the first ExplorerManager.Provider that exists in the component hierarchy, and will render its contents as a tree. You can expand the nodes (the QueryNode, for instance) and you will then see the way the children are populated lazily (using the ChildFactory we implemented in previous sections). It's simple, isn't it?

If you right-click on the "QueryNode" node you'll see a popup menu with a Reload menu item. We'll get to that in later sections of the tutorial.

7.3. The ListView component

ListViews are used to render Nodes like lists, and are thus similar to the Swing JList component. To use a ListView just add it to your component hierarchy and again it will pair itself with the first ExplorerManager.Provider in the component hierarchy. You're done.

If you launch the sample application using Java Web Start (use "javaws http://swingnbrcp.kenai.com/launch.jnlp" as Kenai may not properly set the JNLP mime type) you can see the ListView in action. Note that there's a weird behaviour if you double click on some nodes. We'll get to that in later sections (when we learn about actions).

7.4. Other views

There are more views you can use, such as the ContextTreeView or the ChoiceView or the MenuView that we won't be reviewing in these notes. You're welcome to experiment with those in the sample application, though!.

In the next section we'll review one of the more powerful and advanced NetBeans components: the OutlineView that works much like a "TreeTable", this is, a combination of a tree and a table. It's so important that it deserves a whole section.

7.5. Summary

In this section we've quickly seen different NetBeans UI components, how easy it is to use them and how they attach to the appropriate ExplorerManager.Provider in the component hierarchy.

In the next section we'll see another very important NetBeans component: the OutlineView.

8 UI Components (II): OutlineView and PropertySheetView

The OutlineView and the PropertySheetView are two other great UI components in the NetBeans Platform, and are very easy to use once you get the idea. This section explains how to use them.

views.png

Both OutlineView and PropertySheetView are used to display Properties of Nodes. In an OutlineView each column of the table is a Property of a node, whereas in a PropertySheetView you have different properties grouped in PropertySets. In our sample application we have defined two properties for TweetNodes: one for the date the tweet was sent and another one for the author of the Tweet.

Before learning how to use these components let's quickly review how to define properties for nodes.

8.1. Adding properties to Nodes

So let's add two properties to our TweetNode: the author of the tweet and the date. Node properties are grouped in "sets of properties" (basic properties, advanced properties, etc.) and these "sets" are again grouped in a "sheet" of properties. To add the "tweet date" and "author" to our "TweetNode" we have to override the "crateSheet()" method, like so:

  /**
   * This method is invoked once to create a Sheet of Properties of this node.
   * @return A Sheet of Properties for this node.
   */
  @Override
  protected Sheet createSheet() { 

    // Create an empty sheet
    Sheet sheet = Sheet.createDefault(); ①
    // Create a set of properties
    Sheet.Set set = Sheet.createPropertiesSet(); ②

    // Define properties and add them to set (see below)

    // Add the set of properties to the sheet
    sheet.put( set ); ③

    return sheet;
  }

① ... here we create a "Sheet" to hold sets of properties...

② ...here we create the set of properties...

③ ...and here we add the set to the sheet and return it...

Now comes the tricky part: we have to add properties to the set of properties. Let's see how we add a "Date" property:


   // The Date read-only property
    Property tweetDateProperty = 
      new PropertySupport.ReadOnly<Date>( 
          ① DATE_PROPERTY, // Name of the property 
          ② Date.class,    // Type of property value 
          ③ "Tweet date",  // Display name
          ④ "The date where the tweet was sent" // Description
          ) {
      @Override
      public Date getValue() 
        throws IllegalAccessException, InvocationTargetException {
        // Returns this node's entity date
        return new Date( tweet.getTimestamp() ); ⑤
      }

    };
    set.put( tweetDateProperty ); ⑥

To define properties for nodes we use the PropertySupport class. Since our property is read-only (nobody would want to change the tweet data) we use the PropertySupport.ReadOnly base class. Of course Node properties can be read-write, so you can edit the properties of a node in the PropertySheetView or in the OutlineView, but this is not covered in these notes. Let's review the code above:

① All properties have a "name". I define the names of the properties as a constant. This will have importance later on.

② The second argument to the constructor is the type of the value of the property. In our case the type of the Tweet date is java.util.Date, so we pass here a Date.class.

③ All properties have also a "displayName". This "displayName" is used in the PropertySheetView component as the "visual name" of the property.

④ And all properties also have a "short description". This short description is used in the PropertySheetView as a guide to the user.

⑤ Also all properties have a value. What we do here is we obtain the Tweet entity that the TweetNode represents and just return its date.

⑥ Finally we just add the property to the set of properties.

And we're all set! The OutlineView will attach to the first ExplorerManager.Provider in the component hierarchy and will render its "rootContext" automagically. As you can see it's not that complicated after all. Of course, you can add some extra features. You can add custom editors to properties, so the user can edit them visually. To do that you just have to override the getPropertyEditor()" of the Property and specify a custom PropertyEditor of your liking, such as this one for float values.

For more information about Properties, PropertyEditors and all this stuff you can see this JavaBeans Learning Trail.

8.2. The OutlineView

An OutlineView works like a mix between a JTree and a JTable, this is, it allows you to display hierarchical information (such as our relationship between a Query and its Tweets) and, for each one of the items you can show some of its properties as columns.

Using an OutlineView in your Swing applications is very easy: just create a new one, customize the columns and add it to a JPanel or any other Swing container. Like so:

    // Create an OutlineView with some properties and add it to this window.
    outlineView = new OutlineView();
    outlineView.getOutline().setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
    outlineView.setPropertyColumns(
        TweetNode.DATE_PROPERTY, "Tweet date", ①
        TweetNode.AUTHOR_PROPERTY, "Author"); 
    pnlOutlineViewContainer.add(outlineView, BorderLayout.CENTER);

① The important method here is "setPropertyColumns": this one allows you to specify the properties you want to show and the name of the columns. You can add as many properties and column names as you want. In our case we want to add the DATE_PROPERTY property (note how we use a property name) with column title "Tweet date" and the AUTHOR_PROPERTY (again a property name) with the column title "Author").

Note that the OutlineView acknowledges lazy-loading of children nodes, because it does not require all data to be ready before rendering (like JTable and TableModel do). This means that if you group children nodes in sets that are loaded lazily you can show a huge amount of information using this Swing component. Cool, isn't it?

8.3. PropertySheetView

The PropertySheetView is, as its name implies, a visualizer for PropertySheets. This is a very advanced Swing component, because it acknowledges the java.beans.PropertyEditorManager and uses the registered PropertyEditors to edit node properties. This means that you can build your own spiffy "Date Editor" (or use one from a good component library) and you can build a PropertyEditor to edit property dates with it, for instance. Or you could build your own Float Property Editor to allow your user to edit float values, for instance, and use that editor in a PropertySheetView.

Using a PropertySheetView in your Swing application is, again, very simple: you just have to add it to a JPanel or elsewhere and you're all set:

    // Create a PropertySheetView and add it to this window
    propertySheetView = new PropertySheetView();
    pnlPropertySheetContainer.add(propertySheetView, BorderLayout.CENTER);

The PropertySheetView will attach itself to the first ExplorerManager.Provider in the component hierarchy and will render its data.

8.4. Summary

So in this section we've seen two of the most powerful UI Components in the NetBeans Platform: the OutlineView and the PropertySheetView. During the process we've seen how to define Node properties, and we've seen that these properties can be edited with custom PropertyEditors.

We are now ready to see how to add actions to nodes. But that will be another section...

9 Action!

If you know Swing you already know Actions and AbstractActions so I won't talk about them here (if you don't know about Swing actions you can see this tutorial). Just let me say that the NetBeans Platform has lots of cool types of actions, ready to use, but we'll cover only some of the more important topics here.

actions.png

For instance, you have the ContextAwareAction, an action that is automagically enabled or disabled depending on the contents of the "current context" (usually what the user has selected). This special type of action is very useful in big applications, when you want to enable or disable components (menus, menu items, buttons, etc.) automatically depending on the current selection. We won't be covering ContextAwareActions here, though (because we don't cover the "current selection" either.

9.1. Actions and nodes

All NetBeans Nodes (remember that nodes are responsible for the visualization of one or more entities) have a set of Actions. These actions are usually presented to the user via a popup menu when the user right-clicks on a node, and may have a key binding, an icon, a title and a short description, for instance.

The exact actions that a Node returns may depend on the context (the "global selection") or on the node itself. You decide which actions you want to return for each Node, by overriding the getActions(boolean) method. In our little example application we define a "ReloadAction" for out "QueryNode", this action will be reponsible for refreshing the tweets of the query entity, like so [2]:

  @Override
  public Action[] getActions(boolean context) {
    // A list of actions for this node
    return new Action[]{new ReloadAction(getLookup())};
  }

The NetBeans machinery will build a popup menu for you automagically, so when the user right clicks on a node each action will be included in a popup item in the popup menu. Of course you specify the text of the popup item within the constructor of the Action itself, like so:

    public ReloadAction( Lookup abilities )
    {
      ...
      putValue( AbstractAction.NAME, "Reload");
      putValue( AbstractAction.SHORT_DESCRIPTION, "Reloads this object");
      ...
    }

What we do here is to return a single "ReloadAction", and we build this action using the set of abilities of our "QueryNode", as returned by the "getLookup()" method. Let's see why!

9.2. Actions and abilities

One of the easier ways to create actions is to use the Node's (or entity's) abilities that you already keep in the Lookup of your node (entity). So, for instance, if you have an "Openable" ability with an "open()" method then creating a "OpenAction" is a piece of cake: you just invoke the "open()" method and you're all set. Or if you have a "ReloadableNode" ability with a "reloadChildren()" method then creating a "ReloadAction" is very easy, right?

In fact you may think of "Actions" as visual representations of abilities, and you can relate them both naturally. A "Saveable" ability with a "save" method is naturallly related to a "SaveAction". And, in our case, a "NodeReloadable" ability (as we defined earlier) is naturally related to our "ReloadAction".

What we do in our Action is simple: we just pass it a list of abilities (a "Lookup" object) and we seek for the "ReloadableNode" ability in the list, if the ability is in there then we'll invoke the "reloadChildren" when the actionPerformed in invoked.

Note that the ReloadAction is not coupled to the exact type of node, as it depends only on a standard set of abilities (a "Lookup" object). This allows us to decouple Nodes from Actions, as Actions depend on the abilities we included in the Lookup and not on the exact type of node. This is a very important and a extremely powerful idiom.

The code is as follows:

public final class ReloadAction extends AbstractAction
{
  private ReloadableNode reloadableNode;

  /**
   * Constructor from Lookup
   * @param lookup the set of abilities (of a node, an entity or whatever).
   */
  public ReloadAction( Lookup lookup )
  {
    // Get the ReloadableNode ability
    reloadableNode = lookup.lookup( ReloadableNode.class );
    // Add some Action specific parameters (we could add an icon too)
    putValue( AbstractAction.NAME, "Reload");
    putValue( AbstractAction.SHORT_DESCRIPTION, "Reloads this object");
  }
  // Continued below...

See? We pass the constructor of our ReloadAction a list of abilities (a Lookup) and we extract the "ReloadableNode" ability and keep it in a member variable. Now when the user presses the button or selects a popup item the "actionPerformed" method will be invoked, and...

  // ... continued from above
  public void actionPerformed(ActionEvent e) {
    // If the "ReloadableNode" ability is non empty we just invoke
    // the "reloadChildren" method to refresh the list of children.
    if( reloadableNode!= null )
      try {
          reloadableNode.reloadChildren();
      } catch (Exception ex) {
          Exceptions.printStackTrace(ex);
      }
  }
}

... we just invoke the "reloadChildren" method if the ability is in there.

Oh, come on, it's much simpler than it seems!

9.3. Summary

In this section we've reviewed some topics of the Actions API. We've seen that Nodes have Actions and that Actions can react to a global selection context or can be coupled to the abilities (of nodes or entities). Basically Actions are visual representations (with a display name, icon or short description) of the abilities of the capabilities of nodes and entities.

On the next section we'll summarize what we've explained in these series of articles, and we'll finish this introduction. Comments are, of course, welcome!


[2] The NetBeans' AbstractNode base class already returns a default array of actions for all nodes. You may want to include these in your list of actions (by getting the list using "super.getActions(boolean)". We haven't done so in this simple example, though.

10 Summary

I'm currently working on this section. Keep tuned!

11 Alphabetical Index

A

Abilities

and actions, Actions and abilities

Ability

Adding abilities to entities, Adding abilities to our entities

Adding abilities to nodes, Associating the QueryNode and the QueryNodeChildFactory

Creating an ability, Creating an ability

Seeking the abilities of an object, Querying objects for its abilities

Actions

and abilities, Actions and abilities

and nodes, Actions and nodes

C

client-side data model, Client-side data model, entities

D

data access objects, Data Access Objects

E

ExplorerManager

ExplorerManager.Provider in the component hierarchy, ExplorerManager providers

populating with data, Adding data to the ExplorerManager

Selection events, Handling events

J

jar files, Getting started

L

Lookup

Adding abilities to entities, Adding abilities to our entities

Combining abilities of two or more objects, Creating a leaf node with abilities

Creating an ability, Creating an ability

Seeking the abilities of an object, Querying objects for its abilities

N

Nodes and actions, Actions and nodes

Creating a leaf node, Creating a leaf node with abilities
Creating nodes with children, asynchronously, Creating Nodes with Children, asynchronously

Creating nodes with children, synchronously, Creating Nodes with Children, synchronously

declaring properties of nodes, Adding properties to Nodes

Definition, What is a Node?

U

UI Components

BeanTreeView, The BeanTreeView component

IconView, The IconView component

ListView, The ListView component

Other views, Other views

OutlineView, The OutlineView

PropertySheetView, PropertySheetView


http://www.antonioshome.net/kitchen/swingnbrcp/index.php#swingnbrcp-introduction