JavaSwingJTable and TableModel: Layout


Layout

This discussion is based on JDK1.3. A lot of (internal) things have been changed, the most important possibly the new interpretation of columnMarginChanged() in TableColumnModel, which makes it possible to change TableColumn (preferred) widths and have the JTable react automatically (later); and which makes the user-resizing code (in the TableHeaderUI) cleaner.

Note: In the JDK1.2, the widths set on the TableColumns excluded the column margin, with the JDK1.3, they include it (see Changes). (This with maybe done with consistency to different row heights, which are now also inclusive, and which seems to make the row height model management easier). It is not really good because if the width is really calculated based on renderer component size, it doesn't include the column margin at first, and thus must be adjusted every time the margin changes.

I have included some 1.4 updates, but that has made the text somewhat incoherent. Many things are true only for 1.3. This needs a major rewrite.

Widths and heights (preferred, minimum, maximum)

JTable

JTableHeader

Further points

There is no (sensible) policy at all for the LAF to return something different (except for the header height), since getCellRect() and column layouting are handled in the JTable itself, so the LAF cannot place the cells differently. The code could just as well be placed in JTable/JTableHeader itself.

In early versions of Swing, at least getHeaderRect() was delegated to the UI (#4106449), but this way, which would be cleaner in principle (but wasn't in the implementation) was removed.

The JTableHeader is usually (by ViewportLayout) as wide as its Viewport. If the main viewport of the JScrollPane is larger than the JTable, the JTableHeader fills it, while the JTable is only as large as the column model indicates.

Missing Border/Insets support

JTable and JTableHeader ignore their Insets (#4222732), so setting a Border causes it to be painted over the cells:

Layout

There are two different kinds of layout:

1.4: real layout has moved from sizeColumnsToFit() into doLayout() directly. The code is also a little more robust, since it tries twice to adjust the column widths to the JTable's size (The problem was when columns hit minimum/maximum widths during resizing).

sizeColumnsToFit() should not be called anymore. That actually means a loss of flexibility (the resizing column is taken by a private method from the JTableHeader, so it cannot be replaced as easily as in 1.3 by a custom one).

pre-1.3: if the JTable didn't change width, no layout was performed at all. But unless the autoResizeMode is AUTO_RESIZE_OFF, JTables never change width (at least in a JScrollPane), so sizeColumnsToFit() had to be called manually after each column model (or column) change.

Both JTable and JTableHeader have resizeAndRepaint(), which calls revalidate() and repaint() when normal revalidation. Most of the time, it is called instead of both methods directly. It is called from:

JTable

JTableHeader

When the JTable is actually validated (doLayout()), it calls sizeColumnsToFit(-1); (1.4: does it directly there). The header relies completely on its JTable to layout the columns.

A situation where the columns widths may be reconsidered without actually calling revalidate() was pre-1.4-columnMarginChanged (for example when a column is resized).

For the row, the explicit sizes set for all rows or for each row are taken.

Column layout

sizeColumnsToFit() does in fact two (or three) unrelated things:

autoResizeMode: AUTO_RESIZE_OFF

Basically, widths and preferred widths are regarded as the same and only need synchronizing. Programmatically, setting the width is useless, since it will be overridden again.

different autoResizeMode

TableColumn width changes

Maximum and minimum width are never set. Preferred width and width are set by TableColumn to fit into the range by minimum/maximum width if they are changed.

Preferred width: accommodateDelta

Width: standard layout, accommodateDelta, BasicTableHeaderUI (resizing)

Column resizing

BasicTableHeaderUI sets the width of the resizing column, and that's what makes sense for a controller, since it cannot assume that the preferred width has a one-to-one correspondence with the width, and resizing would be very confusing for the user if the visible width didn't adjust by the "right" amount.

The change is noticed in JTable.columnMarginChanged(), where:

It is assumed that the resizing column is sized on the right size; since JTableHeader doesn't carry any information about it, that is a necessary assumption. It makes resizing on the left of the first column impossible, unless one accepts incompability with all resize modes but AUTO_RESIZE_OFF and AUTO_RESIZE_ALL_COLUMNS. It would have been better to have both a column and a bias (left or right size) because a) this leads to clearer code that handles the resizing and b) it is more powerful.

Column dragging

Only the painting code paints the column (possibly) at a different position. No actual relayout takes place; if it is moved (i.e. dragged far enough to change place with another column), it just appears with the same width at the new position.

No column layout

One can change JTable to use the widths of the TableColumns without adjustments (column layout).


(C) 2001-2009 Christian Kaufhold (swing@chka.de)