Loosely Coupled Open Action (Part 2)

An excellent follow-up question to yesterday's blog entry was: "How do you get a TopComponent that is not a singleton to open and listen to the global lookup? As far as I understand this example applies only for TopComponents that are already open."

Here's a simple solution to the above problem. Create a module installer (via the Module Install template, so that the class will be registered correctly in the manifest) and define it like this:

public class Installer extends ModuleInstall implements LookupListener {

    Result customerResults;
    
    @Override
    public void restored() {
        customerResults = Utilities.actionsGlobalContext().lookupResult(Customer.class);
        customerResults.addLookupListener(this);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        if (customerResults.allInstances().iterator().hasNext()) {
            Customer c = customerResults.allInstances().iterator().next();
            CustomerEditorTopComponent cetc = new CustomerEditorTopComponent(c);
            cetc.open();
            cetc.requestActive();
        }
    }
    
}

The installer will, upon installation of the Customer Editor module, start listening for Customer objects in the global Lookup. Once a Customer object is there, which will be the case when the "Open Customer" action is invoked, a new CustomerEditorTopComponent will automatically be opened, passing in the current Customer object for processing.

Since the TopComponent is not intended to be a singleton, you can remove all its annotations (except for the @Messages annotation), so that the top of the class is now as follows:

@Messages({
    "CTL_CustomerEditorTopComponent=Customer Editor for ",
    "HINT_CustomerEditorTopComponent=This is a CustomerEditor window"
})
public final class CustomerEditorTopComponent extends TopComponent {

    public CustomerEditorTopComponent(Customer c) {
        initComponents();
        setName(Bundle.CTL_CustomerEditorTopComponent() + c.getName());
        setToolTipText(Bundle.HINT_CustomerEditorTopComponent());
        nameField.setText(c.getName());
    }
    ...
    ...
    ...

Also, make sure that your "OpenCustomerActionListener" is in the same module as the above two (i.e., the installer and the CustomerEditorTopComponent), so that the related menu item will only be available on the node when the Customer Editor module is installed.

An alternative approach (here I'm preempting the next follow-up question) is that you don't want to create a new TopComponent for each Customer object, but only if there isn't already a TopComponent open for the new Customer object:

public class Installer extends ModuleInstall implements LookupListener {

    Result customerResults;
    Set uniqueCustomers = new HashSet();

    @Override
    public void restored() {
        customerResults = Utilities.actionsGlobalContext().lookupResult(Customer.class);
        customerResults.addLookupListener(this);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        if (customerResults.allInstances().iterator().hasNext()) {
            Customer c = customerResults.allInstances().iterator().next();
            if (uniqueCustomers.add(c)) {
                CustomerEditorTopComponent cetc = new CustomerEditorTopComponent(c);
                cetc.open();
                cetc.requestActive();
            } else { //In this case, the TopComponent is already open, but needs to become active:
                for (TopComponent tc : WindowManager.getDefault().findMode("editor").getTopComponents()){
                    if (tc.getName().contains(c.getName())){
                        tc.requestActive();
                    }
                }
            }
        }
    }
    
}

In the TopComponent, when the "componentClosed" is called, you could remove the related Customer object from the "uniqueCustomers" set, which would then need to be found within a Utilities class shared between the Installer and the TopComponent classes.

Now you have a very neat solution where the editor and viewer modules do not depend on each other at all.

I've updated the sample to include the above code:

http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.1/misc/OpenCapabilityDemo

Comments:

Besides the comments in the previous post:

1. You probably do not want to remove all annotations from the TC; keep @SaveAsProperties or however you prefer to persist it.

2. tc.getName().contains(c.getName()) is sloppy. The TC's lookup should contain the Customer.

3. Opening windows merely in response to selection changes is a really bad idea from a UI perspective, and necessitates using ModuleInstall which is not to be recommended either.

Posted by Jesse Glick on March 12, 2012 at 07:45 AM PDT #

Hi,

I am currently learning about the Netbeans RCP and tried your suggestion on "loosely coupled open action" and nothing happened. Everytime I hit the open action, the open() method is called, but nothing else happens.

I had a look at the demo and copied the code (just to be sure that no typo is responsible). No change at all.

At this time I was absolutely sure that I did something wrong and got the whole demo from the subversion and started it. Oops the demo isn´t working either. Same behaviour. o.O

This is my environment I´m running the demo:

Product Version: NetBeans IDE 7.1 (Build 201112071828)
Java: 1.7.0_02; Java HotSpot(TM) 64-Bit Server VM 22.0-b10
System: Windows 7 version 6.1 running on amd64; Cp1252; de_DE (nb)

Do you have any idea why this isn´t working?

Thanks for your help and your remarkable blog!

Kindest regards,
Andi

Posted by Andreas Billmann on March 22, 2012 at 09:50 AM PDT #

Andreas, you need to add some annotations to the CustomerEditorTopComponent:

@TopComponent.Description(preferredID = "CustomerEditorTopComponent",
//iconBase="SET/PATH/TO/ICON/HERE",
persistenceType = TopComponent.PERSISTENCE_ALWAYS)
@TopComponent.Registration(mode = "editor", openAtStartup = false)

since the listener is searching for TopComponents that have an "editor" mode:

for (TopComponent tc : WindowManager.getDefault().findMode("editor").getTopComponents()){
if (tc.getName().contains(c.getName())){
tc.requestActive();
}

Posted by Jose Renteria on June 19, 2012 at 12:52 PM PDT #

Andreas you need to add the annotations to the CustomerEditorTopComponent to register the mode = "editor" since the listener is searching for topcomponents with that mode

Posted by Jose Renteria on June 19, 2012 at 12:55 PM PDT #

Hi

I have compiled and run your examples succesfully,
I'm trying to implement this behavior but in my case the first action is not in a BeanNode is in a pre existing table, How I can implement it from other kind of object like in CustomerNode.java?
Is it possible?

Thanks and congratulations you have an excelent blog !!

Posted by Alfonso Molina on July 18, 2012 at 04:33 PM PDT #

If listeners do not work: Make sure to have a STRONG reference to it.

After couple of hours of trial / error, where the listener was not informed of changes, I decided to change the field "Result customerResults;" in the Installer-example above to be a static field: "static Result customerResults;". After that, everything worked fine. Just make sure not to lose your Result object to the garbage collector.

BTW: It seems that you cannot debug ModuleInstall restored-method or anything called from there (NB 7.2).

Posted by Thomas on September 25, 2012 at 11:28 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
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today