Thursday, 28 November 2013

JavaFX TableView Cell Renderer

In this post I will show how to customize the rendering of a JavaFX 2 TableView. The Birthdaycolumn in the screenshot below is a formatted Calendar object. Depending on the year, the text color is changed.

How a Table Cell is Rendered

Each table cell will receive an object, in our case it is an instance of Person. To do the rendering, the cell will need a Cell Value Factory) and a Cell Factory):

Cell Value Factory

The cell must know which part of Person it needs to display. For all cells in the birthday columnthis will be the Personbirthday value.
This is our birthday column:
1
private TableColumn<Person, Calendar> birthdayColumn;
And later during initialization, we’ll set the Cell Value Factory:
1
2
birthdayColumn.setCellValueFactory(
    new PropertyValueFactory<Person, Calendar>("birthday"));
So far nothing too fancy.

Cell Factory

Once the cell has the value, it must know how to display that value. In our case, the birthday’s Calendar value must be formatted and colored depending on the year.
[update 2012-12-27: Set text to null if cell is empty. See comment by James_D below]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
birthdayColumn.setCellFactory(new Callback<TableColumn<Person, Calendar>, TableCell<Person, Calendar>>() {
  @Override
  public TableCell<Person, Calendar> call(TableColumn<Person, Calendar> param) {
      return new TableCell<Person, Calendar>() {

          @Override
          protected void updateItem(Calendar item, boolean empty) {
              super.updateItem(item, empty);

              if (!empty) {
                // Use a SimpleDateFormat or similar in the format method
                setText(format(item));

                if (item.get(Calendar.YEAR) == 2011) {
                  setTextFill(Color.CHOCOLATE);
                } else {
                  setTextFill(Color.BLACK);
                }

              } else {
                setText(null);
              }
          }
      };
  }
});
The Cell Factory contains some complicated stuff (CallbackGenerics and Anonymous Inner Classes). Don’t worry too much about all this. Just focus on the important part which is the updateItem(...) method.
This updateItem(...) method gets called whenever the cell should be rendered. We receive the Calendar item that must be rendered. If empty is true we don’t do anything. Otherwise we format the item and set the text of the cell. Depending on the year, we also set the text color.

ListView and TreeView

Note that the JavaFX 2 ListView and TreeView are rendered in a very similar way.

Download

Download the complete tableview-cell-renderer example.

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.

What's new in JavaFX 2.2 ?

Earlier this week, Oracle released JavaFX 2.2, which is a significant update release:
  • With version 2.2, JavaFX is finally available on Windows (32-bit & 64-bit), Mac OS X (64-bit), and Linux (32-bit & 64-bit), which means that most developers can build and test JavaFX applications on their preferred development environment. Have a look at the JavaFX Certified System Configurations for more details.

  • The JavaFX 2.2 Runtime is now part of Oracle’s Java SE 7u6 implementation. While we already had some form of integration between these two products since December 2011, , we now have one unified installer, with the JavaFX libraries installed alongside the Java SE libraries for both the JDK and the JRE. This means a more streamlined user experience, and the assurance for application developers that the number of computers capable of running JavaFX applications will soon be as large as for Java SE applications.

  • A stand-alone implementation of JavaFX 2 will remain available for Java SE 6 users, but only on Windows. Additionally, that stand-alone version will no longer be supported when Java SE 6 reaches End of Life (EOL) in February 2013. As explained previously, this means there will no longer be bug fixes or security fixes available for Java SE 6 users after that date, unless you or your customers sign up for our commercial Java SE Supportoffering. Otherwise, plan your migration to Java SE 7 right now.

Besides these important changes, JavaFX 2.2 brings in some key new features:
  • JavaFX applications can now be redistributed as self-contained application packages. These platform-specific packages include all application resources and a private copy of Java and JavaFX Runtimes. Distributed as a native installable package, they provide the same installation and launch experience as native applications for that operating system. A key benefit to take into consideration is that it will allow you to deploy JavaFX 2.2 applications bundled with Java SE 7 without impacting existing deployments of older Java SE implementations.

  • Multi-touch support for touch-enabled devices. As of today this is mostly relevant for desktop-class touch screen displays and touch pads, this will enable the adoption of sophisticated UIs on embedded devices running Java SE Embedded on ARM-based chipsets, such as kiosks, telemetry systems, healthcare devices, multi-function printers, monitoring systems, etc. This is a segment of the Java application market that is usually overseen by most application developers, but that is thriving.

  • The JavaFX Canvas API, a Canvas 2D drawing surface that provides HTML5 Canvas-style operations. Developers familiar with HTML5 will definitely be at ease with the JavaFX Canvas API, although it is important to notice that this is not meant to be a duplicate of the HTML5 Canvas Graphics API. We believe this API will also be welcome by developers with other backgrounds, such as AWT or SVG. You can run a demo of the “Fireworks” canvas demo under the section “NEW!” of the Ensemble sample application, or you can watch the making of another Canvas example on this video.

  • JavaFX 2.2 introduces the ability to read and write pixels to and from JavaFX image objects. An example is available as “Image Operator” in the “NEW!” section of Ensemble.

  • Two new UI controls have been added to JavaFX 2.2: a color picker, and a pagination control; you can give them a try in the “NEW!” section of Ensemble. In addition, the WebView control now provides the ability to manage web history. Finally, we have documented how to create a custom control with FXML, which takes advantage of FXML enhancements.

  • HTTP Live Streaming support is a feature that strengthens up JavaFX’s media support. Essentially, media players are now able to switch to alternate video and audio streams, as specified in a downloadable playlist file and based on network conditions.

  • Additional information to help Swing developers implement a Swing application in JavaFX, and SWT developers to integrate JavaFX content in SWT applications. We also have documented some Best Practicesfor developing a JavaFX application.

  • Last but not the least, JavaFX developers can now leverage the new JavaFX Scene Builder 1.0 to visually layout an application UI, and generate FXML content that helps keep a clean separation between application logic and UI. Scene Builder is also a great example of a complex application written in JavaFX. It is currently available on Windows and Mac OS X, and is optimized to work seamlessly with NetBeans 7.2 or higher (it can also be used with other Java IDEs).


In summary, JavaFX 2.2 is a key release that brings much more than Linux support. It fulfills Oracle’s vision to integrate JavaFX with Java SE to a large extent, and is a proof of our commitment for cross-platform support and predictable timelines.