16.1 A Table with Row Headers


As we promised, this is a table with headers for both rows and columns. The JTable handles the column headers itself; we need to add machinery for the rows. Figure 16-1 shows the resulting table. It shows column labels, plus two data columns from a larger table. Scrolling works the way you would expect. When you scroll vertically, the row headers scroll with the data. You can scroll horizontally to see other data columns, but the row headers remain on the screen.

Figure 16-1. A table with both row and column headers
figs/swng2.1601.gif

The trick is that we really have two closely coordinated tables: one for the row headers (a table with only one column) and one for the data columns. There is a single TableModel, but separate TableColumnModels for the two parts of the larger table. In the figure, the gray column on the left is the row header; it's really column 0 of the data model.

To understand what's going on, it helps to remember how a Swing table models data. The TableModel itself keeps track of all the data for the table, i.e., the values that fill in the cells. There's no reason why we can't have two tables that share the same table model that's one of the advantages of the model-view-controller architecture. Likewise, there's no reason why we can't have data in the table model that isn't displayed; the table model can keep track of a logical table that is much larger than the table we actually put on the screen. This is particularly important in the last example, but it's also important here. The table that implements the row headers uses the first column of the data model and ignores everything else; the table that displays the data ignores the first column.

The TableColumnModel keeps track of the columns and is called whenever we add, delete, or move a column. One way to implement tables that use or ignore parts of our data is to build table column models that do what we want, which is add only a particular column (or group of columns) to the table. That's the approach we've chosen. Once we have our models, it is relatively simple to create two JTables that use the same TableModel, but different TableColumnModels. Each table displays only the columns that its column model allows. When we put the tables next to each other, one serves as the row header, and the other displays the body. Depending on the data you put in your tables, you could probably automate many of these steps. Here is the code for our not-so-simple table:

// RowHeaderTable.java // import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; public class RowHeaderTable extends JFrame {   public RowHeaderTable( ) {     super("Row Header Test");     setSize(300, 200);     setDefaultCloseOperation(EXIT_ON_CLOSE);          TableModel tm = new AbstractTableModel( ) {       String data[] = {"", "a", "b", "c", "d", "e"};       String headers[] = {"Row #", "Column 1", "Column 2", "Column 3",                            "Column 4", "Column 5"};       public int getColumnCount( ) { return data.length; }       public int getRowCount( ) { return 1000; }       public String getColumnName(int col) { return headers[col]; }              // Synthesize some entries using the data values and the row number.       public Object getValueAt(int row, int col) {          return data[col] + row;        }     };     // Create a column model for the main table. This model ignores the first     // column added and sets a minimum width of 150 pixels for all others.     TableColumnModel cm = new DefaultTableColumnModel( ) {       boolean first = true;       public void addColumn(TableColumn tc) {         // Drop the first column, which will be the row header.         if (first) { first = false; return; }         tc.setMinWidth(150);  // Just for looks, really...         super.addColumn(tc);       }     };     // Create a column model that will serve as our row header table. This model     // picks a maximum width and stores only the first column.     TableColumnModel rowHeaderModel = new DefaultTableColumnModel( ) {       boolean first = true;       public void addColumn(TableColumn tc) {         if (first) {           tc.setMaxWidth(tc.getPreferredWidth( ));           super.addColumn(tc);           first = false;         }         // Drop the rest of the columns; this is the header column only.       }     };     JTable jt = new JTable(tm, cm);     // Set up the header column and hook it up to everything.     JTable headerColumn = new JTable(tm, rowHeaderModel);     jt.createDefaultColumnsFromModel( );     headerColumn.createDefaultColumnsFromModel( );     // Make sure that selections between the main table and the header stay in sync     // (by sharing the same model).     jt.setSelectionModel(headerColumn.getSelectionModel( ));     // Make the header column look pretty.     //    headerColumn.setBorder(BorderFactory.createEtchedBorder( ));     headerColumn.setBackground(Color.lightGray);     headerColumn.setColumnSelectionAllowed(false);     headerColumn.setCellSelectionEnabled(false);     // Put it in a viewport that we can control.     JViewport jv = new JViewport( );     jv.setView(headerColumn);     jv.setPreferredSize(headerColumn.getMaximumSize( ));     // Without shutting off autoResizeMode, our tables won't scroll correctly     // (horizontally, anyway).     jt.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);     // We have to manually attach the row headers, but after that, the scroll     // pane keeps them in sync.     JScrollPane jsp = new JScrollPane(jt);     jsp.setRowHeader(jv);     jsp.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,                    headerColumn.getTableHeader( ));     getContentPane( ).add(jsp, BorderLayout.CENTER);   }   public static void main(String args[]) {     RowHeaderTable rht = new RowHeaderTable( );     rht.setVisible(true);   } }

The various models we use our subclasses of AbstractTableModel and DefaultTableColumnModel are anonymous inner classes. The new table model doesn't do anything really interesting; it just keeps track of the raw data. There's an array of column headers; the data itself is computed in the getValueAt( ) method. Of course, in a real example, you'd have some way of looking up real data.

Our TableColumnModels are where the magic happens. The addColumn( ) method, which they override, is called whenever a column is added to a table. The first of our two models, cm, keeps track of the body of the table. The first time it is called, it returns without doing anything effectively ignoring the first column in the table, which is the column of row headers. (It does set the local variable first to false, indicating that it already processed the headers.) For subsequent columns, addColumn( ) behaves the way you would expect: it sets a minimum column width, then calls the method in the superclass to insert the column in the table.

The other table column model, rowHeaderModel, overrides addColumn( ) to do the opposite: it inserts the first column (the row header) into the table and ignores all the other columns. Notice that we manually set the maximum width of the header column. Without this action, the header column would try to be as wide as the main table leaving no room for the main table to display itself. This value could be passed in to the constructor of a regular inner class, or you could calculate an appropriate width. These classes make the assumption that column 0 (the row headers) is added to the table first and never added again. As long as the table isn't editable, that assumption is valid. If the table were editable, we would have to add some logic to make sure we always know which column we're working on.

The rest of the code is almost self-explanatory. We create two JTable objects: jt (for the body) and headerColumn (for the row headers). We start by telling our tables to build their columns because we specified our own column models. Our tables use the same TableModel and the appropriate TableColumnModel. To make sure that row selection for the two tables is always in sync, we give them both the same SelectionModel. We give headerColumn a different color and disable column and cell selection. The only thing left is to arrange the display. We create a separate JViewport to display the row headers and put the header column in it. Then we disable autoresize mode for the main table; this is necessary to make the scrollbars work properly. (Things get confusing if a table inside a scrollpane tries to resize itself.) Finally, we create a JScrollPane from jt. To get the row headers into the scrollpane, we add the viewport (which already contains headerColumn) to the JScrollPane by calling setRowHeader( ). Then we just slap the JScrollPane, which now contains both tables, into our JFrame's content pane, and we're done.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net