Thursday 28 November 2013

JavaFX updating item in a TableView

JavaFX has a fundamentally different way of handling these kinds of updates, which may make it tricky to implement this in your current system.

In short, the way updates work in JavaFX for the ListView, TreeView and TableView is this:

Each type of View is made out of Cells. The amount of Cells is usually pretty close to the amount of visible rows and each Cell could be thought of to represent one Row.

Each Cell is basically a small piece of UI, that adapts itself to whatever should be displayed at the given row at that time. The updateItem method is called on these Cells to associate them with an underlying Item from the ObservableList (your model).

Let's say your "Items" in the Model are Person objects with a name. A cell implementation might render this as follows:
  private static final class PersonTableCell extends TableCell<Person, Person> {

    @Override
    protected void updateItem(final Person person, boolean empty) {
      super.updateItem(mediaNode, empty);

      if(!empty) {
        setText(person.getTitle());
      }
    }
  }
The example above will also have the same update problem as your code, that is, if you change the Person object in your model the Cell will not reflect the changed name.

In JavaFX to let the Cell know about the change, you have to add a listener to the "text" property of your Person object. This assumes that the properties of a Person object are JavaFX style properties. This is accomplished like this (using a binding which uses a listener internally):
  private static final class PersonTableCell extends TableCell<Person, Person> {

    @Override
    protected void updateItem(final Person person, boolean empty) {
      super.updateItem(mediaNode, empty);

      textProperty().unbind();

      if(!empty) {
        textProperty().bind(person.titleProperty());
      }
    }
  }
The above will update correctly automatically whenever a Person's title property changes.

However, this means that you will have to change your Person object to use JavaFX style properties. You donot always have this luxury. However, there are other options. You could for example only support a "changedProperty" in the Person class, and have the Cells listen for to this, something like this:

  private static final class PersonTableCell extends TableCell<Person, Person> {
    private Person oldPerson;
    private InvalidationListener listener;

    @Override
    protected void updateItem(final Person person, boolean empty) {
      super.updateItem(mediaNode, empty);

      if(oldPerson != null) {
        oldPerson.removeListener(listener);
        oldPerson = null;
      }

      if(!empty) {
        listener = new InvalidatonListener() {
          public void invalidated(Observable o) {
             setText(person.getTitle());
          }
        };
        person.addListener(listener);
        setText(person.getTitle());
      }
    }
  }
Now each time you want to trigger a "change", you call a method on the Person object that triggers an Invalidation event. All the cells that are listening to this Person object will be notified and update themselves.

No comments: