11

I'm looking for a way to resize a TableColumn in a TableView so that all of the content is visible in each cell (i.e. no truncation).

I noticed that double clicking on the column divider's does auto fit the column to the contents of its cells. Is there a way to trigger this programmatically?

Java Man
  • 1,854
  • 3
  • 21
  • 43
jckdnk111
  • 2,280
  • 5
  • 33
  • 43
  • have you created view in scene builder – raj Apr 25 '14 at 05:02
  • Yes, the view was created in scene builder. – jckdnk111 Apr 25 '14 at 05:17
  • then i thing you cant make it expand on parent expanded – raj Apr 25 '14 at 05:36
  • 1
    I don't think it really matters where it was created. I just want to programmatically force a resize when the table is filled with content. Basically I want to avoid a situation where the data in a cell is truncated. – jckdnk111 Apr 25 '14 at 11:44
  • 2
    Hi, did you find solution for your problem? I need to do exactly the same thing :p – John Apr 19 '16 at 21:21
  • All the answers didn't work for me, so I implemented my own solution: https://stackoverflow.com/a/61935985/2639515 (the answers related to the resizeColumnToFitContent method don't work in newer version of JavaFX) – Alessandro Roaro May 21 '20 at 13:49

5 Answers5

8

Digging through the javafx source, I found that the actual method called when you click TableView columns divider is

/*
 * FIXME: Naive implementation ahead
 * Attempts to resize column based on the pref width of all items contained
 * in this column. This can be potentially very expensive if the number of
 * rows is large.
 */
@Override protected void resizeColumnToFitContent(TableColumn<T, ?> tc, int maxRows) {
    if (!tc.isResizable()) return;

// final TableColumn<T, ?> col = tc;
    List<?> items = itemsProperty().get();
    if (items == null || items.isEmpty()) return;

    Callback/*<TableColumn<T, ?>, TableCell<T,?>>*/ cellFactory = tc.getCellFactory();
    if (cellFactory == null) return;

    TableCell<T,?> cell = (TableCell<T, ?>) cellFactory.call(tc);
    if (cell == null) return;

    // set this property to tell the TableCell we want to know its actual
    // preferred width, not the width of the associated TableColumnBase
    cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);

    // determine cell padding
    double padding = 10;
    Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
    if (n instanceof Region) {
        Region r = (Region) n;
        padding = r.snappedLeftInset() + r.snappedRightInset();
    } 

    int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
    double maxWidth = 0;
    for (int row = 0; row < rows; row++) {
        cell.updateTableColumn(tc);
        cell.updateTableView(tableView);
        cell.updateIndex(row);

        if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
            getChildren().add(cell);
            cell.applyCss();
            maxWidth = Math.max(maxWidth, cell.prefWidth(-1));
            getChildren().remove(cell);
        }
    }

    // dispose of the cell to prevent it retaining listeners (see RT-31015)
    cell.updateIndex(-1);

    // RT-36855 - take into account the column header text / graphic widths.
    // Magic 10 is to allow for sort arrow to appear without text truncation.
    TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
    double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
    Node graphic = header.label.getGraphic();
    double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
    double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
    maxWidth = Math.max(maxWidth, headerWidth);

    // RT-23486
    maxWidth += padding;
    if(tableView.getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY) {
        maxWidth = Math.max(maxWidth, tc.getWidth());
    }

    tc.impl_setWidth(maxWidth);
}

It's declared in

com.sun.javafx.scene.control.skin.TableViewSkinBase

method signature

protected abstract void resizeColumnToFitContent(TC tc, int maxRows)

Since it's protected, You cannot call it from e.g. tableView.getSkin(), but You can always extend the TableViewSkin overriding only resizeColumnToFitContent method and make it public.

Tomasz
  • 103
  • 1
  • 5
5

As @Tomasz suggestion, I resolve by reflection:

import com.sun.javafx.scene.control.skin.TableViewSkin;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class GUIUtils {
    private static Method columnToFitMethod;

    static {
        try {
            columnToFitMethod = TableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
            columnToFitMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public static void autoFitTable(TableView tableView) {
        tableView.getItems().addListener(new ListChangeListener<Object>() {
            @Override
            public void onChanged(Change<?> c) {
                for (Object column : tableView.getColumns()) {
                    try {
                        columnToFitMethod.invoke(tableView.getSkin(), column, -1);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}
yelliver
  • 5,648
  • 5
  • 34
  • 65
3

The current versions of JavaFX (e.g. 15-ea+1) resize table columns, if the prefWidth was never set (or is set to 80.0F, see TableColumnHeader enter link description here).

Ahli
  • 31
  • 2
2

In Java 16 we can extend TableView to use a custom TableViewSkin which in turn uses a custom TableColumnHeader

class FitWidthTableView<T> extends TableView<T> {
    
    public FitWidthTableView() {
        setSkin(new FitWidthTableViewSkin<>(this));
    }

    public void resizeColumnsToFitContent() {
        Skin<?> skin = getSkin();
        if (skin instanceof FitWidthTableViewSkin<?> tvs) tvs.resizeColumnsToFitContent();
    }
}

class FitWidthTableViewSkin<T> extends TableViewSkin<T> {
    
    public FitWidthTableViewSkin(TableView<T> tableView) {
        super(tableView);
    }

    @Override
    protected TableHeaderRow createTableHeaderRow() {
        return new TableHeaderRow(this) {
            
            @Override
            protected NestedTableColumnHeader createRootHeader() {
                return new NestedTableColumnHeader(null) {
                    
                    @Override
                    protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
                        return new FitWidthTableColumnHeader(col);
                    }
                };
            }
        };
    }

    public void resizeColumnsToFitContent() {
        for (TableColumnHeader columnHeader : getTableHeaderRow().getRootHeader().getColumnHeaders()) {
            if (columnHeader instanceof FitWidthTableColumnHeader colHead) colHead.resizeColumnToFitContent(-1);
        }
    }
}

class FitWidthTableColumnHeader extends TableColumnHeader {
    
    public FitWidthTableColumnHeader(TableColumnBase col) {
        super(col);
    }

    @Override
    public void resizeColumnToFitContent(int rows) {
        super.resizeColumnToFitContent(-1);
    }
}
ray_ray_ray
  • 316
  • 1
  • 14
  • After replacing TableViiew with this customized TableView, I don't see any difference in terms of space usage, still a lot of space for blank columns and not enough space for a long text column. I don't have the prefWidth set for the table in FXML. Any other set up need? – vic Sep 22 '22 at 15:21
  • To follow up my previous comment. The customized code works as desired after I change setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY) to setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY). Not prefWidth for sure. – vic Sep 22 '22 at 18:52
0

You can use tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);

You can also try switching between the two policies TableView.CONSTRAINED_RESIZE_POLICY and TableView.UNCONSTRAINED_RESIZE_POLICY in case TableView.UNCONSTRAINED_RESIZE_POLICY alone doesn't fit your need.

Here's a useful link.

Abhinav Chauhan
  • 1,304
  • 1
  • 7
  • 24
damat-perdigannat
  • 5,780
  • 1
  • 17
  • 33
  • Unfortunately that will only set the policy for user-triggered column resizes. I'm looking to programmatically fit the column to its content (i.e. auto adjust column sizes after adding / updating / removing data). – jckdnk111 Apr 25 '14 at 13:12
  • 1
    What you can do is toggle between these two policies via a listener then repaint(re-layout). – damat-perdigannat Apr 26 '14 at 00:13
  • That does resize the columns but it tries to fit them all in the viewable space (i.e. tries to make it do the table doesn't need a horizontal scroll bar). So it actually ends up truncating everything. – jckdnk111 May 01 '14 at 18:14
  • You can set the policy to unconstrained before truncation happens. – damat-perdigannat May 02 '14 at 08:37
  • I have the policy initially set to unconstrained. After updating the data I set the policy to constrained then immediately change it back to unconstrained and then finally do a `requestLayout()` on the TableView as you suggest. The result is every column is truncated. – jckdnk111 May 05 '14 at 19:41
  • 1
    Actually what I'm talking about is something like initially setting the policy to constrained (because there's no data), then when the data becomes long, that's the time we set the policy to unconstrained. But anyway, the supposed behavior upon requestLayout is to not truncate the table. There must be something missing. You can try tinkering with the codes a little more. If the situation still didn't change, you can message me here again. Regards :D – damat-perdigannat May 06 '14 at 13:16