1

I have a JavaFX table column which I would like to display a comma-separated list of strings, unless the text does not fit within the current bounds of the cell, at which point it would display, for example, "Foo and 3 others...", or "3 Bars", i.e. reflecting the number of elements in the list.

Is there a way to check, when building a CellValueFactory for a table column, whether the text would overrun the cell, so I could switch between these two behaviors?

Chris
  • 409
  • 3
  • 17
  • Possible duplicate of [How to get the size of a label before it is laid out?](https://stackoverflow.com/questions/30983584/how-to-get-the-size-of-a-label-before-it-is-laid-out) – Jai Oct 18 '18 at 01:59

1 Answers1

1

You can specify an overrun style for Labeled controls like TableCells.

Overrun style ELLIPSIS will automatically add these ellipses as needed to indicate if the content would have extended outside of the label.

I recommend doing this in a cell factory, like so:

column.setCellFactory(() -> {
    TableCell<?, ?> cell = new TableCell<>();
    cell.setTextOverrun(OverrunStyle.ELLIPSIS);
    return cell;
});

So you would need to use the cell factory instead of the cell value factory. The reason I recommend cell factory is because the table creates and destroys cells on its own as needed, so you'd have a hard time getting all those instances and setting their overrun behavior if you didn't have control of those cells creation like you do with the cell factory.


New attempt

Try something along these lines, you might need to tweak the method to get the length of your string, and you might want to try to figure out the current length of the table cell whenever you update it, but this should get you started. Think it's a decent approach?

public class TestApplication extends Application {

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


    public void start(final Stage stage) {
        stage.setResizable(true);

        TestTableView table = new TestTableView();
        ObservableList<String> items = table.getItems();
        items.add("this,is,short,list");
        items.add("this,is,long,list,it,just,keeps,going,on,and,on,and,on");

        Scene scene = new Scene(table, 400, 200);
        stage.setScene(scene);

        stage.show();
    }


    /**
     * Note: this does not take into account font or any styles.
     * <p>
     * You might want to modify this to put the text in a label, apply fonts and css, layout the label,
     * then get the width.
     */
    private static double calculatePixelWidthOfString(String str) {
        return new Text(str).getBoundsInLocal().getWidth();
    }

    public class TestTableView extends TableView<String> {

        public TestTableView() {
            final TableColumn<String, CsvString> column = new TableColumn<>("COL1");
            column.setCellValueFactory(cdf -> {
                return new ReadOnlyObjectWrapper<>(new CsvString(cdf.getValue()));
            });
            column.setCellFactory(col -> {
                return new TableCell<String, CsvString>() {

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

                        if (item == null || empty) {
                            setText(null);
                        } else {

                            String text = item.getText();
                            // get the width, might need to tweak this.
                            double textWidth = calculatePixelWidthOfString(text);
                            // might want to compare against current cell width
                            if (textWidth > 100) {
                                // modify the text here
                                text = item.getNumElements() + " elements";
                            }

                            setText(text);
                        }
                    }
                };
            });
            this.getColumns().add(column);
        }
    }

    private static class CsvString {

        private final String text;
        private final String[] elements;


        public CsvString(String string) {
            Objects.requireNonNull(string);
            this.text = string;
            this.elements = string.split(" *, *");
        }


        public int getNumElements() {
            return elements.length;
        }


        public String getText() {
            return text;
        }
    }
}
xtratic
  • 4,600
  • 2
  • 14
  • 32
  • This would not produce the result required in the question. When the cell is overrun, Instead of adding ellipses, I'm looking to set the cell value to reflect the number of elements in the cell. See the examples in the question. – Chris Oct 17 '18 at 15:29
  • Oh, sorry, I didn't get that. – xtratic Oct 17 '18 at 15:39
  • @Chris Edited my answer with an approach to solve your problem. – xtratic Oct 17 '18 at 17:03
  • thanks for the answer. I was hoping there would be a pre-defined way of doing this in JavaFX, but your method appears to be the best available one. Thanks again! – Chris Oct 18 '18 at 07:31
  • @Chris Thanks, I'm not sure if it's the best but at least it's something. Problems I could see you running into would mainly be getting the width of the original text or the table cell. If you use any styles on the text you'd have to tweak how you measure it's width and since the cells aren't laid-out yet you might have to do some tricks to measure them. – xtratic Oct 18 '18 at 12:54
  • @Chris Also it's best to cache the `CsvStrings`, so that they don't have to be re-computed on *every* cell update and I don't think I'm doing that here, unless the cell value factory caches them. You might have to make the `TableView` itself hold `CsvString` and add them to the table view items. – xtratic Oct 18 '18 at 12:58