quarta-feira, 26 de setembro de 2012

Showing Object Properties in a TableView

This post is not to talk about any specific app but to show a simple How-To with TableView.
 Show values in a table in JavaFX is easy. You can show an object property with a few lines of code since we have classes that allows you to produce cells that read directly from the object. For example, let's say we have a class Person that contains two properties:
 

public class Person {
 private String name;
 private int id;
       //getters, setters..
}
If you want to create to columns to show the id and name properties, you need to create two columns and inform each column a PropertyValueFactory object instance:
TableView tableView = new TableView();
TableColumn idColumn = new TableColumn("Id");
idColumn.setCellValueFactory(new PropertyValueFactory("id"));
TableColumn nameColumn = new TableColumn("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory("name"));
tableView.getColumns().addAll(idColumn, nameColumn);
Let's complicate a bit! What's if my object Person contains other object, for example, City and we want to show a City property? If we try to add a column for the property city with the following code:
 
TableColumn cityColumn = new TableColumn("City");
cityColumn.setCellValueFactory(new PropertyValueFactory("city"));

But it will not show any city property, it will actually show the toString method return:

Obviously you can go to City class code and override this method. Let't get the things a bit more complicated. This time let's say that you need to show a property from a City property, for example, State.

Person - City - State

Well, you can read things in the method toString of the object City, but this is not the best solution. One alternative is to set a CellValueFactory in your column using a Callback class.
. With this approach we can read any property we want from the Person class and return it to be showed in the table. It's pretty easy to use it:
TableColumn stateColumn = new TableColumn("State");
stateColumn.setCellValueFactory(new Callback, ObservableValue>() {    
    @Override    
    public ObservableValue call(CellDataFeatures c) {
        return new SimpleStringProperty(c.getValue().getCity().getState().getName());
    }
});

That's basically it what I wanted to show in this post. The entire test application code:

package org.jesuino.javafx.tableview.cellfactory;

import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.SceneBuilder;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;
import javafx.util.Callback;

import org.jesuino.javafx.tableview.cellfactory.model.City;
import org.jesuino.javafx.tableview.cellfactory.model.Person;
import org.jesuino.javafx.tableview.cellfactory.model.State;

public class Main extends Application {

 TableView tableViewRef;

 public static void main(String[] args) {
  launch();
 }

 @Override
 public void start(Stage stage) throws Exception {

  stage.setScene(SceneBuilder
    .create()
    .width(420)
    .height(200)
    .root(StackPaneBuilder.create()
      .children(tableViewRef = createTableView()).build())
    .build());
  stage.setTitle("Testing Table View Cell Factory");
  stage.show();

  tableViewRef.getItems().addAll(mockPersonItems());
 }

 @SuppressWarnings("unchecked")
 private TableView createTableView() {
  TableView tableView = new TableView();

  TableColumn idColumn = new TableColumn(
    "Id");
  idColumn.setCellValueFactory(new PropertyValueFactory(
    "id"));
  idColumn.setMinWidth(80);

  TableColumn nameColumn = new TableColumn(
    "Name");
  nameColumn
    .setCellValueFactory(new PropertyValueFactory(
      "name"));
  nameColumn.setMinWidth(150);

  TableColumn cityColumn = new TableColumn(
    "City");
  cityColumn
    .setCellValueFactory(new PropertyValueFactory(
      "city"));
  cityColumn.setMinWidth(150);

  TableColumn stateColumn = new TableColumn(
    "State");
  stateColumn
    .setCellValueFactory(new Callback, ObservableValue>() {
     @Override
     public ObservableValue call(
       CellDataFeatures c) {
      return new SimpleStringProperty(c.getValue().getCity()
        .getState().getName());
     }
    });

  tableView.getColumns().addAll(idColumn, nameColumn, cityColumn,
    stateColumn);

  return tableView;
 }

 private List mockPersonItems() {
  State sp = new State("São Paulo", "SP");

  City saoJoseDosCampos = new City(sp, "São José dos Campos");
  City taubate = new City(sp, "Taubaté");
  City jacarei = new City(sp, "Jacareí");

  Person p1 = new Person("William Antônio Siqueira", 1, saoJoseDosCampos);
  Person p2 = new Person("Maria", 2, saoJoseDosCampos);
  Person p3 = new Person("João", 3, taubate);
  Person p4 = new Person("Joana", 4, jacarei);

  return Arrays.asList(p1, p2, p3, p4);
 }
}

3 comentários:

  1. Thanks for this post. Like the Javadoc it addresses the readonly case only, unfortunately. In other words, if person.name changes it is surprisingly difficult to update the tableview. Apparently the old name is cached by PropertyValueFactory, without any obvious way to invalidate it...

    ResponderExcluir
  2. deepc, I have a work in progress addressing that problem. I created several custom components to display java POJO object in TableView. Take a look at http://panemu.com/articles/?p=73&lang=en
    The sourcecode is available

    ResponderExcluir
  3. Regarding the problem of the TableView not updating automatically when the underlying data changes, I found a simple solution on "Updating rows in Tableview" http://stackoverflow.com/questions/10912690/updating-rows-in-tableview

    Check the answer from June 6 / 2012. The solution is simply to wrap the relevent fields of the class that holds the data inside Property classes and then providing getters for those properties. Example:

    private final SimpleStringProperty name = SimpleStringProperty("");

    public SimpleStringProperty nameProperty() {
    return name;
    }

    public String getName() {
    return name.getValue();
    }

    public void setName(String v) {
    name.setValue(v);
    }


    Regarding the code in this blog, I could not understand the <"persons string="string"> type specification (and neither my Java compiler :D)

    Julian

    ResponderExcluir