X

Geertjan's Blog

  • July 28, 2006

Explorer Managers and Marilyn Monroe

Geertjan Wielenga
Product Manager
An application based on the NetBeans Platform that does not extol the many virtues of Marilyn Monroe cannot truly be said to be complete. (In the middle of that sentence, I suddenly realized that the chances are fairly considerable that no one has ever written that sentence before. Or, probably, even had that thought.) Allow me to explain. Here's a new window which every NetBeans Platform application (including NetBeans IDE itself) should have:

Note: The list above is incomplete, okay, so stop writing that angry e-mail, "Marilyn Fan, Wichita".

So, let's say you want to create a window in honor of Marilyn Monroe. (Chances are that you don't want to do that. You're completely free to use the following instructions for any other reason too. Such as extoling the virtues of Demi Moore.) The window is provided by org.openide.explorer. This provides for an Explorer Manager, which is a class that manages a selection and root context for a set of explorer views, such as the Marilyn explorer view above. Here goes, complete instructions for implementing ExplorerManager.Provider:

  1. Create a module and call it whatever you like. Use the New Window Component wizard to create a component called whatever you want to call it. Maybe best to put it in the "explorer" view, but that's up to you too.

  2. In the Design view, use the Palette (Ctrl-Shift-8) to drop a JScrollPane on the window component and call it moviePane. Select the moviePane in the Projects window, 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();

    Fix imports. Oops. You can't fix imports for the BeanTreeView because you don't have a dependency on the "Explorer and Property Sheet API". Let's fix that by right-clicking the project, choosing Properties, clicking Libraries in the Project Properties dialog box and declaring a dependency on "Explorer and Property Sheet API" and "Nodes API" (we will need this later, so lets set it now so that we don't have to go back here again). Fix imports in the code again, and the dependency you set on "Explorer and Property Sheet API" will result in the import statement for BeanTreeView() being generated for you.

  3. In the Bundle.properties file, change the CTL_yourPrefixTopComponent key to the value "Marilyn". Now you can already right-click the project node and choose "Install/Reload in Development IDE". When it installs, look under the Window menu and you'll find a new menu item. Choose it and you'll see the start of your explorer view:

    How much coding have you done so far? None. And look how far you are already. Give yourself a small pat on the back before continuing.

  4. Back in the topcomponent's source code add implements ExplorerManager.Provider to the signature at the top of the class. Also declare the following line, which instantiates our ExplorerManager:

    private transient ExplorerManager explorerManager = new ExplorerManager();

    The lightbulb will nag you to implement the abstract methods. Follow its advice and then fill out the generated getExplorerManager() as follows:

    public ExplorerManager getExplorerManager() {
    return explorerManager;
    }

  5. 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");

    Fix imports.

    So here we set a class called "CategoryChildren" (which we haven't yet created) as the first node in our explorer manager. As display name it receives "Marilyn Monroe's Movies".

  6. Let's first define what a "Category" is. So 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;
    }
    }

    So a category has a name. That's it, nothing fancy. Now 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;
    AbstractNode result = new AbstractNode(new MovieChildren(obj), Lookups.singleton(obj));
    result.setDisplayName(obj.getName());
    result.setIconBaseWithExtension("org/yourorghere/myfirstexplorer/marilyn_category.gif");
    return new Node[] { result };
    }
    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);
    }
    }

    Fix imports (choose org.openide.nodes.Children and org.openide.nodes.Node).

    Javadoc and an upcoming tutorial should help you if you don't completely understand what's going on in this class.

  7. Next, we'll work on adding the children belonging to the categories. And the children are movies. So, let's first define 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;
    }
    }

    So, a movie has a number, belongs to a category, and has a title. Again, nothing too fancy. Now let's create the children. The class is called MovieChildren.java and has this content:

    public class MovieChildren  extends Children.Keys {
    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 Node[] createNodes(Object key) {
    Movie obj = (Movie)key;
    MovieNode result = new MovieNode(obj);
    result.setDisplayName(obj.getTitle());
    result.setIconBaseWithExtension("org/yourorghere/myfirstexplorer/marilyn.gif");
    return new Node[] { result };
    }
    protected void addNotify() {
    super.addNotify();
    Vector instr = new Vector();
    for (int i = 0; i < items.length; i++) {
    if ( items[i][1].equals(Category.getName()) ) {
    Movie item = new Movie();
    item.setNumber(new Integer(items[i][0]));
    item.setCategory(items[i][1]);
    item.setTitle(items[i][2]);
    instr.addElement(item);
    }
    }
    Movie[] objs = new Movie[instr.size()];
    for (int i = 0; i<instr.size(); i++) {
    objs[i] = (Movie)instr.elementAt(i);
    }
    setKeys(objs);
    }
    }

    Fix imports.

    Note that in the createNodes method, we create a node called MovieNode. So 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);
    this.movie = key;
    }
    public Action[] getActions(boolean popup) {
    return new Action[] { new Sing(), new Dance() };
    }
    public Action getPreferredAction() {
    return new Sing();
    }
    private class Sing extends AbstractAction {
    public Sing() {
    putValue(NAME, "Sing");
    }
    public void actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(null,"Lalala");
    }
    }
    private class Dance extends AbstractAction {
    public Dance() {
    putValue(NAME, "Dance");
    }
    public void actionPerformed(ActionEvent e) {
    JOptionPane.showMessageDialog(null,"Skippetyskip");
    }
    }
    }

    Fix imports.

    Notice that most of this class is about defining actions on the movie nodes. So, when you right-click a movie, you'll be able to choose "Sing" or "Dance". The getPreferredAction() sets the default action, i.e., the one that is performed on double-clicking a node.

So, that's it. Most of the code should be reasonably clear. A few bits aren't and that's why a tutorial will be created around this subject. But, with the comments given here and a bit of reading of the Javadoc, you should be okay. Also, you can download the complete sample from here. The icons used in the sample are also part of the downloadable, so if you like them you can just get them from the downloadable along with the rest of the code. I created this sample on some development version of NetBeans IDE 5.5, but I successfully tested it on 5.0, where I just had to remove the dependencies and re-add them, to refresh the API versions used in the module. So, remember that if you encounter compilation errors, just go to the Project Properties dialog box, then to the Libraries panel, and then remove the modules declared there and then re-add them.

By the way, I didn't know how to do this (i.e., create an Explorer view with nodes and subnodes), until Pierre Matthijs did his latest check in of the JFugue Music NotePad. Now all the instruments are displayed in an Explorer view, which was the basis of the sample discussed in this blog entry. Thanks, Pierre! At last I get how this whole thing works. The trickiest part was setting the BeanTreeView. You really need to dig around in the IDE to work out where to set that.

So, finally, here's to Marilyn Monroe! May she feature in your applications forever.

Join the discussion

Comments ( 8 )
  • Pierre Saturday, July 29, 2006
    Geertjan,
    After 2 days of searching, reading, trying, I came to this solution.
    And, like most things, afterwards it looks rather evident, simple ...
    It works. It does what it has to do.
    But !! Don't blame me, don't shoot at the pianist (literal), if later on it seems there are more adequate, proper, efficient, ... ways !
  • Geertjan Sunday, July 30, 2006
    Pierre, well it's working great and it is the same principle as described in a few tutorials. But I need to see something in action before I begin to understand it and so that's why seeing your code was so useful. Thanks again!
  • gabriel Friday, August 4, 2006
    Hi! i found this article very usefull, but i have a problem, when i add a lot of nodes the GUI freeze for a few seconds. could you point me any tutorial or article abouy my issue?
    thanks, and sorry for my english
  • gabriel Friday, August 4, 2006
    Hi! i found this article very usefull, but i have a problem, when i add a lot of nodes the GUI freeze for a few seconds. could you point me any tutorial or article abouy my issue?
    thanks, and sorry for my english
  • Geertjan Wednesday, August 23, 2006
    Gabriel, sorry, don't know how to solve this problem. Did you find a solution?
  • guest Saturday, September 9, 2006
    56
  • Geertjan Saturday, September 9, 2006
    Don't you mean 42? :-)
  • Nigel Sunday, February 28, 2010

    This is great, but what do I do to provide a one click action on a Node?


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