I have been trying to figure out how to auto-size TreeView columns to fit their content for a while now. This has been asked before, but the response was minimal and not acceptable.
[javafx column in tableview auto fit size
Curiously, the behavior I'm looking for is exactly what happens when you double-click a column resize line in TableView. However, I have been unable to figure out how this occurs when looking through the JavaFX source code.
Does anyone know how/where the double-click behavior on a column resize line is handled in JavaFX TableView/TableColumn/TableColumnHeader?
If possible, I would simply like the columns to automatically do at first render what happens when you double-click the resize line. How this isn't already one of the available pre-created column size constraint policies is beyond me.
The solution that works for me on JavaFX8:
Set the columnResizePolciy
to CONSTRAINED_RESIZE_POLICY
<TableView fx:id="table" >
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
There's a few missing pieces in your code like obj isn't declared. I also can't find the method you're using anywhere in that class. I'm using 1.8.0_20-ea-b05 ,or so -version tells me. I found a similar method in TableViewSkin.
public static void autoSizeTableViewColumns(final TableView<?> tableView) {
TableViewSkin<?> skin = (TableViewSkin<?>) tableView.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
NestedTableColumnHeader rootHeader = headerRow.getRootHeader();
for (TableColumnHeader columnHeader : rootHeader.getColumnHeaders()) {
try {
TableColumn<?, ?> column = (TableColumn<?, ?>) columnHeader.getTableColumn();
if (column != null) {
Method method = skin.getClass().getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
method.setAccessible(true);
method.invoke(skin,column, 30);
}
} catch (Throwable e) {
e = e.getCause();
e.printStackTrace(System.err);
}
}
}
Here is the FINAL solution I came up with for JavaFX 2.2 which now also accounts for the width of the column header:
/**
* Auto-sizes table view columns to fit its contents.
*
* @note This is not a column resize policy and does not prevent manual
* resizing after this method has been called.
* @param tableView
* The table view in which to resize all columns.
*/
public static void autoSizeTableViewColumns(final TableView<?> tableView)
{
autoSizeTableViewColumns(tableView, -1, -1);
}
/**
* Auto-sizes table view columns to fit its contents.
*
* @note This is not a column resize policy and does not prevent manual
* resizing after this method has been called.
*
* @param tableView
* The table view in which to resize all columns.
* @param minWidth
* Minimum desired width of text for all columns.
* @param maxWidth
* Maximum desired width of text for all columns.
*/
public static void autoSizeTableViewColumns(final TableView<?> tableView, int minWidth, int maxWidth)
{
TableViewSkin<?> skin = (TableViewSkin<?>) tableView.getSkin();
if (skin == null)
{
AppLogger.getLogger().warning(tableView + " skin is null.");
return;
}
TableHeaderRow headerRow = skin.getTableHeaderRow();
NestedTableColumnHeader rootHeader = headerRow.getRootHeader();
for (Node node : rootHeader.getChildren())
{
if (node instanceof TableColumnHeader)
{
TableColumnHeader columnHeader = (TableColumnHeader) node;
try
{
autoSizeTableViewColumn(columnHeader, minWidth, maxWidth, -1);
}
catch (Throwable e)
{
e = e.getCause();
AppLogger.getLogger().log(Level.WARNING, "Unable to automatically resize tableView column.", e);
}
}
}
}
/**
* Auto-sizes table view columns to fit its contents.
*
* @note This is not a column resize policy and does not prevent manual
* resizing after this method has been called.
*
* @param col
* The column to resize.
* @param minWidth
* Minimum desired width of text for this column. Use -1 for no minimum
* width.
* @param maxWidth
* Maximum desired width of text for this column. Use -1 for no maximum
* width.
* @param maxRows
* Maximum number of rows to examine for auto-resizing. Use -1
* for all rows.
*/
public static void autoSizeTableViewColumn(TableColumn<?,?> column, int minWidth, int maxWidth, int maxRows)
{
TableView<?> tableView = column.getTableView();
TableViewSkin<?> skin = (TableViewSkin<?>) tableView.getSkin();
if (skin == null)
{
AppLogger.getLogger().warning(tableView + " skin is null.");
return;
}
TableHeaderRow headerRow = skin.getTableHeaderRow();
NestedTableColumnHeader rootHeader = headerRow.getRootHeader();
for (Node node : rootHeader.getChildren())
{
if (node instanceof TableColumnHeader)
{
TableColumnHeader columnHeader = (TableColumnHeader) node;
if(columnHeader.getTableColumn().equals(column))
{
autoSizeTableViewColumn(columnHeader, minWidth, maxWidth, maxRows);
}
}
}
}
/**
* Auto-sizes a table view column to fit its contents.
*
* @note This is not a column resize policy and does not prevent manual
* resizing after this method has been called.
*
* @param col
* The column to resize.
* @param minWidth
* Minimum desired width of text for this column. Use -1 for no minimum
* width.
* @param maxWidth
* Maximum desired width of text for this column. Use -1 for no maximum
* width.
* @param maxRows
* Maximum number of rows to examine for auto-resizing. Use -1
* for all rows.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void autoSizeTableViewColumn(TableColumnHeader header, int minWidth, int maxWidth, int maxRows)
{
TableColumn<?, ?> col = header.getTableColumn();
if(col != null)
{
List<?> items = col.getTableView().getItems();
if (items == null || items.isEmpty())
return;
Callback cellFactory = col.getCellFactory();
if (cellFactory == null)
return;
TableCell cell = (TableCell) cellFactory.call(col);
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 TableColumn
cell.getProperties().put("deferToParentPrefWidth", 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.getInsets().getLeft() + r.getInsets().getRight();
}
int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
double desiredWidth = 0;
// Check header
Label headerLabel = (Label) header.lookup(".label");
String headerText = headerLabel.getText();
if (!headerLabel.getContentDisplay().equals(ContentDisplay.GRAPHIC_ONLY) && headerText != null)
{
Text text = new Text(headerLabel.getText());
text.setFont(headerLabel.getFont());
desiredWidth += text.getLayoutBounds().getWidth() + headerLabel.getLabelPadding().getLeft() + headerLabel.getLabelPadding().getRight();
}
Node headerGraphic = headerLabel.getGraphic();
if((headerLabel.getContentDisplay().equals(ContentDisplay.LEFT) || headerLabel.getContentDisplay().equals(ContentDisplay.RIGHT)) && headerGraphic != null)
{
desiredWidth += headerGraphic.getLayoutBounds().getWidth();
}
// Handle minimum width calculations
// Use a "w" because it is typically the widest character
Text minText = new Text(StringUtils.repeat("W", Math.min(0, minWidth)));
minText.setFont(headerLabel.getFont());
// Check rows
double minPxWidth = 0;
for (int row = 0; row < rows; row++)
{
cell.updateTableColumn(col);
cell.updateTableView(col.getTableView());
cell.updateIndex(row);
// Handle minimum width calculations
// Just do this once
if(row == 0)
{
String oldText = cell.getText();
// Use a "w" because it is typically the widest character
cell.setText(StringUtils.repeat("W", Math.max(0, minWidth)));
header.getChildren().add(cell);
cell.impl_processCSS(false);
minPxWidth = cell.prefWidth(-1);
header.getChildren().remove(cell);
cell.setText(oldText);
}
if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null)
{
header.getChildren().add(cell);
cell.impl_processCSS(false);
desiredWidth = Math.max(desiredWidth, cell.prefWidth(-1));
desiredWidth = Math.max(desiredWidth, minPxWidth);
header.getChildren().remove(cell);
}
}
desiredWidth = desiredWidth + padding;
if(maxWidth > 0)
{
desiredWidth = Math.min(maxWidth, desiredWidth);
}
col.impl_setWidth(desiredWidth);
}
}
Keep in mind that this is "hacking" protected methods which could change at any time with a JavaFX update and break the method. I believe this already changes in JavaFX 8. This could also break depending upon how your application is signed an deployed.
It isn't pretty, but JavaFX has hidden this functionality so unless you want to re-implement the entire thing, this is the best approach I've found.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With