How to bind a JavaFX Label to the selected item from a ListView


Question

I have a ListView full of POJOs and want a label in the GUI to display informations from the selected item.

My POJO looks something like that:

class Customer {
  private String name;
  ...
  public String getName() {
  return name; 
  }

Now when the user selects a customer from the list I want the name of the selected customer displayed in a label.

Obviously I can't bind to the name directly because it is not a Property. (And I don't want to replace my Customers Strings with StringProperty-objects because the SimpleStringProperty is not serializable and I need the Customer to be transfered via RMI.)

I've tried the BeanPathAdapter from JFXtras (which looks really nice by the way) like this:

    BeanPathAdapter<MultipleSelectionModel> customerBeanPathAdapter;
    customerBeanPathAdapter = new BeanPathAdapter<>(lstCustomers.getSelectionModel());
    customerBeanPathAdapter.bindBidirectional("selectedItem.name", lblCustomerName.textProperty());

But this solution only throws me an Exception:

...
Caused by: java.lang.IllegalArgumentException: Unable to resolve accessor getSelectedItem
at jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle.buildAccessor(BeanPathAdapter.java:3062)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle.buildAccessorWithLikelyPrefixes(BeanPathAdapter.java:3022)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle.updateMethodHandles(BeanPathAdapter.java:2986)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle.<init>(BeanPathAdapter.java:2977)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldBean.performOperation(BeanPathAdapter.java:1348)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldBean.performOperation(BeanPathAdapter.java:1186)
at jfxtras.labs.scene.control.BeanPathAdapter.bindBidirectional(BeanPathAdapter.java:567)
at jfxtras.labs.scene.control.BeanPathAdapter.bindBidirectional(BeanPathAdapter.java:369)
at at.gs1.sync.qm.client.gui.MainWindowController.initialize(MainWindowController.java:61)
... 22 more
Caused by: java.lang.IllegalAccessException: symbolic reference class is not public: class javafx.scene.control.ListView$ListViewBitSetSelectionModel, from jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:512)
at java.lang.invoke.MethodHandles$Lookup.checkSymbolicClass(MethodHandles.java:1113)
at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1094)
at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:626)
at jfxtras.labs.scene.control.BeanPathAdapter$FieldHandle.buildAccessor(BeanPathAdapter.java:3049)
... 30 more

So I hoped there would be a better solution than to use lstCustomers.getSelectionModel().selectedItemProperty().addListener(...) and handle the population of the labels there manually.

1
6
9/4/2013 7:15:59 AM

Accepted Answer

A better solution I think, to the one I gave before, is to use the BeanPathAdapter as you tried.
However the BeanPathAdapter needs to have the following property added to it:

private final ObjectProperty<B>  beanProp = new SimpleObjectProperty<>();
{
    beanProp.addListener( new ChangeListener<B>()
    {
        @Override
        public void changed( ObservableValue<? extends B> ob, B oldVal, B newVal )
        {
            setBean( newVal );
        }
    } );
}

public ObjectProperty<B> beanProperty()
{
    return beanProp; 
}

Then in your code you need the following:

BeanPathAdapter<Customer>  custBean;
custBean = new BeanPathAdapter<>( new Customer() );   // empty or any customer
custBean.bindBidirectional( "name", label.textProperty() );
custBean.beanProperty().bind( listview.getSelectionModel().selectedItemProperty() );
3
12/18/2013 10:59:15 AM

I don't think that there's a simple one liner that you are looking for.
You could do the following:

label.textProperty().bind( Bindings.selectString( listview.getSelectionModel().selectedItemProperty(), "name" ) );

But you will need to modify your Customer POJO like so:

class Customer 
{
    private String name;
    ...
    public String getName() { return name;  }

    public ReadOnlyStringProperty nameProperty()
    {
        return new SimpleStringProperty( name );
    }
}

I don't think this is recommended though because properties are expected to reflect changes in the underlying data and the above will only reflect the name as it was when nameProperty is called. So if setName is called the property won't reflect the change. If the Customer name never changes then you could get away with this.


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon