JavaFX表格列调整大小以适应单元格内容

11

我正在寻找一种方法来调整 TableView 中 TableColumn 的大小,以便在每个单元格中都能看到所有内容(即不截断)。

我注意到双击列分隔符会自动调整列的宽度以适应其单元格内容。是否有办法以编程方式触发此操作?


你在 Scene Builder 中创建了视图吗? - raj
是的,视图是在场景构建器中创建的。 - jckdnk111
那么我认为您无法将其展开到父级已展开。 - raj
1
我认为它被创建的地方并不重要。我只想在表格填充内容时通过编程强制调整大小。基本上,我想避免单元格中的数据被截断的情况。 - jckdnk111
2
你好,你解决了你的问题吗?我需要做完全相同的事情 :p - John
所有的答案对我都不起作用,所以我实现了自己的解决方案:https://dev59.com/lGUq5IYBdhLWcg3wQ-NF#61935985(与resizeColumnToFitContent方法相关的答案在较新版本的JavaFX中不起作用) - Alessandro Roaro
5个回答

8

在浏览javafx源码时,我发现当你点击TableView列分隔符时实际调用的方法是:

/*
 * 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);
}

这个声明定义在

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

方法签名
protected abstract void resizeColumnToFitContent(TC tc, int maxRows)

由于它受到保护,您无法从tableView.getSkin()中调用它,但是您可以始终通过扩展TableViewSkin并仅覆盖resizeColumnToFitContent方法并使其公开来实现。


2
刚刚进入了https://javafx-jira.kenai.com/browse/RT-39533,以便能够以更少的hacky方式进行操作。 - rli
1
Jira有任何更新吗?链接只会重定向到新的漏洞网站。 - Darth Ninja

5

按照@Tomasz的建议,我通过反射进行解决:

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();
                    }
                }
            }
        });
    }
}

很棒的解决方案!完美运行。 - Ruslan Gabbazov
4
在JDK 9上,我遇到了NoSuchMethodException异常 :( - atlantis
对我没用,只是让我无法手动调整列大小。 - Axel Kennedal
@AxelKennedal,你使用哪个Java版本? - yelliver
1
TableViewSkin的“columnToFitMethod”方法在我的Java 8托管程序中,如果tableView.getSkin()返回null,则会抛出NullPointerException,这使得它对我毫无用处。此外,我不会依赖于私有遗留sun类仍然可用... - Infernoz

3

JavaFX当前版本(例如15-ea+1)会调整表格列的大小,如果尚未设置prefWidth属性(或者将其设置为80.0F,请参见TableColumnHeader(点击此处查看链接))。


2
在Java 16中,我们可以扩展TableView以使用自定义的TableViewSkin,该皮肤又使用自定义的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);
    }
}

在使用这个自定义的TableView替换TableViiew后,我在空间使用方面没有看到任何区别,仍然有很多空白列的空间,而长文本列的空间不足。在FXML中,我没有设置表格的prefWidth。是否需要进行其他设置? - vic
跟进我的上一个评论。在将setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY)更改为setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY)后,定制代码按预期工作。肯定不是prefWidth的问题。 - vic

0

您可以使用 tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);

如果TableView.UNCONSTRAINED_RESIZE_POLICY不能满足您的需求,您还可以尝试在两种策略TableView.CONSTRAINED_RESIZE_POLICYTableView.UNCONSTRAINED_RESIZE_POLICY之间切换。

这里有一个有用的链接。


不幸的是,那只会为用户触发的列调整设置策略。我正在寻找以编程方式适应列内容的方法(即在添加/更新/删除数据后自动调整列大小)。 - jckdnk111
1
你可以通过监听器在这两个策略之间切换,然后重新绘制(重新布局)。 - damat-perdigannat
这确实调整了列的大小,但它试图将它们全部适应可视空间(即尝试使表格不需要水平滚动条)。因此,它实际上会截断所有内容。 - jckdnk111
在发生截断之前,您可以将策略设置为“无限制”。 - damat-perdigannat
我最初将策略设为未受限制。更新数据后,我将策略设置为受限制,然后立即将其更改回未受限制,最后按照您的建议在 TableView 上执行 requestLayout()。结果是每一列都被截断了。 - jckdnk111
1
实际上我所说的是,最初将策略设置为受限制的(因为没有数据),然后当数据变得很长时,我们将策略设置为不受限制。但无论如何,在请求布局时应该避免截断表格。肯定有些东西丢失了。你可以尝试再调整一下代码。如果情况仍未改变,可以再次在这里给我留言。祝好:D - damat-perdigannat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接