TableView and ListValueFactory in JavaFX

The TableView control is pretty complete to use and very convenient. But the classical use of it specify that a line is represented by an instance of an Object, usually a property bean. But in some cases it would be very useful to have a line composed by multiple instances of a specific class, i.e. having an instance per column, and each column should display a particular property of your instance. A concrete example would be the management interface of translations in NetBeans. Indeed, this screen display all properties files of a particular resource bundle, having a column for the translations keys and as many columns as needed to display the translations for each keys.

netbeans_i18n_editor

As said above, we could need an instance of object per column, meaning multiple instances per line. So a line of the TableView should be a List<? extends Object>. The problem is to know which data to display in a particular column.

ListValueFactory

As you may know, you can set CellValueFactory on a TableColumn, which will display the value in the cell. For example, you can set a PropertyValueFactory that will look for a property of a bean, and will display it. But that works if you have a single Object per line, not a collection. So we will create our own implementation that will check a property of a bean inside a list.

public class ListValueFactory<T, V> implements Callback<TableColumn.CellDataFeatures<List<T>, V>, ObservableValue<V>> {
  
  private String propertyName;

  public ListValueFactory(String propertyName) {
    this.propertyName = propertyName;
  }
    
  @Override
  public ObservableValue<V> call(TableColumn.CellDataFeatures<List<T> , V> p) {
        
    int index = 0;
        
    ListIterator<TableColumn<List<T>, ?>> iterator = 
       p.getTableView().getColumns().listIterator();
        
    TableColumn tmpColumn;
    while(iterator.hasNext()) {
      tmpColumn = iterator.next();

      if(tmpColumn == p.getTableColumn())
        break;
      else
        index++;
    }

    T object = p.getValue().get(index);

    try {
      Method method = object.getClass().getMethod(propertyName.concat("Property"));
      return (Property<V>) method.invoke(object);
    } catch (Exception ex) {
      Logger.getLogger(ListValueFactory.class.getName()).log(Level.SEVERE, null, ex);
      return null;
    }
  }
}

As you can see, the implementation is totally generic. First, we try to know the index of the column and we suppose that this index is the same index of the data in the list of data. WARNING: this is totally wrong if, in your UI, you move columns ; so take this into consideration because the implementation is not shown here. Once we have the index, we look for the data in the list, and finally, using so introspection, we look for the getter of the property in order to get the value. And that’s it.

Usage

To use this implementation in your code, you juste have to do the following:

TableView<List<Person>> tableView = new TableView<>();
TableColumn<String> columnOne = new TableColumn<>("First name one");
columnOne.setCellValueFactory(new ListValueFactory<List<Person>, String>("firstName"));
TableColumn<String> columnTwo = new TableColumn<>("First name two");
columnTwo.setCellValueFactory(new ListValueFactory<List<Person>, String>("firstName"));
tableView.getColumns().addAll(columnOne, columnTwo);
About these ads

One Response to TableView and ListValueFactory in JavaFX

  1. You may be interested in knowing there already exists a MapValueFactory in JavaFX, and it’s possible that a ListValueFactory is a useful addition. You should consider filing a Jira tweak request with this code.

    In terms of the code, there are probably a couple of things that we could improve:

    1) You should replace the first segment of the code (related to finding the column index) with something like this:
    int index = p.getTableView().getVisibleLeafColumns().indexOf(p.getTableColumn());

    2) For the latter code that uses reflection to get the *Property method, consider looking at how it is done in the PropertyValueFactory class – in short I use the (private) PropertyReference class to handle all the reflection.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: