Programmatically Expanding & Collapsing Nodes

Let's say you have two toolbar buttons, one for expanding and the other for collapsing the currently selected node, as shown below, within the black box drawn on the screenshot:

How to, from the two toolbar buttons above, expand/collapse the currently selected node?

The answer is very similar to "Add Widget from Action in Toolbar". Start by creating two interfaces, one for expanding and the other for collapsing:

public interface Expandable {
    void expand();
}
public interface Collapsible {
    void collapse();
}

Next, put implementations of the above into the Lookup of the TopComponent that displays the Node hierarchy. So, start by going to the TopComponent and, within the TopComponent (since that's where you have access to the BeanTreeView and to the ExplorerManager that controls the Node hierarchy) implement your two interfaces:

private class CollapsibleMovieNodes implements Collapsible {
    @Override
    public void collapse() {
        myBeanTreeView.collapseNode(myExplorerManager.getSelectedNodes()[0]);
    }
}

private class ExpandableMovieNodes implements Expandable {
    @Override
    public void expand() {
        myBeanTreeView.expandNode(myExplorerManager.getSelectedNodes()[0]);
    }
}

Then, put those two implementations into the Lookup of the TopComponent:

associateLookup(new ProxyLookup(
        Lookups.fixed(new CollapsibleMovieNodes(), new ExpandableMovieNodes()),
        ExplorerUtils.createLookup(manager, map)));

Finally, now, you can get the two implementations from the Lookup, anywhere in your application, and just call the method on it. For example, here is an Action that can be invoked from a toolbar button to expand the currently selected Node:

@ActionID(
    category = "Edit",
    id = "com.mit.moview.controler.ExpandNodeAction")
@ActionRegistration(
    iconBase = "com/mit/moview/controler/expandIcon.png",
    displayName = "#CTL_ExpandNodeAction")
@ActionReference(
    path = "Toolbars/ExpCol", 
    position = 3330)
@Messages("CTL_ExpandNodeAction=Expand Node")
public final class ExpandNodeAction implements ActionListener {

    private final Expandable context;

    public ExpandNodeAction(Expandable context) {
        this.context = context;
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
        context.expand();
    }
 
}

What's cool about the above is that if there's no Expandable in the Lookup, e.g., when the Properties window or Output window are selected, the toolbar button will automatically be disabled and greyed out.

And here's the solution in the form of a screenshot, showing that the Actions can be in different modules to where the View is found, and are loosely coupled from each other, since the module providing the Actions does not need a dependency on the module providing the View, since they are loosely coupled because they interact via the two capabilities:

But what about if the toolbar button (i.e., the underlying Action) should only be enabled if a Node has children? In other words, if a Node cannot be expanded because it does not have child Nodes, then the toolbar button should not be enabled. In that case, you need to do a bit more work because you want to have access in your Action to two different objects: the Node, as well as the Expandable. That's where you need to use a CookieAction and let the Action be enabled eagerly, i.e., not via the Action*. factories. Here's the solution:

@ActionID(
    category = "Edit",
    id = "com.mit.moview.controler.ExpandNodeAction")
@ActionRegistration(
    lazy = false,
    displayName = "not-used")
@ActionReference(
    path = "Toolbars/ExpCol",
    position = 3330)
public final class ExpandNodeAction extends CookieAction {
    private final Lookup context;
    private Expandable expandable;
    public ExpandNodeAction() {
        context = Utilities.actionsGlobalContext();
    }
    @Override
    protected boolean enable(Node[] activatedNodes) {
        if (context.lookup(Expandable.class) != null
                && context.lookup(Node.class) != null
                && !context.lookup(Node.class).isLeaf()) {
            this.expandable = context.lookup(Expandable.class);
            return true;
        }
        return false;
    }
    @Override
    protected int mode() {
        return CookieAction.MODE_ONE;
    }
    @Override
    protected Class[] cookieClasses() {
        return new Class[]{Node.class, Expandable.class};
    }
    @Override
    protected void performAction(Node[] nodes) {
        if (expandable != null) {
            expandable.expand();
        }
    }
    @Override
    public String getName() {
        return null;
    }
    @Override
    protected String iconResource() {
        return "com/mit/moview/controler/expandIcon.png";
    }
    @Override
    public HelpCtx getHelpCtx() {
        return HelpCtx.DEFAULT_HELP;
    }   
}

Finally, what if the toolbar button for expanding should be disabled when a Node has already been expanded? Create a new capability, NodeExpandable, introduce it into the Lookup of the Node whenever it is expanded, remove it again when it is collapsed. Then, in the Action, check for the presence/absence of that capability and enable/disable accordingly.

Comments:

Thanks for tutorial. But I have one problem. I want to call method for expand node after explorerManager().setRootContext(). And this way method for expand doesn't work. I think that this method create own thread and lock my beanTreeView, so I can't use method for expanding nodes.
I tried call this method after with WindowManager.invokeWhenUIReady() or EventQueue.invokeLater() but nothing helps...

Posted by guest on October 16, 2013 at 02:58 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today