JavaFX Tip 22: Autosize (Tree) Table Columns

One of the first things mentioned as a “missing feature” in the JavaFX “Missing Features Survey” was the ability to auto-resize columns in tables / tree tables. It is correct that there is no public API for it, but when you pay close attention then you will notice that there must be code for doing this somewhere inside JavaFX, because the user can auto-resize a column by double clicking on the divider line between the column and the next column to the right.

But like most people I felt that this was not good enough for my code. I wanted an API for FlexGanttFX that would allow the user to auto resize one or all columns inside the Gantt charts. So I searched for the code that was hidden somewhere in the tree table  or tree table skin (can’t actually remember where) and reused it with some minor modifications in my classes.

The following is the result of this work. It targets the TreeTableView and not the TableView, but making it work for the standard table is straight-forward. Simply replace all TreeTableColumn occurrences with TableColumn. Please notice that resizing all rows can have a serious performance impact, so you might have to limit the number of rows that will be considered for the calculations via the maxRows parameter.

	/**
	 * This method will resize all columns in the tree table view to ensure that
	 * the content of all cells will be completely visible. Note: this is a very
	 * expensive operation and should only be used when the number of rows is
	 * small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumns() {
		resizeColumns(-1);
	}

	/**
	 * This method will resize all columns in the tree table view to ensure that
	 * the content of all cells will be completely visible. Note: this is a very
	 * expensive operation and should only be used with a small number of rows.
	 *
	 * @param maxRows
	 *            the maximum number of rows that will be considered for the
	 *            width calculations
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumns(int maxRows) {
		for (TreeTableColumn<R, ?> column : getTreeTable().getColumns()) {
			resizeColumn(column, maxRows);
		}
	}

	/**
	 * This method will resize the given column in the tree table view to ensure
	 * that the content of the column cells will be completely visible. Note:
	 * this is a very expensive operation and should only be used when the
	 * number of rows is small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumn(TreeTableColumn<R, ?> column) {
		resizeColumn(column, -1);
	}

	/**
	 * This method will resize the given column in the tree table view to ensure
	 * that the content of the column cells will be completely visible. Note:
	 * this is a very expensive operation and should only be used when the
	 * number of rows is small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumn(TreeTableColumn<R, ?> tc, int maxRows) {
		final TreeTableColumn col = tc;

		List<?> items = getItems();
		if (items == null || items.isEmpty()) {
			return;
		}

		Callback cellFactory = tc.getCellFactory();
		if (cellFactory == null) {
			return;
		}

		TreeTableCell<R, ?> cell = (TreeTableCell<R, ?>) 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("deferToParentPrefWidth", Boolean.TRUE); //$NON-NLS-1$

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

		TreeTableRow<R> treeTableRow = new TreeTableRow<>();
		treeTableRow.updateTreeTableView(treeTableView);

		int rows = maxRows == -1 ? items.size()
				: Math.min(items.size(), maxRows);
		double maxWidth = 0;
		for (int row = 0; row < rows; row++) {
			treeTableRow.updateIndex(row);
			treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));

			cell.updateTreeTableColumn(col);
			cell.updateTreeTableView(treeTableView);
			cell.updateTreeTableRow(treeTableRow);
			cell.updateIndex(row);

			if ((cell.getText() != null && !cell.getText().isEmpty())
					|| cell.getGraphic() != null) {
				getChildren().add(cell);
				cell.impl_processCSS(false);

				double w = cell.prefWidth(-1);

				maxWidth = Math.max(maxWidth, w);
				getChildren().remove(cell);
			}
		}

		// dispose of the cell to prevent it retaining listeners (see RT-31015)
		cell.updateIndex(-1);

		// RT-23486
		double widthMax = maxWidth + padding;
		if (treeTableView
				.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
			widthMax = Math.max(widthMax, tc.getWidth());
		}

		tc.impl_setWidth(widthMax);
	}

2 Comments

  1. I appreciate you making this post. I haven’t had time to dig into it yet and see how it all works. However this seems like something that should be an option out of the gate. I cannot think of any situation in which I would want the columns in a tableview to clip the content.

  2. […] Automatic column sizing – a way to adjust the width of a column to its content, to ensure readability of all values. This feature is actually implemented inside the TableView codebase. It is used to resize the column when the user double clicks on the edges of the column headers. However, the code for this is not public. I have recently posted an article showing how to do it. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s