BeansBinding Across Modules in a NetBeans Platform Application

Here's two TopComponents, each in a different NetBeans module. Let's use BeansBinding to synchronize the JTextField in TC2TopComponent with the data published by TC1TopComponent and received in TC2TopComponent by listening to the Lookup.

The key to getting to the solution is to have the following in TC2TopComponent, which implements LookupListener:

private BindingGroup bindingGroup = null;
private AutoBinding binding = null;

@Override
public void resultChanged(LookupEvent le) {
    if (bindingGroup != null && binding != null) {
        bindingGroup.getBinding("customerNameBinding").unbind();
    }
    if (!result.allInstances().isEmpty()){
        Customer c = result.allInstances().iterator().next();
        // put the customer into the lookup of this topcomponent,
        // so that it will remain in the lookup when focus changes
        // to this topcomponent:
        ic.set(Collections.singleton(c), null);
        bindingGroup = new BindingGroup();
        binding = Bindings.createAutoBinding(
                // a two-way binding, i.e., a change in
                // one will cause a change in the other:
                AutoBinding.UpdateStrategy.READ_WRITE,
                // source:
                c, BeanProperty.create("name"),
                // target:
                jTextField1, BeanProperty.create("text"),
                // binding name:
                "customerNameBinding");
        bindingGroup.addBinding(binding);
        bindingGroup.bind();
    }
}

I must say that this solution is preferable over what I've been doing prior to getting to this solution: I would get the customer from the resultChanged, set a class-level field to that customer, add a document listener (or action listener, which is invoked when Enter is pressed) on the text field and, when a change is detected, set the new value on the customer. All that is not needed with the above bit of code.

Then, in the node, make sure to use canRename, setName, and getDisplayName, so that when the user presses F2 on a node, the display name can be changed. In other words, when the user types something different in the node display name after pressing F2, the underlying customer name is changed, which happens, in the first place, because the customer name is bound to the text field's value, so that the text field's value will also change once enter is pressed on the changed node display name.

Also set a PropertyChangeListener on the node (which implies you need to add property change support to the customer object), so that when the customer object changes (which happens, in the second place, via a change in the value of the text field, as defined in the binding defined above), the node display name is updated.

In other words, there's still a bit of plumbing you need to include. But less than before and the nasty class-level field for storing the customer in the TC2TopComponent is no longer needed. And a listener on the text field, with a property change listener implented on the TC2TopComponent, isn't needed either. On the other hand, it's more code than I was using before and I've had to include the BeansBinding JAR, which adds a bit of overhead to my application, without much additional functionality over what I was doing originally. I'd lean towards not doing things this way. Seems quite expensive for essentially replacing a listener on a text field and a property change listener implemented on the TC2TopComponent for being notified of changes to the customer so that the text field can be updated. On the other other hand, it's kind of nice that all this listening-related code is centralized in one place now.

So, here's a nice improvement over the above. Instead of listening for a customer, listen for a node, from which the customer can be obtained. Then, bind the node display name to the text field's value, so that when the user types in the text field, the node display name is updated. That saves you from having to listen in the node for changes to the customer's name. In addition to that binding, keep the previous binding, because the previous binding connects the customer name to the text field, so that when the customer display name is changed via F2 on the node, the text field will be updated.

private BindingGroup bindingGroup = null;
private AutoBinding nodeUpdateBinding;
private AutoBinding textFieldUpdateBinding;

@Override
public void resultChanged(LookupEvent le) {
    if (bindingGroup != null && textFieldUpdateBinding != null) {
        bindingGroup.getBinding("textFieldUpdateBinding").unbind();
    }
    if (bindingGroup != null && nodeUpdateBinding != null) {
        bindingGroup.getBinding("nodeUpdateBinding").unbind();
    }
    if (!result.allInstances().isEmpty()) {
        Node n = result.allInstances().iterator().next();
        Customer c = n.getLookup().lookup(Customer.class);
        ic.set(Collections.singleton(n), null);
        bindingGroup = new BindingGroup();
        nodeUpdateBinding = Bindings.createAutoBinding(
                AutoBinding.UpdateStrategy.READ_WRITE,
                n, BeanProperty.create("name"),
                jTextField1, BeanProperty.create("text"),
                "nodeUpdateBinding");
        bindingGroup.addBinding(nodeUpdateBinding);
        textFieldUpdateBinding = Bindings.createAutoBinding(
                AutoBinding.UpdateStrategy.READ_WRITE,
                c, BeanProperty.create("name"),
                jTextField1, BeanProperty.create("text"),
                "textFieldUpdateBinding");
        bindingGroup.addBinding(textFieldUpdateBinding);
        bindingGroup.bind();
    }
}

Now my node has no property change listener, while the customer has no property change support. As in the first bit of code, the text field doesn't have a listener either. All that listening is taken care of by the BeansBinding code. 

Thanks to Toni for help with this, though he can't be blamed for anything that is wrong with it, only thanked for anything that is right with it. 

Comments:

I'm not sure, but isn't a property change support needed for the auto binding to work from the bean side? At least this is what i have experienced.

Posted by Philip Markus on June 26, 2012 at 12:06 AM PDT #

Yeah, me too, on second thoughts. Without the property change support on the bean, the node display name is only updated when you select a different node and then come back to the node that you changed. But if you include property change support on the bean, the update is done immediately.

Posted by Geertjan on June 26, 2012 at 01:45 AM PDT #

Yes, you need bound property support, but I'd say it's a small price to pay for a decoupled architecture. You can have NetBeans to auto-generate the plumbing code for bound properties, or even use an annotation processor such as Lombok / Lomprop.

I've been using binding, an in particular BeansBinding and BetterBeansBinding, for years with a great satisfaction. The only reason for which I'm going to change it is that the future is probably in the new binding facility that comes with JavaFX 2. I'm just waiting for an official release for Linux to start using it (and have customers to start using it).

Posted by Fabrizio Giudici on June 28, 2012 at 08:29 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
23
24
25
26
27
28
29
30
   
       
Today