JavaSwingJTable and TableModel: FAQ


FAQ

Scrolling / Interaction with JScrollPane / row and column headers

JTable, as any swing component, doesn't display scroll bars etc. on its own. Instead these are handled by a JScrollPane (or a similar component).

Why doesn't JTable display its column header?

The column headers are displayed by a separate component, a JTableHeader. If the JTable is put inside a JScrollPane, it adds the header automatically as column header of the JScrollPane.

If you don't put it into a JScrollPane, you have to add the header by hand (it's probably the best to use a BorderLayout).

pane.setLayout(new BorderLayout()); // unless already there
pane.add(table, BorderLayout.CENTER);
pane.add(table.getTableHeader(), BorderLayout.NORTH);

This will not work with dragging in JDK1.4beta3 (if you care).

If you don't use a JScrollPane, you won't be able to scroll at all. Unless the JTable is guaranteed to have only few columns and rows, that is a bad idea.

How can I have a JTable in a JScrollPane with horizontal scroll bars?

table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

If autoResizeMode is set differently, the JTable automatically resizes itself to the width of the JScrollPane.

Why is a JTable (with few columns) displayed with space on the right in a JScrollPane?

The default size for components in JScrollPane is the size returned by getPreferredScrollableViewportSize(), which for JTable is set to a (somewhat reasonable) default value independent from the actual widths of the columns (= the preferred size of the JTable). The most simple solution might be to call

table.setPreferredScrollableViewportSize(table.getPreferredSize());

but that also effects the viewport height, which leads to huge heights unless the TableModel has few rows; and horizontal scroll bars will never appear, even if the JTable is much too wide. It is better to only adjust the width to lower values by overriding getPreferredScrollableViewportSize() as follows:

public Dimension getPreferredScrollableViewportSize()
{
    Dimension size = super.getPreferredScrollableViewportSize();
    return new Dimension(Math.min(getPreferredSize().width, size.width), size.height);
}

or to call:

Dimension size = table.getPreferredScrollableViewportSize();
table.setPreferredScrollableViewportSize
    (new Dimension(Math.min(getPreferredSize().width, size.width), size.height);

The latter will not adjust dynamically.

How can I make a certain cell/row/column visible?

For cells, it can be done easily with: table.scrollRectToVisible(table.getCellRect(row, column, true));

See JTable scrolling article for row / column scrolling.

After changing the table model (adding rows etc.), these method calls must be wrapped in SwingUtilities.invokeLater() to give the JTable a chance to relayout, at least if there are any changes in the columns / column widths.

How can a test whether a cell/row/column is visible in JTable?

See JTable scrolling.

How can I display a row header with JTable?

See discussion of Row headers.

Can I display a fixed row/column on the bottom/right with JTable?

This is not possible with a single standard JScrollPane. It must be (non-easily) extended to support it. It may be possible to use two JScrollPanes, both with a JTable, and synchronize them, but that is difficult.

Columns

See introduction to TableColumnModel.

As soon as you do any modifications to the TableColumnModel, I would do everything explicitly and not rely on JTable's implicit changes that happen when its property autoCreateColumnsFromModel is true. Creating default columns is done like this:

TableModel data = ...

TableColumnModel columns = new DefaultTableColumnModel();

for (int count = data.getColumnCount(), i = 0; i < count; i++)
{
    TableColumn c = new TableColumn(i);

   
    c.setHeaderValue(data.getColumnName(i));
    
    // insert column-specific adjustments here
    // for example a column-wise renderer
    // c.setCellRenderer(...);

    columns.add(c);
}

JTable table = new JTable(data, columns);

How can you set widths for the columns?

You can set minimum/maximum widths and a preferred width on TableColumns. Explicitly setting the width property is useless, as it is almost always soon overridden again by JTable. Insert the calls at the place marked above.

With autoResizeMode == AUTO_RESIZE_OFF, the preferred width is taken directly as width, with other modes, the available space is distributed on the columns relative to their preferred widths.

Why doesn't setting the widths work?

The arguments to TableColumns' setWidth methods snap to currently allowed range. This means the proper order is:

setMinWidth

setMaxWidth

setPreferredWidth

How can I calculate reasonable preferred widths for all columns?

There is a sizeWidthsToFit() method in TableColumn, but it only uses the header value and doesn't work properly because it does not have all the needed information. See Cell sizes article for example code that sets the (preferred) widths based on the renderer's preferred widths.

Why are column widths/renderers etc. lost when I add/remove a column to the TableModel?

This also happens with DefaultTableModel.setDataVector(), even if the column structure remains the same..

TableModel doesn't support adding of columns directly. Instead it has to notify its listeners that it completely changed. Depending on the property "autoCreateColumnsFromModel" JTable throws aways the old TableColumns (which store all the information) and creates new ones. You can turn this behaviour off by calling table.setAutoCreateColumnsFromModel(false); but then you will have to modify the TableColumnModel by hand to actually add/remove columns.

How can I remove columns?

TableColumnModel.removeColumn()

How can I dynamically change the column names?

This isn't really supported by TableModel. The only way for the TableModel to message a row name change is to tell that the whole table structure changed, which either will destroy all column settings (autoCreateColumnsFromModel == false) or do nothing at all.

Instead modify the value directly in the TableColumnModel.

int viewColumn = table.convertColumnIndexToView(column);
TableColumn column = table.getColumnModel().getColumn(viewColumn);
column.setHeaderValue(newName);
table.getTableHeader().repaint();

How can I prevent reordering/resizing of columns?

These are handled by the JTable's JTableHeader. Use:

table.getTableHeader().setReorderingAllowed(false);

or

table.getTableHeader().setResizingAllowed(false);

How can I prevent the user from resizing of a single column?

TableColumn c = table.getColumnModel().getColumn(table.convertColumnIndexToView(column));
c.setResizable(false);

Watch out that the JTable doesn't implicitly create new TableColumns.

What problems are there when the columns are reordered?

You must keep in mind whether column indices are in the view (order of the TableColumnModel) or in the model. Only the latter are reordered by the user. Usually column arguments are those of the view, except (of course) those given to model methods. To convert between these types of column indices, use the convertColumnIndexToXXX() methods in JTable. Note that the renderer also get the 'view' column as argument.

Selection

How can I select a column when the user clicks on the column header?

See selection article.

How can I programmatically (de)select rows/columns?

The following methods actually modify the ListSelectionModel of the JTable, which contains the selected row indices:

table.addRowSelectionInterval

table.setRowSelectionInterval

table.removeRowSelectionInterval

To modify column selections there are equivalent methods which modify the ListSelectionModel of the TableColumnModel:

table.addColumnSelectionInterval

etc.

table.clearSelection affects both column and row selection.

Why does the column selection get confused when reordering columns?

This is a bug in DefaultTableColumnModel. The method moveColumn() does not properly call insertIndexInterval/removeIndexInterval on the column selection model. This is fixed in 1.4. Before, use a fixed subclass:

public class FixedDefaultTableColumnModel
    extends DefaultTableColumnModel
{
    public void moveColumn(int column, int to)
    {
        if (to < 0 || to >= getColumnCount())
            throw new IllegalArgumentException(String.valueOf(to));

        if (to == column)
        {
            fireColumnMoved(new TableColumnModelEvent(this, column, to));
            return;
        }

        TableColumn c = (TableColumn)tableColumns.remove(column);

        boolean selected = selectionModel.isSelectedIndex(column);
        boolean oldAdjusting = selectionModel.getValueIsAdjusting();
        selectionModel.setValueIsAdjusting(true);

        selectionModel.removeIndexInterval(column, column);

        tableColumns.add(to, c);

        selectionModel.insertIndexInterval(to, 1, true);
        
        if (selected)
            selectionModel.addSelectionInterval(to, to);
        else
            selectionModel.removeSelectionInterval(to, to);

        fireColumnMoved(new TableColumnModelEvent(this, column, to));

        selectionModel.setValueIsAdjusting(oldAdjusting);        
    }
}

How can I listen for row/column selection?

table.getSelectionModel().addListSelectionListener()

table.getColumnModel().addColumnModelListener()

Why do selection events get fired twice?

getValueIsAdjusting() is probably once true, one false. Just ignore events with it being true.

Appearance / Renderers

Each cell's appearance is governed by a TableCellRenderer. TableCellRenderers are assigned column-wise on TableColumn in the TableColumnModel, or (only used when there is none set on the table column) based on column class, by JTable.setDefaultRenderer(). See also Columns section above for configuring the former.

The default implementation, DefaultTableCellRenderer, has some strange logic with foreground/background. Calls to setBackground/setForeground will also change the default color for all subsequent calls to getTableCellRendererComponent (instead of using the JTable colors). This is probably meant to change easily the color of a column, but the colors set that way are lost once the LAF is changed, so it isn't really useful. To avoid confusion, subclasses that call the DefaultTableCellRenderer implementation of getTableCellRenderer() must either always or never (not sometimes, depending on a condition) set the color (back to null if the implementation should take the JTable's value). It is usually better to set the colors before the call to super.getTableCellRendererComponent (although that code will then read and set them again), because a) that will set proper colors on the currently focused, editable cell, and b) it will handle selection automatically (take the selection colors of JTable, the way of least confusion), c) changing the background colors doesn't require an additional call setOpaque(true). (which is not needed for 1.4 anyway).

Why doesn't the JTable appear "greyed-out" when it is set to disabled?

The default renderers don't react to the JTable's enabled state. Since the renderers don't react to user input anyway, it is only a matter of apperarance. It is not possible to adjust the default renderers (those for Numbers, Booleans, Date, the header renderer) in any way. Custom renderers should always adjust the renderer components like this:

class FixedTableCellRenderer
    extends DefaultTableCellRenderer
{
    public Component getTableCellRendererComponent
        (JTable table, Object value, boolean selected, boolean focused, int row, int column)
    {
        setEnabled(table == null || table.isEnabled());

        super.getTableCellRendererComponent(table, value, selected, focused, row, column);

        return this;
    }
}

How can I have colored cells?

In theory, DefaultTableCellRenderer allows customizing the default background/foreground, but that doesn't work properly with LAF changes (yet?), so you always need a subclass.

Here is an example subclass that paints every fifth row with a green background.

class ColoredTableCellRenderer
    extends DefaultTableCellRenderer
{
    public Component getTableCellRendererComponent
        (JTable table, Object value, boolean selected, boolean focused, int row, int column)
    {
        setEnabled(table == null || table.isEnabled()); // see question above

        if ((row % 5) == 0)
            setBackground(Color.green);
        else
            setBackground(null);

        super.getTableCellRendererComponent(table, value, selected, focused, row, column);

        return this;
    }
}

How can I make columns/cells centered/right-aligned etc.?

You need to call setHorizontalAlignment on the renderer component. If you use a custom renderer, simply put in the constructor. If you are using DefaultTableCellRenderer, create one of them, set the alignment and set it on the JTable (setDefaultRenderer) or on the TableColumns you want to have it it.

Why doesn't my TableCellRenderer display its background color?

You probably lack a call to setOpaque(true);There is a catch with DefaultTableCellRenderer in JDK1.3: It implicitly sets its opacity back to false when it believes it has the same background color as the JTable. If you change the background in your subclass, it is easiest to explicit call setOpaque(true); always afterwards.

1.4: This is fixed. With DefaultTableCellRenderer, you don't even need to call setOpaque() anymore, the background color will always be painted if it is different from the JTable's.

How can I have a different renderer for different rows?

The TableCellRenderer method is passed a row argument. Based on that (or other ones) you can either return different Components or b) delegate to different renderers.

How can I change the font of the table header?

The default renderer for header cells takes its font from the JTableHeader. Set the font on the header.

How can I write a custom header renderer?

The default header renderer can not be customized at all. The following is an general purpose implementation that is even better than the default renderer.

class HeaderRenderer
    extends DefaultTableCellRenderer
{
    public HeaderRenderer()
    {
        setHorizontalAlignment(SwingConstants.CENTER);
        setOpaque(true);

        // This call is needed because DefaultTableCellRenderer calls setBorder()
        // in its constructor, which is executed after updateUI()
        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
    }

    public void updateUI()
    {
        super.updateUI();
        setBorder(UIManager.getBorder("TableHeader.cellBorder"));
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
                       boolean selected, boolean focused, int row, int column)
    {
        JTableHeader h = table != null ? table.getTableHeader() : null;

        if (h != null)
        {
            setEnabled(h.isEnabled());         
            setComponentOrientation(h.getComponentOrientation());

            setForeground(h.getForeground());
            setBackground(h.getBackground());
            setFont(h.getFont());
        }
        else
        {
            /* Use sensible values instead of random leftover values from the last call */
            setEnabled(true);
            setComponentOrientation(ComponentOrientation.UNKNOWN);

            setForeground(UIManager.getColor("TableHeader.foreground"));
            setBackground(UIManager.getColor("TableHeader.background"));
            setFont(UIManager.getFont("TableHeader.font"));
        }

        setValue(value);
 
        return this;
    }
}

and install it.

Why is JTable so slow and why does it ask the renderer every time the mouse is moved?

By default, JTable's tooltips are enabled, and it (and its header) prepares the renderer component to ask it for it tooltip. This can be turned off by:

ToolTipManager.sharedInstance().unregisterComponent(table);
ToolTipManager.sharedInstance().unregisterComponent(table.getTableHeader());

How can I have tooltips with JTable?

By default, JTable asks the renderer for its tooltip, so just set the tooltip on the renderer.

For speed reasons, it might be preferable to override JTable.getToolTipText(MouseEvent) (and maybe also for the JTableHeader) and look up the value there directly.

How can I set the background color below (and to the right) of table cells if there are few rows (or colums)?

These areas don't belong to JTable by default. Instead its JViewport parent shows through there. You may either set a background color on the JViewport:

table.getParent().setBackground(...);
// table.getParent().setOpaque(true); if possibly turned off somewhere else

or use something like described in the Drag-and-Drop section to make the JTable higher/wider.

User interaction

Why doesn't my TableCellRenderer react to MouseEvents?

The components returned by TableCellRenderers are only used to paint the cell, not to react to user interaction. Add a MouseListener to the JTable instead. Note that single clicks already have a LAF- and TableCellEditor-specific meaning, so it is better only to react to double clicks. For editing of values, use a TableCellEditor.

How can I catch a double click on a JTable cell?

Add a MouseListener to the JTable. Renderers do not respond to user interaction. Editors would.

table.addMouseListener(new MouseAdapter()
{
    public void mouseClicked(MouseEvent e)
    {
        if (e.getComponent().isEnabled() && e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2)
        {
            Point p = e.getPoint();
            int row = table.rowAtPoint(p); 
            int column = table.columnAtPoint(p); 
            // Use table.convertRowIndexToModel / table.convertColumnIndexToModle to convert to view indices

            // ...
        }
    }
}

How can I disable KeyStrokes that the JTable handles?

JTable (or its TableUI) may handle KeyStrokes directly that should be handled on a higher level (accelerators, mnemonics).

One has to install a disabled action for the KeyStroke in question. There is no way to remove KeyStrokes directly that a parent input map handles.

disabled = new AbstractAction()
{
    public boolean isEnabled() { return false; } 
    public void actionPerformed(ActionEvent e) { }
};

// ....

// could use any unique-enough string

table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(...), "none");
table.getActionMap().put("none", disabled);

Strictly speaking, one should also install this in the WHEN_FOCUSED input map.

Note that if an editor component has focus, it will also possibly handle the key stroke.

Editing / TableCellEditors

How can I make table cells uneditable?

Your TableModel must return false from isCellEditable(row, column).

DefaultTableModel always returns true from this method. You need to write a subclass to change this, for example

DefaultTableModel data = new DefaultTableModel(....)
{
    public boolean isCellEditable(int row, int column)
    {
        return <insert condition>;
    }
};

TableModel

See discussion of TableModel.

How can I add/remove rows?

Use DefaultTableModel.

DefaultTableModel data = new DefaultTableModel(...);
JTable table = new JTable(data);

// somewhen later

data.addRow(...);

Drag-and-Drop

Why doesn't the JTable accept drops on the area below the rows (if it has few)?

That area isn't part of the JTable anymore, instead its parent component, the JViewport, shows, and it cannot know what to do with any data to be dropped. To make JTable, like JList, always fill the whole viewport, even its cells aren't enough, use:

JTable table = new JTable(data)
{
    public boolean getScrollableTracksViewportHeight()
    {
        Component parent = getParent();

        if (parent instanceof JViewport)
            return parent.getHeight() > getPreferredSize().height;

        return false;
    }
};

This has (vaguely suggested as concerning JViewport and not JTable) been reported as a bug. (#4310721)

The horizontal equivalent cannot be used for JTables without autoresizing whose columns may be not wide enough because then JTable will start resizing the columns again to fill all space; this also leads to weird effects during resizing. JTable cannot be easily changed to span the whole viewport while the columns have lesser widths.


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