42
votes

afaik The TableView in javafx have 2 column resize policies: CONSTRAINED_RESIZE_POLICY and UNCONSTRAINED_RESIZE_POLICY, but I want columns is resized to fit the content of theirs cells I think it's a simple problem in other platform (like datagridview in C#) but can not resolve

15
there seem to be bug for this problem: bugs.openjdk.java.net/browse/…Line

15 Answers

40
votes

After 3 years I come back to this problem again, some suggestions are calculating the size of text of data in each cell (it's complicated depending on font size, font family, padding...)

But I realize that when I click on the divider on table header, it's resized fit to content as I want. So I dig into JavaFX source code I finally found resizeColumnToFitContent method in TableViewSkin, but it is protected method, we can 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();
                    }
                }
            }
        });
    }
}

Note that we call "tableView.getItems()" so we have to call this function after setItems()

17
votes

After testing the previous solutions I finally found one that worked for me. So here is mine (call the method after inserting the data into table):

public static void autoResizeColumns( TableView<?> table )
{
    //Set the right policy
    table.setColumnResizePolicy( TableView.UNCONSTRAINED_RESIZE_POLICY);
    table.getColumns().stream().forEach( (column) ->
    {
        //Minimal width = columnheader
        Text t = new Text( column.getText() );
        double max = t.getLayoutBounds().getWidth();
        for ( int i = 0; i < table.getItems().size(); i++ )
        {
            //cell must not be empty
            if ( column.getCellData( i ) != null )
            {
                t = new Text( column.getCellData( i ).toString() );
                double calcwidth = t.getLayoutBounds().getWidth();
                //remember new max-width
                if ( calcwidth > max )
                {
                    max = calcwidth;
                }
            }
        }
        //set the new max-widht with some extra space
        column.setPrefWidth( max + 10.0d );
    } );
}
6
votes

I think just by overriding a call back function that returns true will solve your problem it will disable the re-sizing of columns and all columns will be re-sized to fit the content of their cells.

Example:

TableView<String[]> table = new TableView<>();
table.setColumnResizePolicy(new Callback<TableView.ResizeFeatures, Boolean>() {
  @Override
  public Boolean call(ResizeFeatures p) {
     return true;
  }
});
3
votes

If you want that only one column fills the remaining width of a table, I have found a pretty straight forward solution, which is short and does not require the hacky reflection solution described above:

DoubleBinding usedWidth = columnA.widthProperty().add(columnB.widthProperty()).add(columnC.widthProperty());

fillingColumn.prefWidthProperty().bind(tableView.widthProperty().subtract(usedWidth));
2
votes

Or to make it short:

// automatically adjust width of columns depending on their content
configAttributeTreeTable.setColumnResizePolicy((param) -> true );
2
votes

I have used the other solutions on this question, and it works pretty good. However, the downside of this is when the width of the TableView is greater than the required width of the TableColumns together. I have created a hack to solve this problem, and it works OK:

orderOverview.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> FXUtils.customResize(orderOverview));

where FXUtils.customResize() is created as follows:

public static void customResize(TableView<?> view) {

    AtomicDouble width = new AtomicDouble();
    view.getColumns().forEach(col -> {
        width.addAndGet(col.getWidth());
    });
    double tableWidth = view.getWidth();

    if (tableWidth > width.get()) {
        TableColumn<?, ?> col = view.getColumns().get(view.getColumns().size()-1);
        col.setPrefWidth(col.getWidth()+(tableWidth-width.get()));
    }

}

I hope this could be helpful for other people as well!

1
votes

This is the way I found :

tableview.setColumnResizePolicy( TableView.CONSTRAINED_RESIZE_POLICY );
idCol.setMaxWidth( 1f * Integer.MAX_VALUE * 50 ); // 50% width
nameCol.setMaxWidth( 1f * Integer.MAX_VALUE * 30 ); // 30% width
ageCol.setMaxWidth( 1f * Integer.MAX_VALUE * 20 ); // 20% width
0
votes

This code autoresizes all column widths in relational proportions to the table width,
while it can fix the first column width to a given value when table width is lower than x

        // To generalize the columns width proportions in relation to the table width,
        // you do not need to put pixel related values, you can use small float numbers if you wish,
        // because it's the relative proportion of each columns width what matters here:

        final float[] widths = { 1.2f, 2f, 0.8f };// define the relational width of each column 

        // whether the first column should be fixed
        final boolean fixFirstColumm = true; 

        // fix the first column width when table width is lower than:
        final float fixOnTableWidth = 360; //pixels 

        // calulates sum of widths
        float sum = 0;
        for (double i : widths) {
            sum += i;
        }

        // calculates the fraction of the first column proportion in relation to the sum of all column proportions
        float firstColumnProportion = widths[0] / sum;

        // calculate the fitting fix width for the first column, you can change it by your needs, but it jumps to this width
        final float firstColumnFixSize = fixOnTableWidth * firstColumnProportion;

        // set the width to the columns
        for (int i = 0; i < widths.length; i++) {
            table.getColumns().get(i).prefWidthProperty().bind(table.widthProperty().multiply((widths[i] / sum)));
            // ---------The exact width-------------^-------------^
    if (fixFirstColumm)
            if (i == 0) {
                table.widthProperty().addListener(new ChangeListener<Number>() {
                    @Override
                    public void changed(ObservableValue<? extends Number> arg0, Number oldTableWidth, Number newTableWidth) {

                        if (newTableWidth.intValue() <= fixOnTableWidth) {

                            // before you can set new value to column width property, need to unbind the autoresize binding
                            table.getColumns().get(0).prefWidthProperty().unbind();
                            table.getColumns().get(0).prefWidthProperty().setValue(firstColumnFixSize);

                        } else if (!table.getColumns().get(0).prefWidthProperty().isBound()) {

                            // than readd the autoresize binding if condition table.width > x
                            table.getColumns().get(0).prefWidthProperty()
                                    .bind(table.widthProperty().multiply(firstColumnProportion));
                        }

                    }
                });
            }
        }

advice to put the code in an separated TableAutoresizeModel class, there you can handle further calculations, for example on hiding columns add listener...

0
votes

I implemented a solution that it's fairly more complicated than the ones that I found here, but that allows a specific column to be resized by double clicking on the header, while still letting the user resize columns manually.

This is achieved by listening to click events on the header of the table (TableHeaderRow). When a double click occurs, the specific column header is found by matching the mouse event X and Y.

Note: to make this work it's necessary that each column has an ID set.

// when skin is loaded (hence css), setup click listener on header to make column fit to content max width on double click
tableView.skinProperty().addListener((a, b, newSkin) -> {
    TableHeaderRow headerRow = (TableHeaderRow) tableView.lookup("TableHeaderRow");
    NestedTableColumnHeader headers = (NestedTableColumnHeader) (headerRow.getChildren().get(1));

    headerRow.setOnMouseClicked(evt -> {
        if (evt.getClickCount() != 2 || evt.getButton() != MouseButton.PRIMARY) return;

        // find the header column that contains the click
        for (TableColumnHeader header : headers.getColumnHeaders()) {
            if (header.contains(header.parentToLocal(evt.getX(), evt.getY()))) {
                fitColumnWidthToContent(header.getId());
            }
        }
        evt.consume();
    });
});

The method that takes care of the resizing is the following:

 private void fitColumnWidthToContent (String colId) {
    // find column matching id
    TableColumn column = null;

    for (TableColumn tempCol : tableView.getColumns()) {
        if (tempCol.getId().equals(colId)) {
            column = tempCol;
            break;
        }
    }

    if (column == null) {
        throw new IllegalStateException("Column ID doesn't match any actual column");
    }

    // set default width to column header width
    Text text = new Text(column.getText());
    double max = text.getLayoutBounds().getWidth();

    for (int i = 0; i < tableView.getItems().size(); i++ ) {
        if (column.getCellData(i) == null) continue;

        text = new Text(column.getCellData(i).toString());
        double textWidth = text.getLayoutBounds().getWidth();

        if (textWidth > max) {
            max = textWidth;
        }
    }

    column.setPrefWidth(max + 12);
}

I hope this can be useful to anyone.

In order to allow also manual resizing, it's necessary to add a bit more code on table initalization:

// listen to width changes in columns and set to pref width (otherwise if for example width changes because of
// user resizing the column, applying the old pref width won't work because it stayed the same)
for (TableColumn col : tableView.getColumns()) {
    col.widthProperty().addListener((obs, oldVal, newVal) -> {
        col.setPrefWidth(newVal.doubleValue());
    });
}
0
votes

I have implemented a solution for TreeTableView. It is still in evolution but it manifests now promising results. Hereafter a description of the solution.

In the control skin class, I added to the control children the TreeTableView and an invisible VBox. A cell factory provide derived cells to the target TreeTableColumn. The derived cells wrap a Label node which is added or removed to the invisible VBox according to the empty property, and which its prefWidth is set according to the cell width. The cells make use of:

getProperties().put(Properties.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE)

I override the cell's computePrefWidth() method as follow:

@Override
protected double computePrefWidth(double height) {

    return Double.max(_box.prefWidth(-1.0), super.computePrefWidth(height) + 24.0);

}

The Vbox width property is bind to the TreeTableColumn's prefWidth. This is required to resize as well the header of the column.

Is worth to note, that at the time being, to simplify the development of a solution, this approach works well with built in sort, order, and resize feature disabled. Ie.

_nameColumn = new TreeTableColumn<>("Name");
_nameColumn.setResizable(false);
_nameColumn.setReorderable(false);
_nameColumn.setSortable(false);

Happy coding

0
votes

@HarleyDavidson 's answer in kotlin

val String.fxWidth: Double
    get() = Text(this).layoutBounds.width

//  call the method after inserting the data into table
fun <T> TableView<T>.autoResizeColumns() {
    columnResizePolicy = TableView.UNCONSTRAINED_RESIZE_POLICY
    columns.forEach { column ->
        column.setPrefWidth(
            (((0 until items.size).mapNotNull {
                column.getCellData(it)
            }.map {
                it.toString().fxWidth
            }.toMutableList() + listOf(
                column.text.fxWidth
            )).maxOrNull() ?: 0.0) + 10.0
        )
    }
}
0
votes

This will set the minimum width of columns based on the font and the text, so that the column names wont be cropped.

public static void setDataTableMinColumnWidth(TableView<?> dataTable)
    {
        for (Node columnHeader : dataTable.lookupAll(".column-header"))
        {
            var columnString = columnHeader.getId();
            if (columnString != null)
            {
                for (Node columnHeaderLabel : columnHeader.lookupAll(".label"))
                {
                    var tableColumn = dataTable.getColumns()
                                               .stream()
                                               .filter(x -> x.getId()
                                                             .equals(columnString))
                                               .findFirst();
                    if (columnHeaderLabel instanceof Label && tableColumn.isPresent())
                    {
                        var label = (Label) columnHeaderLabel;
                        /* calc text width based on font */
                        var theText = new Text(label.getText());
                        theText.setFont(label.getFont());
                        var width = theText.getBoundsInLocal()
                                           .getWidth();
                        /*
                         * add 10px because of paddings/margins for the button
                         */
                        tableColumn.get()
                                   .setMinWidth(width + 10);
                    }
                }
            }
        }
    }

How to use:

dataTable.needsLayoutProperty()
             .addListener((obs, o, n) -> setDataTableMinColumnWidth(dataTable));

For the Columns, the id property needs to be set first:

    TableColumn<BundImportTask, String> columnTask = new TableColumn<>("My Column");
    columnTask.setId("MyColumnId");
    columnTask.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()
                                                                        .fileName()));
-1
votes

After long research. Best Solution is..

tblPlan.setColumnResizePolicy((param) -> true );
Platform.runLater(() -> customResize(tblPlan));

"Custom Resize"

public void customResize(TableView<?> view) {

        AtomicLong width = new AtomicLong();
        view.getColumns().forEach(col -> {
            width.addAndGet((long) col.getWidth());
        });
        double tableWidth = view.getWidth();

        if (tableWidth > width.get()) {
            view.getColumns().forEach(col -> {
                col.setPrefWidth(col.getWidth()+((tableWidth-width.get())/view.getColumns().size()));
            });
        }
    }
-1
votes
<TableView fx:id="datalist" layoutX="30.0" layoutY="65.0" prefHeight="400.0" AnchorPane.bottomAnchor="100.0" AnchorPane.leftAnchor="30.0" AnchorPane.rightAnchor="30.0" AnchorPane.topAnchor="100.0">
        <columns>
            <TableColumn fx:id="number" minWidth="-1.0" prefWidth="-1.0" style="width: auto;" text="number" />
            <TableColumn fx:id="id" minWidth="-1.0" prefWidth="-1.0" text="id" />
            <TableColumn fx:id="name" minWidth="-1.0" prefWidth="-1.0" text="name" />
            <TableColumn fx:id="action" minWidth="-1.0" prefWidth="-1.0" text="todo" />
        </columns>
         **<columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
         </columnResizePolicy>**
      </TableView>
-3
votes
public static void autoFillColumn(TableView<?> table, int col) {
    double width = 0;
    for (int i = 0; i < table.getColumns().size(); i++) {
        if (i != col) {
            width += table.getColumns().get(i).getWidth();
        }
    }
    table.getColumns().get(col).setPrefWidth(table.getWidth() - width);
}