Home Button
Contents
Swing Logo

Swinging Duke
Feedback Button
Left ArrowRight Arrow

The JTable API

A Table Component for Swing

This is a specification for a user-interface component that presents data in a two-dimensional table format. This table component has the following key features:

The overall design of JTable is quite similar to the design of JTree or JListBox. Like those classes, JTable has a data model and a selection model. However, JTable also has a third model called the TableColumnModel. Here's a brief description of JTable's models:

The JTableHeader UI component mentioned in the preceding list is a companion object to the JTable component. The table header component handles user actions such as selecting, resizing, and reordering table columns. The reason this is a separate object from the JTable component is that in most applications, a JTable lives within a scroll pane. Separating the header from the body makes it much easier to ensure the correct scrolling behavior -- that is, that while the header is scrolled in synchronization with the body horizontally, it always stays at the top of the scroll pane as the body scrolls vertically.

The JTableHeader component has only one model: the TableColumnModel.


The TableDataModel API

This is the TableDataModel API:

public interface TableDataModel
{
    public int getRowCount();
    public Object getValueAt(Object columnIdentifier, int rowIndex);
}

public interface EditableTableDataModel extends TableDataModel {
    public void setValueAt(Object aValue, Object columnIdentifier, int rowIndex);

    public void addTableModelListener(TableModelListener x);
    public void removeTableModelListener(TableModelListener x);
}

public interface TableModelListener extends java.util.EventListener
{
    // This fine grain notification tells listeners the exact cell
    // that change.  Useful for notifying listeners an edited cell.
    void tableCellChanged(TableModelEvent e);

    // This tells the listeners 1 or more rows' values have changed
    void tableRowsChanged(TableModelEvent e);

    // This tells the listeners 1 or more rows have been inserted
    void tableRowsInserted(TableModelEvent e);

    // This tells the listeners 1 or more rows have been removed
    void tableRowsRemoved(TableModelEvent e);

    // This tells the listeners that most if not all values have
    // changed, including possibly the number of rows.
    void tableChanged(TableModelEvent e);
}

public class TableModelEvent extends java.util.EventObject
{
    TableModelEvent(TableDataModel source, Object columnIdentifier,
		    int[] indices);
    
    public Object getColumnIdentifier();
    public int[] getRowIndices();
}


The TableDataModel Interface

The TableDataModel provides an interface for a two-dimensional data source. This design makes the TableDataModel useful for driving UI components that display 2D data, such as JTable components. The overall design of the TableDataModel is very similar to JTree's model design, so we won't go into any more detail about it here. Please see the design specification of JTree or JListBox for more information.

Regarding the architecture of the TableDataModel, two design details are noteworthy:

Quite often, a table is used in a context in which the editing of cell values is either not allowed or not possible. Because the TableDataModel to provides an immutable data model, and because the EditableTableDataModel provides a mutable data model, it is simple and straightforward for developers to select and implement a table model. Using an Object as a column identifier frees the TableDataModel from having to keep track of column reorderings in a table. This strategy allows the TableDataModel to drive more than one table at a time, even though each table might have a different kind of column ordering.

TableDataModel is not concerned with what type of Object is used as the identifier; it simply uses the equals() method to compare identifiers.


TableColumnModel

This is the TableColumnModel API:

public interface TableColumnModel
{
  // Modifying the model
  void addColumn(JTableColumn c);
  void removeColumn(JTableColumn c);
  void moveColumn(int columnIndex, int newIndex);

  // Querying the model
  int getColumnCount();
  Enumeration getColumns();
  int getColumnIndex(Object columnIdentifier);
  JTableColumn getColumn(Object columnIdentifier);

  // Selection model
  void setSelectionModel(ListSelectionModel sm);
  ListSelectionModel getSelectionModel();

  // Listener
  void addColumnListener(TableColumnListener x);
  void removeColumnListener(TableColumnListener x);
}

public interface TableColumnListener extends java.util.EventListener
{
  void tableColumnAdded(TableColumnEvent e);
  void tableColumnRemoved(TableColumnEvent e);
  void tableColumnMoved(TableColumnEvent e);
  void ColumnSelectionChanged(ListSelectionEvent e);
}

public class TableColumnEvent extends java.util.EventObject
{
  TableColumnEvent(TableColumnModel source, int from, int to);
    
  // Used for removed and moved
  public Object getFromIndex();

  // Used for add and moved
  public Object getToIndex();
}

public class JTableColumn extends Object
{
  public JTableColumn(Object anIdentifier);

  public Object identifier();

  public void setHeaderCellValue(Object value);
  public Object getHeaderCelValue();
  public setHeaderCellRenderer(ListCellRenderer r);
  ListCellRenderer getHeaderCellRenderer();
  public setDataCellRenderer(ListCellRenderer r);
  ListCellRenderer getDataCellRenderer();

  public void setColumnMargin(int newMargin);
  public int getColumnMargin();
  public void setWidth(int newWidth);
  public int getWidth();
  public void setMinWidth(int newMinWidth);
  public int getMinWidth();
  public void setMaxWidth(int newMaxWidth);
  public int getMaxWidth();
  public void setResizable(boolean flag);
  public boolean getResizable();
}

The main purpose of the TableColumnModel is to allow JTable and JTableHeader objects to share information about the ordering of columns and the selection of columns. JTable uses the ordering and selection of JTableColumn objects to control the drawing and highlighting of table cells. JTableHeader allows the user to manipulate the column size and column order interactively.

A JTableColumn represents all the attributes of a column in an JTable, including width, resizibility, and minimum and maximum width. Also, JTableColumn helps to determine how the table interprets and displays value objects from the TableDataModel source in the column. This operation is performed using the column's dataCellRenderer.


The JTable API

This is the JTable API:

public class JTable extends JComponent implements
	TableModelListener, TableColumnListener, ListSelectionListener
{
  // Constructors
  public JTable();
  public JTable(TableDataModel dm);
  public JTable(TableDataModel dm, TableColumnModel cm);
  public JTable(TableDataModel dm, TableColumnModel cm, ListSelectionModel sm);

  // Local attributes
  public void setRowHeight(int newHeight);
  public int getRowHeight();
  public void setRowMargin(int newMargin);
  public int getRowMargin();
  public void setGridColor(Color newColor);
  public Color getGridColor();
  public void setShowGrid(boolean b);
  public boolean getShowGrid();

  // Managing TableUI
  public TableUI getUI();
  public void setUI(TableUI ui);
  public void updateUI();

  // Managing models
  public TableDataModel getModel();
  public void setModel(TableDataModel newModel);
  public TableColumnModel getColumnModel();
  public void setColumnModel(TableColumnModel cm);
  public ListSelectionModel getSelectionModel();
  public void setSelectionModel(ListSelectionModel sm);

  // Implementing TableModelListener interface
  void tableCellChanged(TableModelEvent e);
  void tableRowsChanged(TableModelEvent e);
  void tableRowsInserted(TableModelEvent e);
  void tableRowsRemoved(TableModelEvent e);
  void tableChanged(TableModelEvent e);

  // Implementing TableColumnListener interface
  void tableColumnAdded(TableColumnEvent e);
  void tableColumnRemoved(TableColumnEvent e);
  void tableColumnMoved(TableColumnEvent e);
  void ColumnSelectionChanged(ListSelectionEvent e);

  // Implementing ListSelectionListener interface
  void selectionChanged(ListSelectionEvent e);
}

JTable draws its data cell using the dataCellRenderer (See JListBox for a discussion of renderers) from each column, and optionally draws a grid.


Open Questions

At the current state of Swing development, two questions remain unresolved. The first question can be summed up as follows:

Up to now, in every component that has a separate model object, the component's API has been designed to provide cover methods for the model, making it unnecessary for most casual users of the component to know anything about the model. In general, that is probably a good thing. However, the JTable object has three models, and one of those models has another model inside it. (TableColumnModel contains a ListSelectionModel). In most situations, the user has to do perform some minimal setup operations to hook the JTableHeader to the same TableColumnModel to which the JTable is attached. So the question is this: Is it still worthwhile to cover all those models, given the fact that the user will already know something about the models?

Another point to consider is this: An API describing a JTable that has as many cover methods as the kind of component described in the preceding paragraph would probably be more difficult to to understand than the current clean, small API and the various kinds of models it describes.

Developer comments on both these topics are appreciated..

There is also a related question: If cover methods are provided by JTable, should the same TableColumnModel cover methods in JTableHeader?

Developers are invited to comment on this topic also.


Work Still to be Done

At this stage of development, the following tasks remain:


NOTE: This first implementation of JTable assumes a fixed-height row. If there is a demand for a variable row height, one can be added in a future release.


The JTableHeader API

This it the JTableHeader API:

public class JTableHeader extends JComponent implements TableColumnListener
{ 
  public JTableHeader(); 

  // Managing TableHeaderUI
  public TableHeaderUI getUI();
  public void setUI(TableHeaderUI ui);
  public void updateUI();

  // Managing models
  public TableColumnModel getColumnModel();
  public void setColumnModel(TableColumnModel cm);

  // Local behavior attributes
  void setReorderingAllowed(boolean b);
  boolean getReorderingAllowed();
  void setResizingAllowed(boolean b);
  boolean getResizingAllowed();

  // Implementing TableColumnListener interface
  void tableColumnAdded(TableColumnEvent e);
  void tableColumnRemoved(TableColumnEvent e);
  void tableColumnMoved(TableColumnEvent e);
  void ColumnSelectionChanged(ListSelectionEvent e);
}

JTable draws its header cell using the headerCellRenderer specified in TableColumnModel. It allows the user to resize a column and reorder columns.

Pending the availability of custom cursors, the cursor shape will change to a resize cursor whenever the mouse is over the resize region of a header cell.

On the Drawing Board (Time Permitting)

If time allows, the following features will be added in a later release:

Arrows


Version 0.4. Last modified 09/04/97.
Copyright © 1995-97 Sun Microsystems, Inc. All Rights Reserved.

Sun's Home Page