JavaFX TableColumn resize to fit cell content
Asked Answered
E

5

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?

Envy answered 25/4, 2014 at 4:52 Comment(6)
have you created view in scene builderWafd
Yes, the view was created in scene builder.Envy
then i thing you cant make it expand on parent expandedWafd
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.Envy
Hi, did you find solution for your problem? I need to do exactly the same thing :pBrigand
All the answers didn't work for me, so I implemented my own solution: https://mcmap.net/q/282657/-auto-fit-size-column-in-table-view (the answers related to the resizeColumnToFitContent method don't work in newer version of JavaFX)Kwangju
C
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.

Couch answered 17/11, 2014 at 10:51 Comment(2)
Just entered javafx-jira.kenai.com/browse/RT-39533 to be able to do so in a less hacky way.Disgorge
Any update on the jira? The link only redirects to a new bugs site.Squirt
T
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();
                    }
                }
            }
        });
    }
}
Tamalatamale answered 31/7, 2016 at 17:4 Comment(5)
Great solution! Works perfect.Heliotrope
On JDK 9, I get a NoSuchMethodException :(Ohare
Doesn't work for me, just makes it so I can't resize columns manuallyGlendaglenden
@AxelKennedal which Java version do you use?Tamalatamale
The TableViewSkin "columnToFitMethod" method throws a NullPointerException if tableView.getSkin() returns null in my Java 8 hosted program, which makes this useless to me. Also, I would not rely on private legacy sun classes remaining available...Orton
A
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).

Aubry answered 6/2, 2020 at 2:7 Comment(0)
A
3

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);
    }
}
Arciniega answered 14/11, 2021 at 9:59 Comment(2)
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?De
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.De
A
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.

Ann answered 25/4, 2014 at 12:37 Comment(6)
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).Envy
What you can do is toggle between these two policies via a listener then repaint(re-layout).Ann
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.Envy
You can set the policy to unconstrained before truncation happens.Ann
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.Envy
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 :DAnn

© 2022 - 2024 — McMap. All rights reserved.