Skip to content

TableView SmartResize

Edvin Syse edited this page Sep 6, 2016 · 10 revisions

WikiDocumentationTableView SmartResize

TableView SmartResize Policy

The JavaFX TableView is a powerful visualisation control used in almost every business application out there, but there has always been one critical part missing: The ability to resize columns intuitively, both by default and through configuration. As a result, many JavaFX applications are missing the polish and accessibility expected from today's business software.

The SmartResize.POLICY tries to bridge this gap by providing sensible defaults combined with powerful and dynamic configuration options.

Usage

To apply the resize policy to a TableView we configure the columnResizePolicy of the table. For this discussion we will use a list of hotel rooms. This is our initial table with the SmartResize Policy activated:

tableview(rooms) {
    column("#", Room::id)
    column("Number", Room::number)
    column("Type", Room::type)
    column("Bed", Room::bed)

    columnResizePolicy = SmartResize.POLICY
}

Here is a picture of the table with the SmartResize policy activated:

The default settings gave each column the space it needs based on it's content and gave the remaining width to the last column. When you resize a column by dragging the divider between column headers, only the column immediately to the right will be affected, to avoid pushing the columns to the right outside the viewport of the TableView.

While this often presents a pleasant default, there is a lot more we can do to improve user experience in our particular case. It is evident that our table didn't need the full 800 pixels it was provided, but it gives us a nice chance to elaborate on the configuration options of the SmartResize policy.

The bed column is way too big, and it seems more sensible to give the extra space to the Type column, since it might contain arbitrary long descriptions of the room. To give the extra space to the Type column, we change it's column definition:

column("Type", Room::type).remainingWidth()

The result is show below:

Now it becomes apparent that the Bed column looks cramped, being pushed all the way to the left. We configure it to keep it's desired width based on the content plus 50 pixels padding:

column("Bed", Room:bed").contentWidth(padding = 50.0)

The result is a much more pleasant visual impression:

These things might not seem like much, but it means a lot to people who are forced to stare at your software all day!

If the user increases the width of the Number column, the Type column will gradually decrease in width, until it reaches it's default width of 10 pixels (JavaFX default). After that, the Bed column must start giving away it's space. We don't ever want the Bed column to be smaller that what we configured, so we tell it to use it's content based width plus the padding we added as it's minimal width:

column("Bed", Room:bed").contentWidth(padding = 50.0, useAsMin = true)

Trying to decrease the Bed column either by explicitly expanding the Type column or implicitly by expanding the Number column will simply be denied by the resize policy. It is worth noting that there is also a useAsMax choice for the contentWidth resize type. This would effectively result in a hard coded, unresizable column, based on the required content width plus any configured padding. That would be a good way to go for the # column:

column("#", Room::id).contentWidth(useAsMin = true, useAsMax = true)

The rest of the examples will probably not benefit the user of our example view, but we still have more configuration options to cover, so let's try to make the Number column 25% of the total table width:

column("Number", Room::number).pctWidth(25.0)

When you resize the tableview, the Number column will gradually expand to keep up with our 25% width requirement, while the Type column gets the remaining extra space.

An alternative approach to percentage width is to specify a weight. This time we add weights to both Number and Type:

column("Number", Room::number).weigthedWidth(1.0)
column("Type", Room::type).weigthedWidth(3.0)

The two weighted columns share the remaining space after the other columns have received their fair share, and since the Type column has a weight that is three times bigger than the Number column, it's size will be three times bigger as well, and this will be reevaluated as the tableview itself is resized.

This setting will make sure we keep the mentioned ratio between the two columns, but it might become problematic if the Table View is resized to be very small. The the Number column would not have space to show all of it's content, so we guard against that by specifying that it should never grow below the space it needs to show it's content, plus some padding, for good measure:

column("Number", Room::number).weigthedWidth(1.0, minContentWidth = true, padding = 10.0)

This makes sure our table behaves nicely also under constrained width conditions.

Dynamic content resizing

Since some of the resizing modes are based on the actual content of the columns, they might need to be reevaluated even when the table or it's columns aren't resized. For example, if you add or remove content items from the backing list, the required content measurements might need to be updated. For this you can call the requestResize function after you have manipulated the items:

SmartResize.POLICY.requestResize(tableView)

In fact, you can ask the TableView to ask the policy for you:

tableView.requestResize()

Statically setting the content width

In most cases you probably want to configure your column widths based on either the total available space or the content of the columns. In some cases you might want to configure a specific width, that that can be done with the prefWidth function:

column("Bed", Room::bed).prefWidth(200.0)

A column with a preferred width can be resized, so to make it non-resizable, use the fixedWidth function instead:

column("Bed", Room::bed).fixedWidth(200.0)

When you hard code the width of the columns you will most likely end up with some extra space. This space will be awarded to the right most resizable column, unless you specify remainingWidth() for one or more column. In that case, these columns will divide the extra space between them.

In the case where not all columns can be afforded their preferred width, all resizable columns must give away some of their space, but the SmartResize Policy makes sure that the column with the biggest reduction potential will give away it's space first. The reduction potential is the difference between the current width of the column and it's defined minimum width.

Next: Error Handler

Clone this wiki locally