The ListView class of JavaFX 8 shows one item per line – one String
in the simplest case. What if you want to visually separate individual string fragments (words, numbers)? You could use a TableView with multiple columns, but that may not be appropriate for your data. Or you could insert tab characters ("\t"
) into each string, but tab spacing is not configurable and rather unpredictable.
A better way to align individual parts of a single text column is to implement your own ListCell class. Assume a cell data type of ItemData
with three properties left, middle, right
that should appear at the corresponding positions within each column, with the right
part also right-aligned. Here’s what the ListCell
implementation would look like:
class CustomListCell extends ListCell<ItemData> {
private Font _itemFont = ...;
@Override
protected void updateItem(ItemData item, boolean empty) {
super.updateItem(item, empty);
Pane pane = null;
if (!empty) {
pane = new Pane();
// left-aligned text at position 0em
final Text leftText = new Text(item.left());
leftText.setFont(_itemFont);
leftText.setTextOrigin(VPos.TOP);
leftText.relocate(0, 0);
// left-aligned text at position 4em
final Text middleText = new Text(item.middle());
middleText.setFont(_itemFont);
middleText.setTextOrigin(VPos.TOP);
final double em = middleText.getLayoutBounds().getHeight();
middleText.relocate(4 * em, 0);
// right-aligned text at position 8em
final Text rightText = new Text(item.right());
rightText.setFont(_itemFont);
rightText.setTextOrigin(VPos.TOP);
final double width = rightText.getLayoutBounds().getWidth();
rightText.relocate(8 * em - width, 0);
pane.getChildren().addAll(leftText, middleText, rightText);
}
setText("");
setGraphic(pane);
}
}
The sample assumes you want to specify a custom text font, here stored in the field _itemFont
. We must set this font for each Text
object before we start measuring and positioning the object. That’s especially important if you rely on CSS styling for fonts. Such styling is applied after the layout calculations in updateItem
have been performed, so you’ll get the correct font but wrong alignments if you don’t also set the font in code before taking metrics.
Even though we want to show plain text, we need to use setGraphic
to enable precise alignment. We put all Text
objects in a basic Pane
that allows absolute positioning. The first Text
is simply left-aligned at (0, 0). The second Text
is also left-aligned but should appear at a “column” position of 4em, based on our custom font, so that’s what we specify in relocate
. And the third Text
should be right-aligned, so we measure its width and subtract it from the desired “column” position of 8em.
Note that you must call both setText
and setGraphic
in every updateItem
call, even if just to clear them. That’s because ListView
optimizes item display by reusing existing ListCell
instances. If you don’t set both display properties, the current instance continues to show the previous item’s values!
All that remains is enabling the new ListCell
styling, and that’s easy with the new lambda syntax: myListView.setCellFactory(t -> new CustomListCell());
If you only have single-line items as in the code sample, you might also wish to call setFixedCellSize
with e.g. 1.1em of your item font. And that’s how you align text fragments in a ListView
item.
I would have loved to see a screen shot of this
Well, it’s just aligned text columns… not very exciting! But the first screenshot in the Star Chess announcement shows an example. The ListView in the lower right uses this alignment method, except for the interspersed headings.
This worked for me…thanks so much! One issue is that I was not able to figure out how to setStyle for -fx-text-fill; somehow, when I switched the item datatype from String to my composite type (two String properties), using the above method, this single style setting for text fill stopped working. Other style settings work fine. As a workaround, I just changed the font to bold and set the background to a different color.