NetBeans Nodes, Explorer Manager, and Component Palette Tutorial
In this tutorial, you will learn how to create a BeanTreeView with drag and drop
functionality. You will view the nodes in a variety of explorer views and create
a Component Palette containing items created from your nodes.
The main points of this tutorial are threefold:
- To demonstrate how a fully functional yet simple BeanTreeView is created.
- To demonstrate a key feature in the NetBeans APIs, that of loosely coupled views.
- To demonstrate how nodes can be turned into items in a Component Palette,
without the intervention of a layer.xml file.
Once this tutorial is finished, you will have a BeanTreeView that provides
the following menu items, in addition to the support for dragging and dropping
the child nodes:
You will be shown how to change the explorer view from BeanTreeView
to MenuView, ListView, and several other views supported
by the Explorer and Property Sheet API.
This is what the completed project will look like in the Projects window:
Installing the Software
Before you begin, you need to install the following software on your
computer:
- Because of issue 74984,
you must use a NetBeans IDE 5.5 development build from after
12 September 2006. (NetBeans IDE 5.0 or 5.5 Beta 2 will not be good enough.)
Click here
to get the latest build.
- Java Standard Development Kit (JDK™) version
1.4.2 (download)
or 5.0 (download)
Also, you will use 4 icons in the tutorial. You can right-click
them here and save them locally, then copy them to the module project's
location, after you create the module project later in this tutorial. Here are
the 4 icons:
Getting Started
In this section, we use wizards to create a module project and
a custom window component. We add a JScrollPane and create a new
BeanTreeView.
We then install the module project and display
our view's top node.
- Choose File > New Project. In the New Project wizard,
choose NetBeans Plug-in Modules under Categories and Module Project
under Projects. Click Next. Type ExplorerBeanTreeView in Project Name
and set Project Location to an appropriate folder on your disk. If they
are not selected, select
Standalone Module and Set as Main Project. Click Next.
- Type org.netbeans.myfirstexplorer in Code Name Base
and Explorer Bean Tree View in Module Display Name. Click Finish.
- Right-click the project, choose Properties, click Libraries
in the Project Properties dialog box and declare a dependency on the following APIs:
- Right-click the module project, choose
New > File/Folder and choose Window Component from the
NetBeans Module Development category. Click Next. Choose editor
in the drop-down list and select Open on Application Start. Click Next.
- Type MyFirst in Class Name Prefix. Optionally,
add an icon with a dimension of 16x16 pixels. Click Finish.
- Open MyFirstTopComponent.java in the Design view.
Right-click in the TopComponent, choose Set Layout,
and select BorderLayout.
- Use the Palette (Ctrl-Shift-8) to drop a
JScrollPane on MyFirstTopComponent.java. Resize the
JScrollPane so that it covers the entire TopComponent. Right-click
the JScrollPane, choose Change Variable Name and type moviePane.
- Open the Inspector, if it isn't open. (Use the Window menu.)
In the Inspector, select the moviePane, open the
Properties window (Ctrl-Shift-7), click the "Code" tab, and add this
line to the Custom Creation Code property (the very last property in the list):
new BeanTreeView();
Click OK.
BeanTreeView is one of several views provided by the Explorer and Property Sheet API.
We will look at the other views later in this tutorial.
- Click the Source toggle button in the GUI Builder.
Right-click in the Source Editor,
and choose Fix imports. The dependency you set on "Explorer and Property Sheet API" will
cause the import statement for the BeanTreeView being generated for you by the IDE.
- In the Bundle.properties file, change the CTL_MyFirstTopComponent
key to the value "Marilyn Explorer".
- Right-click the project node and choose "Install/Reload
in Development IDE". If a warning message appears, click OK.
When the module installs, look under the Window menu and you will find a new menu item
called "Open MyFirst Window", at the top of the list of menu items.
Choose it and you will see the start of your explorer view:
Without any coding, you have created the start of your BeanTreeView.
Using Nodes to Represent Data
Using nodes to represent data involves adding an Explorer Manager
to our TopComponent's Lookup. The Explorer Manager controls the view, especially
the top node of our view. Next we will create a Category object and a Movie object and display
each via a separate AbstractNode
object and Children.Keys object. The AbstractNode provides the
basic implementation of a node, allowing you to provide icons, as well
as actions such as Cut and Delete. With the Children.Keys object, you simplify
the handling of the nodes, using lightweight keys instead of the nodes themselves.
Further details on the specific approaches taken are explained where appropriate below.
Setting Up an Explorer Manager
An Explorer Manager
is the NetBeans API class that manages a view. For example, it notifies PropertyChangeListeners
about changes to the nodes in the view. It also controls the root node
in a view. For example, when you want to define a root node, you need
to use the Explorer Manager's setRootContext() method.
To provide an Explorer Manager in a TopComponent, you
must implement ExplorerManager.Provider, as shown in this subsection.
- Open the MyFirstTopComponent.java in the
Source view and add implements ExplorerManager.Provider
to the signature at the top of the class.
- Next, instantiate the ExplorerManager as a transient object:
private transient ExplorerManager explorerManager = new ExplorerManager();
- Place the cursor in the signature.
A lightbulb will prompt you to let the IDE insert an import statement and implement the abstract methods.
Follow its advice, by clicking on the suggestion,
and then fill out the generated getExplorerManager() as follows:
public ExplorerManager getExplorerManager() {
return explorerManager;
}
- Now go to the Constructor and add the following after the last existing line:
associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap()));
explorerManager.setRootContext(new AbstractNode(new CategoryChildren()));
explorerManager.getRootContext().setDisplayName("Marilyn Monroe's Movies");
Here we place the Explorer Manager in the TopComponent's Lookup.
We set a class called "CategoryChildren" as the root node. We will create
this class in the next section, and we will display it
as the first node in our view. As display name it receives "Marilyn Monroe's Movies".
- Fix imports. A red underline will remain because we have not created
the CategoryChildren class yet. We will do so in the next section.
Creating a Category
Let's first define what a "Category" is.
- Create a class
called Category.java and add the following content:
public class Category {
private String name;
/** Creates a new instance of Category */
public Category() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
From the above, you can see that a category has a name, and nothing more.
- Create
another class, this time for creating the nodes for the categories:
public class CategoryChildren extends Children.Keys {
private String[] Categories = new String[]{
"Adventure",
"Drama",
"Comedy",
"Romance",
"Thriller"};
public CategoryChildren() {
}
protected Node[] createNodes(Object key) {
Category obj = (Category) key;
return new Node[] { new CategoryNode( obj ) };
}
protected void addNotify() {
super.addNotify();
Category[] objs = new Category[Categories.length];
for (int i = 0; i < objs.length; i++) {
Category cat = new Category();
cat.setName(Categories[i]);
objs[i] = cat;
}
setKeys(objs);
}
}
In this example, a popular children implementation called Children.Keys is used.
By subclassing Children.Keys, you need not explicitly keep track of the nodes.
Instead, you keep track of a set of keys, which are lighter
weight objects. Each key typically represents one node. You must tell the implementation how
to create a node for each key. You can decide for yourself what type of keys to use.
addNotify() is called the first time that a list of nodes is needed.
An example of this is when a node is expanded. Here, when addNotify() is called,
a new category is instantiated. When a child node needs to be constructed,
the createNodes() method is called. It is passed the key for which it is making a node. It returns either none,
one, or more nodes corresponding to what should be displayed for the key.
In this example, a new instance of one category node is being created, and the
key is passed into its constructor.
- Fix imports, choosing org.openide.nodes.Children and org.openide.nodes.Node.
Note that in the code above,
we create a node called CategoryNode. We will create it in the next step.
- Create a class called CategoryNode.java
and define it as follows:
public class CategoryNode extends AbstractNode {
/** Creates a new instance of CategoryNode */
public CategoryNode( Category category ) {
super( new MovieChildren(category), Lookups.singleton(category) );
setDisplayName(category.getName());
setIconBaseWithExtension("org/netbeans/myfirstexplorer/marilyn_category.gif");
}
public PasteType getDropType(Transferable t, final int action, int index) {
final Node dropNode = NodeTransfer.node( t,
DnDConstants.ACTION_COPY_OR_MOVE+NodeTransfer.CLIPBOARD_CUT );
if( null != dropNode ) {
final Movie movie = (Movie)dropNode.getLookup().lookup( Movie.class );
if( null != movie && !this.equals( dropNode.getParentNode() )) {
return new PasteType() {
public Transferable paste() throws IOException {
getChildren().add( new Node[] { new MovieNode(movie) } );
if( (action & DnDConstants.ACTION_MOVE) != 0 ) {
dropNode.getParentNode().getChildren().remove( new Node[] {dropNode} );
}
return null;
}
};
}
}
return null;
}
public Cookie getCookie(Class clazz) {
Children ch = getChildren();
if (clazz.isInstance(ch)) {
return (Cookie) ch;
}
return super.getCookie(clazz);
}
protected void createPasteTypes(Transferable t, List s) {
super.createPasteTypes(t, s);
PasteType paste = getDropType( t, DnDConstants.ACTION_COPY, -1 );
if( null != paste )
s.add( paste );
}
public Action[] getActions(boolean context) {
return new Action[] {
SystemAction.get( NewAction.class ),
SystemAction.get( PasteAction.class ) };
}
public boolean canDestroy() {
return true;
}
}
An AbstractNode is a basic implementation of a node. It simplifies common
requirements, such as the creation of the display name and the handling of icons.
Other common requirements are handled as well. To understand what each of the methods
in the code above does,
click the method's link to jump to the related Javadoc.
- Fix imports. After you fic the import statements, several red underlines
will remain, because we have not created Movie.java, MovieChildren.java,
and MovieNode.java. yet. We will do so in the next section.
Creating a Movie
Next, we'll work on adding the children belonging to the categories.
And the children are movies. Let's begin by defining what a "movie" is.
- Create a
class called Movie.java, with the following content:
public class Movie {
private Integer number;
private String category;
private String title;
/** Creates a new instance of Instrument */
public Movie() {
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
From the above, you can see that a movie has a number, belongs to a category, and has a title.
- Now let's create the category's children. The
class to be created is called MovieChildren.java. We use
Index.ArrayChildren,
so that we can put the nodes in an array list, which is loaded as needed. Until a
child node is needed, such as when the parent node is expanded, it is not created. This is the content of the class:
public class MovieChildren extends Index.ArrayChildren {
private Category category;
private String[][] items = new String[][]{
{"0", "Adventure", "River of No Return"},
{"1", "Drama", "All About Eve"},
{"2", "Drama", "Home Town Story"},
{"3", "Comedy", "We're Not Married!"},
{"4", "Comedy", "Love Happy"},
{"5", "Romance", "Some Like It Hot"},
{"6", "Romance", "Let's Make Love"},
{"7", "Romance", "How to Marry a Millionaire"},
{"8", "Thriller", "Don't Bother to Knock"},
{"9", "Thriller", "Niagara"},
};
public MovieChildren(Category Category) {
this.category = Category;
}
protected java.util.List<Node> initCollection() {
ArrayList childrenNodes = new ArrayList( items.length );
for( int i=0; i<items.length; i++ ) {
if( category.getName().equals( items[i][1] ) ) {
Movie item = new Movie();
item.setNumber(new Integer(items[i][0]));
item.setCategory(items[i][1]);
item.setTitle(items[i][2]);
childrenNodes.add( new MovieNode( item ) );
}
}
return childrenNodes;
}
}
- Right-click the project, choose Properties, and use the Sources category
to change the source level from 1.4 to 1.5. Click OK.
- Fix imports. A red underline will remain because we have
not create MovieNode.java, which we will do in the next step.
- Create a class called MovieNode.java
and define it as follows:
public class MovieNode extends AbstractNode {
private Movie movie;
/** Creates a new instance of InstrumentNode */
public MovieNode(Movie key) {
super(Children.LEAF, Lookups.fixed( new Object[] {key} ) );
this.movie = key;
setDisplayName(key.getTitle());
setIconBaseWithExtension("org/netbeans/myfirstexplorer/marilyn.gif");
}
public boolean canCut() {
return true;
}
public boolean canDestroy() {
return true;
}
public Action[] getActions(boolean popup) {
return new Action[] {
SystemAction.get( CopyAction.class ),
SystemAction.get( CutAction.class ),
null,
SystemAction.get( DeleteAction.class ) };
}
}
Fix imports.
Notice that most of this class is about defining actions on
the movie nodes. When you right-click a movie, you'll be
able to choose "Copy" or "Cut" or "Delete".
Creating a Root Node
Now we are going to install our module. When we do so, we will test
our module's functionality and see if everything is as we would want it to be.
- Right-click the module and choose Install/Reload in Development IDE.
- Examine the result:
- Notice that even though you can drag and drop movies from
one category to another (by dragging with your mouse, with the Ctrl
key held down when you want to copy a node),
the menu items are greyed out. Also, notice that
the root node does not have an icon.
- First, we need to enable the menu items by adding the actions
to the TopComponent's action map. Do this by adding the following
snippet to the end of the TopComponent's Constructor:
ActionMap map = getActionMap();
map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(explorerManager));
map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(explorerManager));
map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(explorerManager));
map.put("delete", ExplorerUtils.actionDelete(explorerManager, true));
- Next, to be able to control the icon displayed by the root node,
we need to create a class for that node. Currently, we are using a default
AbstractNode, over which we have no control.
Create a class called RootNode.java, with this content:
public class RootNode extends AbstractNode {
/** Creates a new instance of RootNode */
public RootNode(Children children) {
super(children);
}
public Image getIcon(int type) {
return Utilities.loadImage("org/netbeans/myfirstexplorer/right-rectangle.png");
}
public Image getOpenedIcon(int type) {
return Utilities.loadImage("org/netbeans/myfirstexplorer/down-rectangle.png");
}
}
Notice that here we set one icon for when the node is in its closed state
and another for when it is expanded. To use this node, we need to change this line
in the TopComponent:
explorerManager.setRootContext(new AbstractNode(new CategoryChildren()));
We need to replace that line with this line:
explorerManager.setRootContext(new RootNode(new CategoryChildren()));
- Install the module again and notice the icons displayed for the root node's
collapsed and expanded states. Here, the icon for the expanded state is shown:
Also notice that the movie node's menu items are now enabled and functional.
Using Explorer Views to View Data
The NetBeans APIs provide a variety of explorer views, which are very
simple to add to your TopComponent. After adding one or two lines of code,
the view on your data can be completely different, creating a radically
altered display for your end users and a wide range of choices for you
and your development team.
However, note that only the BeanTreeView supports
the drag and drop functionality you added earlier in this tutorial. When you
change to a different explorer view, as shown below, the drag and drop functionality
will simply be disabled.
List View
List view is an explorer view that displays items in a list. It is provided by
the ListView
class, which belongs to the Explorer And Property Sheet API.
- Add this line to the end of the TopComponent's Constructor:
listView = new ListView();
Put the cursor in the line and let the IDE generate
an import statement for org.openide.explorer.view.ListView.
Also let the IDE create the listView field.
- Below the line above, add this line, which adds the view to the TopComponent:
add(listView, BorderLayout.CENTER);
Let the IDE generate the java.awt.BorderLayout import statement for BorderLayout.
Note: When you created the TopComponent earlier in this tutorial,
you should have set the layout manager to BorderLayout. If you did not do this,
make the JScrollPane smaller, right-click the TopComponent, choose Set Layout,
and select BorderLayout.
- Install the module again. Notice that the view is now as follows:
When you click on a category, the movies are displayed:
Choice View
Choice view is an explorer view based on a combo box. It is provided by
the ChoiceView
class, which belongs to the Explorer And Property Sheet API.
- Add this line to the end of the TopComponent's Constructor:
choiceView = new ChoiceView();
Put the cursor in the line and let the IDE generate
an import statement for org.openide.explorer.view.ChoiceView.
Also let the IDE create the choiceView field.
- Instead of the line that
adds a ListView to the TopComponent, write a line that
adds the ChoiceView:
add(choiceView, BorderLayout.CENTER);
- Install the module again. Notice that the view is now as follows:
Note: If your TopComponent is very large, the combo box
provided by the choice view will be very large as well.
Menu View
Menu view is an explorer view that displays the hierarchy
of nodes in a popup menu. Initially, it shows a left button which
opens a popup menu from the root context and a right button which
opens a popup menu from the currently explored context. It is provided by
the MenuView
class, which belongs to the Explorer And Property Sheet API.
- Add this line to the end of the TopComponent's Constructor:
menuView = new MenuView();
Put the cursor in the line and let the IDE generate
an import statement for org.openide.explorer.view.MenuView.
Also let the IDE create the menuView field.
- Instead of the line that
adds a ChoiceView to the TopComponent, write a line that
adds the MenuView:
add(menuView, BorderLayout.CENTER);
- Install the module again. Notice that the view is now as follows:
When you click on the first button, the complete list of categories is displayed:
When you click with the right mouse button on the "Browse from root" button,
the "Browse from current point" button is enabled and you can browse
to movies within the selected category:
Other Views
The org.openide.explorer.view package provides many other
explorer views, in addition to those outlined above. For example, IconView
presents the categories and its contents as icons:
Other views include ContextTreeView
and ListTableView.
Finally, a TreeTableView
could also be used. This NetBeans API class lets you
create a view tree of nodes on the left and its properties in a table on the right. This is
an area that deserves a tutorial of its own. Similarly, creating you own explorer view is a
worthwhile but complex project that will be described in a separate tutorial.
Using Palette Items to Reuse Nodes
Alternatively, the nodes can form the basis of palette items, as shown below:
In this section,
you are shown how to add the items to a Component Palette and how to add some
simple drag and drop functionality to the items in the palette. Only a brief
overview will be given here, because other tutorials exist that provide
details on the Component Palette API.
Instead of adding an Explorer Manager to the TopComponent's Lookup,
you will need to add a PaletteController.
When you do this, the Component Palette opens when the TopComponent opens, displaying
its content, consisting of palette items. PaletteController
is provided by the Core - Component Palette API.
- Right-click the project, choose
Properties, and add a dependency on Core - Component Palette in the Libraries category
of the Project Properties dialog box.
- Declare a new PaletteController
and set the root node as the palette's root:
private PaletteController palette = null;
private RootNode paletteRoot;
- In the TopComponent's Constructor, comment out the calls to the Explorer Manager.
You can also comment out the definition of the action map, since the Component
Palette automatically provides Copy, Cut, Paste, and Delete actions to palette items.
In the Inspector, select the moviePane, open the
Properties window (Ctrl-Shift-7), click the "Code" tab, and delete the
line in the Custom Creation Code property (the very last property in the list).
- At the end of the Constructor, add this line to add the Component
Palette to the TopComponent's Lookup:
associateLookup( Lookups.fixed( new Object[] {getPalette()} ));
- Here, we create a new instance of the PaletteController
and return it to the TopComponent's Lookup:
private PaletteController getPalette() {
if( null == palette ) {
paletteRoot = new RootNode(new CategoryChildren());
paletteRoot.setName( "Palette Root");
palette = PaletteFactory.createPalette( paletteRoot,
new MyPaletteActions(), null, new MyDragAndDropHandler() );
}
return palette;
}
- A palette consists of a root, a set of actions,
and a handler for drag and drop events. For purposes of this
simple example, we will set our palette actions to null:
private static class MyPaletteActions extends PaletteActions {
public Action[] getImportActions() {
return null;
}
public Action[] getCustomPaletteActions() {
return null;
}
public Action[] getCustomCategoryActions(Lookup lookup) {
return null;
}
public Action[] getCustomItemActions(Lookup lookup) {
return null;
}
public Action getPreferredAction(Lookup lookup) {
return null;
}
}
- And here is the definition of our drag and drop handler, using
the NetBeans API class DragAndDropHandler:
public static final DataFlavor MyCustomDataFlavor
= new DataFlavor( Object.class, "MyDND" );
private static class MyDragAndDropHandler extends DragAndDropHandler {
public void customize(ExTransferable exTransferable, Lookup lookup) {
final MovieNode item = (MovieNode)lookup.lookup( MovieNode.class );
if( null != item ) {
exTransferable.put( new ExTransferable.Single( MyCustomDataFlavor ) {
protected Object getData() throws IOException, UnsupportedFlavorException {
//return item.getSomeData();
return null;
}
});
}
}
}
- Install the module again. When the TopComponent opens, the new
Component Palette is shown. The categories you created in this tutorial
are now categories in the Component Palette, while the movies are items
within the categories. Next, you need to add drag and drop functionality
to the items in the palette, as described in the NetBeans Drag and Drop Tutorial.
|
|