org.openide.nodes.NodeListener

Here's a node hierarchy, with 4 selected, followed by a right-click, which results in a "Delete" menu item being shown:

Click the menu item and you see this dialog:

Click Yes and then all 4 are gone:

How to implement this?

  1. Enable the Delete Action on the ExplorerManager. The "true" below causes the "Confirm Deletion" dialog to appear.
    ActionMap map = getActionMap();
    map.put("delete", ExplorerUtils.actionDelete(controler, true));
    
    associateLookup(ExplorerUtils.createLookup(controler, map));
  2. Make the Node deletable. Make sure that the underlying object is in the Node Lookup. Then return "true" in "canDestroy", include the "DeleteAction" in "getActions", and, most important of all, call "fireNodeDestroyed" in "destroy":
    public class MansionNode extends BeanNode {
    
        public MansionNode(Mansion bean) throws IntrospectionException {
            super(bean, Children.LEAF, Lookups.singleton(bean));
            setDisplayName("Mansion: " + bean.getCity());
            setIconBaseWithExtension("com/mit/movie/viewer/resources/movie-orange-icon.gif");
        }
    
        @Override
        public boolean canDestroy() {
            return true;
        }
    
        @Override
        public Action[] getActions(boolean context) {
            return new Action[]{(SystemAction.get(DeleteAction.class))};
        }
    
        @Override
        public void destroy() throws IOException {
            fireNodeDestroyed();
        }
        
    }
  3. Implement NodeListener in the ChildFactory. The interesting thing is that when you implement "NodeListener", the "nodeDestroyed" is automatically called by "fireNodeDisplayed" in the Node's "destroy". So, the link between the Node and the ChildFactory does not need to be provided by ChangeSupport, which is how Toni did it in a cool example he made on this topic. Instead, the NodeListener, added to the Node, listens for the "fireNodeDestroyed" in the "destroy".
    public class MansionChildFactory extends ChildFactory<Mansion> implements NodeListener {
    
        private final Actor actor;
        private List<Mansion> mansions;
    
        public MansionChildFactory(Actor actor) {
            this.actor = actor;
            mansions = new ArrayList<Mansion>();
            mansions.addAll(actor.getMansions());
    
        }
    
        @Override
        protected boolean createKeys(List<Mansion> list) {
            list.addAll(mansions);
            return true;
        }
    
        @Override
        protected Node createNodeForKey(Mansion key) {
            MansionNode mansionNode = null;
            try {
                mansionNode = new MansionNode(key);
                mansionNode.addNodeListener(this);
            } catch (IntrospectionException ex) {
                Exceptions.printStackTrace(ex);
            }
            return mansionNode;
        }
    
        @Override
        public void nodeDestroyed(NodeEvent ev) {
            Mansion removedMansion = ev.getNode().getLookup().lookup(Mansion.class);
            mansions.remove(removedMansion);
            refresh(true);
        }
    
        @Override
        public void childrenAdded(NodeMemberEvent nme) {
        }
    
        @Override
        public void childrenRemoved(NodeMemberEvent nme) {
        }
    
        @Override
        public void childrenReordered(NodeReorderEvent nre) {
        }
    
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
        }
        
    }

There's only one problem with the above. Let's assume that instead of "MansionChildFactory", we have "PersonChildFactory", with a "createNodesForKey" that creates either an ActorNode or a DirectorNode. Under these circumstances, the above sequence doesn't work. In short, I don't know how to set up the above for a ChildFactory that uses "createNodesForKey" instead of "createNodeForKey".

Comments:

Excellent ! Wanted to do this for years but was always considered a low hanging fruit .... thanx

Posted by Bernd Ruehlicke on March 01, 2013 at 08:25 AM PST #

Interesting solution. The only problem/risk I can see here is that the nodes "are" the model. So changes in the model might be missed.

What I do, usualy, is that every node in my tree is backed by model object. That means that when destroy gets called on the node, I actually destroy (remove) the object represented by the node just receiving the destroy from its parent in the model.

Since the parent node is listening to changes to its children list (still in the model), it will trigger a refresh.

To sum up, any changes in attributes or children are made in the model, even from the nodes. Nodes then are just listening to these changes and refresh accordingly wherever the change came from.

Geetjan, what do you think about that? Isn't this approach saner even it requires you to create a model object for every node.

I was asking me this question because I had to artificially create intermediate nodes to represent for single business object.

Cheers,

JM

Posted by jmborer on January 13, 2014 at 02:35 AM PST #

How is that different to what's described in this blog entry?

Posted by Geertjan on January 13, 2014 at 02:59 AM PST #

It is almost the same except but with a subtlety which I find interesting and happens in:

@Override
public void destroy() throws IOException {
fireNodeDestroyed();
}

@Override
public void nodeDestroyed(NodeEvent ev) {
Mansion removedMansion = ev.getNode().getLookup().lookup(Mansion.class);
mansions.remove(removedMansion);
refresh(true);
}

The difference is that you need to fire the node destroy event and listen to it and then only change the model.

What I do usually is remove the object in the model from within destroy() method and therefore don't need to trigger a refresh, because the parent (owner of the list), observes the list and when it changes it will refresh its children. However the problem I had is that my models use lists for their children. Unfortunately expect GlazedLists, there are not lists AFAIK that trigger change events (hopefully this is fixed in JavaFX). So I had to listen to theses events myself by building appropriate models. In that way your solution sounds interesting: no more need to have list change observers. However, the problem is that if another thread changes the model (not one from the gui), the UI might miss this event and not be refreshed accordingly. Am I wrong here?

Don't get me wrong, I am not saying this method is wrong. In contrary, it is very interesting. I am just wondering about some details.

In conclusion, I would say that all approaches are interesting. Which one to use will depend on your needs and also the way you understand you code.

Cheers,

Jm

Posted by jmborer on January 13, 2014 at 03:53 AM PST #

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
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today