layer.xml

a netbeans rcp blog

Posts Tagged ‘Nodes’

Dynamic node edit property menu

Posted by Nicklas Löf on February 14, 2011

Hi everybody.

I’m sorry for the last months of no posts here but there hasn’t been any time for me to write any blog posts since I have been really busy at work (working with Netbeans RCP offcourse)

But here is a post I started to wrote some time ago but haven’t been able to finish until today.

So, in my application I wanted to have my users to be able to access the property editor for each of my Nodes properties without having to open up the normal Properties popup panel (which action you can access with SystemAction.get(PropertiesAction.class)). My goal was that when the user did right click on a node the menu would show me a bunch of Edit ‘Propertyname’ menu entries and when it was selected the editor for that property was showing up (the same editor that you get when clicking the “…” button in the properties panel). The screenshot below shows what I’m trying to describe in words.

So how can we achieve this?  I couldn’t find any clear examples so I did one of my favourite things..   look at the netbeans source code..  And this might not be the only way to do this and most certain not the nicest way but it works.

To start with we need to do this in our Node.java file and we need to override the following method:

public Action[] getActions(boolean context){ }

This method is called each time the user right clicks on a node in your application. And as we can see it expects to get back an Array of Actions so lets create an array with our dynamic actions:

public Action[] getActions(boolean context){
    ArrayList<Action> actionsToShow = new ArrayList<Action>();
        for (Node.PropertySet propertySet : getPropertySets()) {
            for (final Node.Property<?> property : propertySet.getProperties()) {
                if (property.canWrite() && property.getPropertyEditor().supportsCustomEditor()) {
                    actionsToShow.add(new EditPropertyAction(property, this));
                 }
           }
    }
    return (Action[]) actionsToShow.toArray(new Action[actionsToShow.size()]);
}

Ok. What is this code doing? First I’m just creating an ArrayList that I can add my Actions into. Then I’m asking my node to get the the propertyset and for each property I’m asking for the Node.Property. If Node.Property is writeable (we don’t want to show read-only properties in the menu) and that it has a customeditor (Integers doesnt have one for example) then I’m creating a EditPropertyAction that takes two arguments, the property and the node itself. Finally I’m returning the ArrayList as an Array.

Are we done yet? No we aren’t. We need to implement the EditPropertyAction.

    private static class EditPropertyAction extends AbstractAction {
        private final Node.Property<?> property;
        private final Node node;

        public EditPropertyAction(Node.Property<?> property, Node node) {
            this.property = property;
            this.node = node;
            putValue(Action.NAME, "Edit " + property.getDisplayName());
        }

        @Override
        public void actionPerformed(ActionEvent ae) {
            final PropertyPanel propertyPanel = new PropertyPanel(property, PropertyPanel.PREF_CUSTOM_EDITOR);
            propertyPanel.setChangeImmediate(true);
            propertyPanel.setProperty(property);

            final DialogDescriptor dlg = new DialogDescriptor(propertyPanel, node.getDisplayName() + " " + property.getDisplayName());

            dlg.setButtonListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    if (dlg.getValue().equals(DialogDescriptor.OK_OPTION)) {
                        try {
                            propertyPanel.updateValue();
                        } catch (Exception e1) {
                        }
                        dlg.setClosingOptions(new Object[]{dlg.getValue()});
                    } else {
                        dlg.setClosingOptions(null);
                    }
                }
            });
            Dialog createDialog = DialogDisplayer.getDefault().createDialog(dlg);
            createDialog.setVisible(true);
        }
    }

Pheew.. that was alot of code! A quick explanation of this code: An action that extends AbstractAction. It has the node itself and the property as fields which are set by the constructor. And then the Action name is set to “Edit ” and the displayname of the property.

Lets move on to the actionperformed. This is what is called by the application when the user selects the entry in the menu. What we do here is creating a new PropertyPanel which is a Netbeans class that extends JComponent. So what we get back is a Swing component. And to show this component I’m using another nice  builtin Netbeans thing called DialogDescriptor. What it does is showing a popup jframe with the component inside and an Ok and Cancel button. And as you can see I’m creating a very basic ActionListner and connect it to the dialogdescriptor and when the user press Ok all we have to do is calling propertyPanel.updateValue() and the property is updated with the information the user entered, if cancel was pressed the property is not updated.

That’s all needed. And as you can see there is no connections to the node data in here so it’s possible to create a NodeUtilities class with a static method that generates this for every node you want. And if you have customized and created your own CustomEditor and registred with your Node that editor is what you gonna see. This is also true for any editors you have registered globally using PropertyEditorManager.registerEditor() off course.

If you know a nicer way to do this let me know!

Posted in Netbeans | Tagged: | 1 Comment »

Refresh Node children automatically

Posted by Nicklas Löf on October 14, 2010

Hello followers!

Todays problem to solve is how to refresh your Node children when there is a change in the data model. For example if you have a list of Customers and a delete button. When it’s pressed you have added code that are removing the selected customer from the list of customers but the node will still be visible in your list view. How can this be resolved?

In my basic of nodes post I did use a ChildFactory to create my nodes. Childfactories has a createKeys() method. In createKeys you populate a list with all the data objects you want to create nodes from and createNodeForKey() is called automatically for each object. Childfactories also have a nice method called refresh(). What refresh does is that it calls the createKeys() method again but it only calls createNodeForKey() for objects that doens’t already have a node. This is great. The old nodes (with current cookiesets and other states) are still there, only the new ones are added (or removed in case there is data that has been removed).

So lets call this refresh method then? well.. you can’t.. it’s protected and can only be called from the class that are extends Childfactory which is our nodechildfactory. At first I created a public method called update() which then called the refresh() method but then I started to realize that I need to keep an instance of my childfactory public in my TopComponent and call it when my data was updated. This works but isn’t a good way to do it. You know.. decoupling and all that. The lesser dependencies the better.

What can we do instead? Once again the power of lookups saves us! This solution is very simliar to the one I used in my post about using Lookup to change views in your application but lets look at some code again and lets start with creating a total empty class.

public class NodeRefreshEvent {
}

Phew.. that was a complicated one. Now lets look at our class that provides us with the model data objects. It’s the same class that I used in Basic for nodes. It’s a class with a static method that returns a list of Customer. Lets say that we have a deleteCustomer() method now and when a customer is deleted I want my node view to reflect this change.

public class CustomerService implements Lookup.Provider{
    private InstanceContent content = new InstanceContent();
    private Lookup lookup = new AbstractLookup(content);

    @Override
    public Lookup getLookup()
    {
        return lookup;
    }
}

So what have been added? I’m implemeting Lookup.Provider that contains the method getLookup(). And I need a Lookup to return so I create a lookup using the InstanceContent class which is a List where I can put instances of any object. So what do you say.. should I be crazy and put my small and silly NodeRefreshEvent class into this list when my data has been updated? Let’s try that!

public void deleteCustomer(int id){
    customers.delete(id);  // Lets pretend customers is a map with id as key
    content.add(new NodeRefreshEvent());
}

(See the Important section at the end of this post for some important information about this method)

Was that everything? So easy!.. No.. you need to do one more thing. Remember the childfactory?

class CustomerNodeFactory extends ChildFactory<Customer> implements LookupListener{
    private final Result<NodeRefreshEvent> lookupResult;
    public CustomerNodeFactory() {
        lookupResult = CustomerService.getLookup().lookupResult(NodeRefreshEvent.class);
        lookupResult.addLookupListener(this);
    }
}

So.. in my ChildFactory I’m now implementing a LookupListener. It does excatly what it says. It listens to changes on a Lookup. Which Lookup? Thats what I define in the constructor. I say that I want to listen for changes in the Lookup of CustomerService but only ones that are of the type NodeRefreshEvent.

Now we need to react when a change is done. How can that be done? Trough the resultChanged method that the LookupListener have added for me off course. This is where we will do our magic.

    @Override
    public void resultChanged(LookupEvent le)
    {
        Lookup.Result r = (Lookup.Result) le.getSource();
        Collection c = r.allInstances();

        for (Object object : c)
        {
            if (object instanceof NodeRefreshEvent){
                refresh(true);
            }
        }
    }

So here we check if the event we got in the Lookup is an instance of NodeRefreshEvent. If it is then do refresh() and boom.. the magic happens.. The createKeys() method is called and together with createNodeForKey() the nodes list is updated with new entries or removal of deleted entries!

And once again we have done some decoupling. Our CustomerService doesn’t have any idea who is interested in the addition or removal of customers. And remember we aren’t limited to just one listener. We could have 20 of them that listen to a NodeRefreshEvent and do things.

If you need to send a message together with the NodeRefreshEvent it can easily be done. Just create a field and a getter. When you creates your new instance of NodeRefreshEvent add the message to the constructor and pick it up in the Listener like this:

for (Object object : c)
{
    if (object instanceof NodeRefreshEvent){
        String message = ((NodeRefreshEvent)object).getMessage();
        refresh(true);
        doSomethingWithMessage;
    }
}

Message is offcourse not limited to a String. It can be anything you want. An enum, another object….

One last important thing

As Darrin pointed out in the comments section I forgot to include one important thing. The InstanceContent list is never emptied. We just add new things into the Lookup but we never removes them. And since we loop over all content in the Lookup we gonna update the nodes multiple time. So this is something we must prevent and that is to remove our previous NodeRefreshEvent instance before adding a new one.

Unfortuneatly there is no removeAll or clear method that can be called. The only way to remove an object from the InstanceContent is by using the instance itself. So we would need to save our created instance of NodeRefreshEvent and then call content.remove(ourInstance); This is how I did in a previous blog post but a few days ago I found this post that showed me another way to do it. BeanDev

So lets use a modified version of this example in our removeCustomer method

public void deleteCustomer(int id){
    customers.delete(id);  // Lets pretend customers is a map with id as key
    content.set(Arrays.asList(new NodeRefreshEvent()), null);
}

So instead of adding one object we use set which wants a list that we simply can create with Arrays.asList. And now the whole content of the InstanceContent is replaced.

Posted in Netbeans | Tagged: , , | 11 Comments »

The one with the cookies and some action and a little bit of Lookup

Posted by Nicklas Löf on October 13, 2010

So.. this is a blog post about cookies. Don’t we all love cookies? Sure we do but this isn’t about those small sugar bombs, neither is it about cookies in web browsers. Since this is a blog about Netbeans you might have already guessed that there is something in Netbeans RCP called Cookies.

I recently needed a way to tell my application that my data was updated and I knew that those cookies could help me. What I wanted to achieve was that by pressing a button in my toolbar the nodes that was updated had to be processed. What cookies does is giving your nodes a temporary state so you can track them.

Lets start by see if we can feed our nodes with some cookies…

First we need to create a cookie. In this example I’m creating an interface that extends a Node cookie. The interface contains one method called update().

public interface UpdateCookie extends Node.Cookie{
    public String update();
}

Ok.. now I have a cookie! What should I do with it? Should I have my node to implement this interface? No.. remember what I said earlier.. I want to give my node a temporary state. But add and remove interfaces can’t be done at runtime can they? No.. so we need to something else in our node so lets look at that.

public class CustomerNode extends BeanNode<Customer> {
    public CustomerNode(Customer bean) throws IntrospectionException {
        super(bean);
        setDisplayName(bean.getName());
    }
}

So this is a basic BeanNode. It has something called a CookieSet which is a Java Collection with Cookies. By default it’s empty but let’s add our cookie to it. And remember it’s an interface so it will ask us to implement all our abstract methods.

getCookieSet().add(new UpdateCookie() {
    @Override
    public String update() {
        return "Hello from" + getDisplayName() +" I'm updated!";
    }
});

Cookie added! And our update method will just return a String in this example but this is where you should call your application logic and do something with your data for example update a database or call a webservice..

This is a bit of bad example because now we have added the cookie to all our nodes. Normally you would have a method in your node that you call when the data is updated for example.. and that method should add the cookie to the cookieset. And to improve this example let’s add a small if statement that only adds this cookie to one of the nodes.. like this:

if (bean.getName().equals("Apple")) {
    getCookieSet().add(new UpdateCookie() {.........  like above....}
}

As I said, this is only to add the cookie for just one node in this example, normally it’s done somewhere else in your application.

Ok.. so now one of our Nodes has a UpdateCookie assigned. What can we do now? Let’s create an action so we can do something with our nodes. What actions are is out of scope of this post but the basic is that an action is something that are called when you click an icon in the toolbar or select something in the menu. So let’s create a menu item.

Add a new action by right click at your project and select New->Netbeans Module->Action. Leave the action to be Always enabled. Put it in the file category and call it UpdateCustomerAction with display name Update customers. Netbeans will now do everything for you and open up our newly created action. When an action is choosen the actionPerformed() method is called so this is where we will add our code.

What do we want to do? We want to grab all our Nodes that have the UpdateCookie assigned. First of all we need to get hold of our Nodes which are located in an Explorermanager in a Topcomponent. It’s VERY important that we add this command to our Topcomponent first:

associateLookup(ExplorerUtils.createLookup(myExplorerManager, this.getActionMap()));

This tells the Topcomponent that it should place the current selected nodes into the Lookup of the Topcomponent. So each time one or serveral nodes are selected those nodes are put into the Lookup of our Topcomponent so we can grab them externally.

So lets go back to our action. Now we can get our nodes.. at first this code might be tempting to write:

public final class UpdateCustomerAction implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        Node[] nodes = TopComponent.getRegistry().getCurrentNodes();

        for (int i = 0; i < nodes.length; i++) {
            Node node = nodes[i];
            UpdateCookie cookie = node.getCookie(UpdateCookie.class);
            if (cookie != null) {
                System.out.println(cookie.update());
            }
        }
    }
}

If we run our application now and select Update customer in the File menu with all our nodes selected in the Topcomponent it would correctly say in the Output window “Hello from Apple I’m updated!” but before explaining what happend I want to make the code above a bit cleaner. Instead of looping over all nodes and assign the cookie to a local variable and check for null there is a much more elegant way to do this. How? By using Lookup off course!

public void actionPerformed(ActionEvent e) {
        Collection<? extends UpdateCookie> lookupAll = TopComponent.getRegistry().getActivated().getLookup().lookupAll(UpdateCookie.class);
        for (UpdateCookie updateCookie : lookupAll) {
            System.out.println(updateCookie.update());
        }
    }
}

So what am I doing here? I’m asking for the current activated TopComponent. From this TopComponent I’m asking for the Lookup and tells it that I want to get all Nodes that are associated with an UpdateCookie. I will get a Java Collection with the result and since I just asked for UpdateCookie I only got the Cooke instance and not the whole Node and I can also be sure that it’s safe to call the update method.

Decoupling
There is actually a third way to grab our cookies. It’s a bit like above but using the global lookup instead. This is a proxy of all TopComponent lookups and from here you can get the selected node without having to reference to a specific TopComponet. This is good for decoupling and reusage. If we do it that way we can actually reuse our UpdateCustomerAction in other components. So the final code would look like this:

    public void actionPerformed(ActionEvent e) {
        for (UpdateCookie updateCookie : Utilities.actionsGlobalContext().lookupAll(UpdateCookie.class)) {
            System.out.println(updateCookie.update());
        }
    }

So to summarize what have been done.

  • I added an UpdateCookie to my Apple node. UpdateCookie is an interface with one method. This method will be called and from it I can do whatever I need to do with my node. In this case I just returned a string with the display name of the Node.
  • Then I added an action that grabbed all my UpdateCookie nodes. I looped over them and called the update() method that returned me the string with the name of the Node.

After we have done our updates we want our Cookie to be removed from our node since it’s only a temporary state. This can be done with this line added to the update() method:

getCookieSet().remove(getCookieSet().getCookie(UpdateCookie.class));

The next time you select Update customer nothing will happen because the cookie was removed.

Ending and exercise

This is just one example of what can be done with cookies. For fun and exercise try add SaveCookie to one of your nodes cookieset. Then watch the default Save button in the toolbar when you select different nodes. I will explain what happens in a later episode of Layer.xml 🙂

Posted in Netbeans | Tagged: , , , , | 1 Comment »

The basic of nodes part 2

Posted by Nicklas Löf on October 2, 2010

In my previous post I did go trough the very basic of nodes and we ended up with something that looks like this screenshot.

At the end I said that by looking at this screenshot there is probably two questions. Why is there an empty node with a white icon at the top? Why is the name repeated twice?

I will clarify this in this post which isn’t finished but will be shortly.

So lets start with the empty white node. Why is it there? To understand this we need to add one thing to our knowledge of nodes that I didn’t mention in the previous post but you might have understood it from some of the methods we called. First of all our NodeFactory did extend ChildFactory. Secondly we used a static method called Children.create. Do you see a common pattern?    Yes.. children!

Every single node that are created can have 0, 1 or many children. It’s a very simple thing but can be used to build folder structures of nodes. For example the Project tab in Netbeans or the File explorer. Those are nodes with children representing folders and files.

So the white icon node shown in the top is actually the node we created with this command.

em.setRootContext(new AbstractNode(Children.create(new CustomerNodeFactory(), true)));

It’s the AbstractNode which is our RootNode. When we are creating it we say that we are creating a Node that will contain children from our factory. Sometimes we want to show this node if we are showing a folder structure but in this case we don’t. So what can we do? We can’t remove the node since our Customers are children of the RootNode. Fortunately Netbeans Outlineview provides us with a simple method that hides the root node.

outlineView.getOutline().setRootVisible(false);

And boom. The root nodes is gone and we have a nice list of our customers.

So what can we do about the name that is repeated twice? The first column is actually a node column that always is visible. Remember that we did setPropertyColumns when we created the outlineview? If we hadn’t done that only the first colum would have been visible. But where does it get the name? If you look at the Customernode you will see that we did this:

setDisplayName(bean.getName());

So we could just remove that line and we will see only the icon in that column. We can also remove the name property from our setPropertyColumns() method call. So there is lot of possiblities and what you choose depends on the situation. Removing the display name works fine if you only are showing your nodes in this list but what if you want to show the same nodes in a tree folder view? Then the display name would be empty in that case.

I choose to remove the name column from my properties list:

outlineView.setPropertyColumns("type", "Type","contacted","Contacted");

That looks better doesn’t it? But wait..  now the colum for the company name isn’t Name anymore, it says Nodes now. We don’t want that. This can be solved easily by passing our desired column name to the outlineview when we are creating it.

outlineView = new OutlineView("Name");

And here we are done with part 2 of Nodes. I will add more parts showing how the icon can be customized, how we can add menu items to the menu that are shown when you right click on a node.

Posted in Netbeans | Tagged: , | 4 Comments »

The basic of nodes

Posted by Nicklas Löf on October 1, 2010

Nodes are one of the building blocks of any Netbeans RCP application. Since I’m still learning the Netbeans RCP basics I decided to write down what I have understood of nodes so far. So this is my personal notes about nodes as a small tutorial.

The first thing to understand about nodes is that they don’t represent the data. Nodes are just a bunch of objects that shows a graphical representation of the data. They contains all the data needed to display themselves but thats all.

This screenshot shows a view of some nodes I have created. It’s possible to edit all the data visible and the changes of the data will be reflected immediately in our data objects and there isn’t much code we have to write to achive this.

The easiest way to see this is by diving into some code. Lets say that we have an application that shows us all our customers. The basic of the customer class looks like this:

public class Customer {

    private String name;
    private Type type;
    private Boolean contacted;
}

Type is a simple Enum that can be added in the class.

public enum Type{
    small,
    medium,
    big;
}

You will see later why I have choosen to have an enum as one of the properties of the Customer class.

The class also has a constructor and getters and setters. It’s very easy to have Netbeans auto create this code for us. Just place the cursor inside the class after the field and press ctrl+i. In the popup menu select Constructor and choose that you want to initialize all the three fields. Then hit ctrl+i again and choose getters and setters and select all fields. This is a very easy way to quickly build up small classes.

So now we have our class. How can we create the Nodes? The easiest way to handle Nodes and the data behind them (remember..  Nodes is only the visual layer) is to use a BeanNode. And to use a BeanNode our class needs to be a Bean. Sound complicated? It isn’t. Netbeans will take care of this. Right click on your class and choose BeanInfo editor. It will now create all the information it needs. Switch to Design view and make sure that all our three fields (or properties as they are called) are green. Toggle them by right click on them. Sometimes you have properties that you still want to have getters and setters on but not to be editable trough Bean properties thats why it’s possible to choose here. It’s also possible to set read and read/write permissions on each property. But lets leave it at default settings.

Now we need to create our Customers in some way. In a real application it would probably be created from a database, a webservice or anything else but in this test lets create them manually in a CustomerService class.

public class CustomerService {
    static List<Customer> getCustomers(){
        return Arrays.asList(new Customer("Apple", Customer.Type.big, Boolean.TRUE),new Customer("Microsoft", Customer.Type.medium, Boolean.FALSE),new Customer("John", Customer.Type.medium, Boolean.TRUE));
    }
}

A small static service that returns an ArrayList with three Customers. Now it’s time to create the nodes that will be our visible layer to the customer data!

public class CustomerNode extends BeanNode<Customer>{
    public CustomerNode(Customer bean) throws IntrospectionException{
        super(bean);
        setDisplayName(bean.getName());
    }
}

So this is our CustomerNode. There will be one Node for each customer. The node itself doesn’t contain the customer data but as you can see it has a displayName. Other properties it can have is a HTMLDisplayName an icon and some other properties used for the visual presentation that we will not touch for now.

To create a collection of Nodes we will use a ChildFactory which is a class that will take care of mapping our Customer data objects into Nodes. Once again not much code is needed.

public class CustomerNodeFactory extends ChildFactory<Customer>{
    @Override
    protected boolean createKeys(List<Customer> list) {
    list.addAll(CustomerService.getCustomers());
    return true;
}

@Override
protected Node createNodeForKey(Customer key) {
    try {
            return new CustomerNode(key);
        } catch (IntrospectionException ex) {
            return Node.EMPTY;
        }
    }
}

What the code above does is that it will take List that contains Customer. Does that look familar? The list is empty in our case so we just call our CustomerService and retrieves the three customers we created earlier. And then add all those to the list.

So what’s up with the createNodeForKey method. It’s never called? Well..  we don’t call it from our code but Netbeans will call it for each Customer found in the List. And what we do is returning a CustomerNode and that’s all. Now we have our Nodes!   Now we need to display them and this is done by creating a Topcomponent and follow these steps:
* Right click in the design view and set the Layout to BorderLayout.
* Drag a JScrollPanel on top of the design.
* Switch to source view and create two fields.

private final ExplorerManager em;
private final OutlineView outlineView;

* Modify the TopComponent class so it implements ExplorerManager.Provider and allow Netbeans to create the required methods for you.
* In the auto created getExplorerManager() method return em;
* In the constructor we need to create a view for our nodes and initialize our ExplorerManager.

outlineView = new OutlineView();
this.em = new ExplorerManager();

* Switch back to design view and select customized code on the JscrollPanel and modify the source so it says

jScrollPane1 = outlineView;

Now we are almost done! There is one thing left to do. Let our view to know which nodes it should display. This is done in the ComponentOpened() method.

public void componentOpened() {
    em.setRootContext(new AbstractNode(Children.create(new CustomerNodeFactory(), true)));
    outlineView.setPropertyColumns("name", "Name", "type", "Type","contacted","Contacted");
}

The first line says that we want to create a new Node. This node will be the Root for all other nodes. And then we basically tells it that the children of our RootNode is created by the CustomerNodeFactory class that we created earlier. The second line is a list of properties (the field name they have in the Customer class) and what the column showing this data should be called. So here we can select which of our Customer data that should be visible in the view.

If everyting went well the module should compile now and show you something that looks like this:

As we can see we have our three Customers with the names, they type of the customer and if they have been contacted. Since we used BeanNodes we can click and edit and all our changes will go directly into our Customer without any more coding needed. Netbeans contains some premade editors for the different field. Strings can be edited in place. Enums will be shown as a drop down of all available values of that enum. And booleans has automatically a checkbox.

The beautiful thing here is that we can implement other views displaying the same nodes differently. For example we could show the customers in an icon view. Or just a list without properties. We can even create our own views and display the nodes just as we want.

If you look at the screenshot above you probably are asking two questions. Why is there an empty node with a white icon at the top? Why is the name repeated twice? I will cover that in part 2 of the The basic of nodes.

Posted in Netbeans | Tagged: | 3 Comments »