Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX TableColumn resize to fit cell content

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?

like image 307
jckdnk111 Avatar asked Apr 25 '14 04:04

jckdnk111


4 Answers

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);
    }
}
like image 50
ray_ray_ray Avatar answered Sep 28 '22 13:09

ray_ray_ray


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.

like image 8
Tomasz Avatar answered Oct 16 '22 00:10

Tomasz


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();
                    }
                }
            }
        });
    }
}
like image 5
yelliver Avatar answered Oct 16 '22 00:10

yelliver


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).

like image 3
Ahli Avatar answered Oct 15 '22 22:10

Ahli