One day you're a happy JavaFX developer, cranking out applications and blissfully providing your application's users with table after table of rich textual information. The next day it happens: your users approach you and ask if there's a way to include a thumbnail of each part in the inventory list. Or a small photo in the contact list/personnel roster. Or a snapshot/graph of the latest conditions for each monitored sensor or weather camera. So now what?
PropertyValueFactory, a convenience implementation of the Callback interface, is typically used to handle the work involved with populating all cells within a single TableColumn. As long as you provide the name of a valid property to the PropertyValueFactory constructor and set the TableColumn's CellValueFactory to the resulting PropertyValueFactory, the cell is populated, TableView adds an observer to the value, and life moves merrily along.
Adding a graphical element to a TableView isn't much harder, actually. The TableColumn's CellFactory handles rendering of the cell's contents, so if you want to place something other than textual content in the cell, you'll need to also set the TableColumn's CellFactory to one of your liking and override the updateItem() method. Let's take a look at a quick example.
I'd always intended to create some small-but-useful Twitter utilities and hadn't really had the opportunity, but a simple Twitter client provides a perfect demonstration of this capability. Here is an example of what a "feed" interface might look like:
For this TableView example, there are only two columns:
- one for the user, showing the user's picture, name, and screen name
- one for the tweet
The tweet's text is simply a string property of each MTweet object, but the user information comes from several properties of the MTweet object's associated MUser object. The MTweet's text is properly assigned to the cell's text property per the discussion above, but what about the user information? Per the JavaFX 2.2 docs,
Because by far the most common use case for cells is to show text to a user, this use case is specially optimized for within Cell. This is done by Cell extending from
Labeled. This means that subclasses of Cell need only set the
text property, rather than create a separate
Label and set that within the Cell. However, for situations where something more than just plain text is called for, it is possible to place any
Node in the Cell
graphic property. Despite the term, a graphic can be any Node, and will be fully interactive. For example, a ListCell might be configured with a
Button as its graphic. The Button text could then be bound to the cells
item property. In this way, whenever the item in the Cell changes, the Button text is automatically updated.
To display the tweet's text in the "Text" column of our table, a couple lines of code does all that is necessary:
In order to bundle the user information and place it within a single cell, we set the "User Info" column's CellValueFactory in similar fashion to our text column above:
And then we set that column's CellFactory to something along the order of this:
For each cell in that TableColumn, updateItem is called with that cell's contents as the "item", the first parameter. Within our example above, we create a VBox, populate it with the user's photo (Image, via ImageView), name (String) and screen name (String), do a bit of formatting, and assign the VBox to the cell's graphic property. And with that, we're off and running. :-)
There is much more you can do, and this should get the ideas flowing. Here's to prettier apps (better optics?) and happier users!
All the best,
<<< UPDATE 1 >>>
Jonathan Giles, JavaFX team UI Controls tech lead, made a few suggestions for improving the efficiency of the code used above. The points he made were excellent, and I wanted to provide an update that shows revised code that incorporates them. I did leave the original code above, as I'm hoping it helps make a clearer path for those implementing similar code for the first time.
In order to override the TableCell constructor, we'll extend the particular TableCell<MTweet, MUser> class. Doing so allows us to create the VBox, Labels, and ImageView used in our composite user cell once per cell, rather than each time updateItem() is called. While we're at it, we'll also perform formatting/assignment duties in the constructor and avoid repeating those calls. Here is the new derivative class:
Creating the new UserTableCell class means we can update our setCellFactory() method call for our user info column like so:
Thanks to Jonathan Giles (@JonathanGiles) for the suggestions!
All the best,
<<< UPDATE 2 >>>
One more update! This time using an anonymous inner class for the column's cell factory. Same basic functionality, but this is perhaps the tidiest option.
Options are good, as they say... :-)
All the best,