Some final touches

The final touch is to add a few methods to handle some common tasks associated with a JTable.

  1 
  2 package com.vizitsolutions.identitytable;
  3 
  4 import javax.swing.ActionMap;
  5 import javax.swing.table.DefaultTableColumnModel;
  6 import javax.swing.InputMap;
  7 import javax.swing.JComponent;
  8 import javax.swing.JScrollPane;
  9 import javax.swing.JTable;
 10 import javax.swing.JViewport;
 11 import java.awt.event.KeyEvent;
 12 import javax.swing.KeyStroke;
 13 import javax.swing.ListSelectionModel;
 14 import javax.swing.table.TableCellRenderer;
 15 import javax.swing.table.TableColumn;
 16 import javax.swing.table.TableColumnModel;
 17 
 18 /**
 19  * <p>$Id$</p>
 20  *
 21  * <p>
 22  * This class maps a TableModel into two JTables, and places
 23  * them into a JScrollPane. The first JTable contains n fixed
 24  * columns, and represents what is commonly called a rowheader.
 25  * This is placed in the rowheader area of the JScrollPane. These
 26  * columns will not scroll horizontally. This is particularly
 27  * useful when you have one or more columns that identify an object
 28  * and several aditional columns that contain attributes of the
 29  * object. Placing the identifying columns in these fixed columns
 30  * ensures that they are always visible while the attributes of the
 31  * object are scrolled into view.
 32  * </p>
 33  * <p>
 34  * The second JTable contains the remaining columns and is placed in
 35  * the main viewport and will scroll normally.
 36  * </p>
 37  *
 38  * @author Alex Kluge
 39  * @version $Revision$, $Date$
 40  */
 41 public class IdentityTable5 extends JScrollPane
 42 {
 43     // These names follow a similar pattern to those used in JTable UI delegate.
 44     public static final String LEFT_ARROW_ACTION_NAME  = "selectPreviousColumn";
 45     public static final String RIGHT_ARROW_ACTION_NAME = "selectNextColumn";
 46     public static final String SHIFT_TAB_ACTION_NAME   = "selectPreviousColumnCell";
 47     public static final String TAB_ACTION_NAME         = "selectNextColumnCell";
 48     
 49     private JTable           mainTable;
 50     private TableColumnModel mainColumnModel      = new DefaultTableColumnModel();
 51     private JTable           rowHeaderTable;
 52     private TableColumnModel rowHeaderColumnModel = new DefaultTableColumnModel();
 53     
 54     /**
 55      * Creates a new instance of IdentityTable
 56      */
 57     public IdentityTable5(IdentityTableModel baseTableModel)
 58     {
 59         // Start out life as an empty scrollpane.
 60         super();
 61         
 62         // Loop over each of the fixed columns, and create columns in the row
 63         // header column model for them.
 64         TableColumn column;
 65         int         columnCount         = baseTableModel.getColumnCount();
 66         int         currentColumn;
 67         int         identityColumnCount = baseTableModel.getIdentityColumnCount();
 68         
 69         for (currentColumn = 0; currentColumn<identityColumnCount; currentColumn++)
 70         {
 71             column = new TableColumn(currentColumn);
 72             column.setHeaderValue(baseTableModel.getColumnName(currentColumn));
 73             
 74             rowHeaderColumnModel.addColumn(column);
 75         }
 76         
 77         // Now loop over the remaining columns, and add them to the main table's column model.
 78         for (currentColumn = identityColumnCount; currentColumn<columnCount; currentColumn++)
 79         {
 80             column = new TableColumn(currentColumn);
 81             column.setHeaderValue(baseTableModel.getColumnName(currentColumn));
 82             
 83             mainColumnModel.addColumn(column);
 84         }
 85         
 86         // Providing a column model here atomatically sets the
 87         // autoCreateColumnsFromModel flag to false.
 88         rowHeaderTable = new JTable(baseTableModel, rowHeaderColumnModel);
 89         rowHeaderTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
 90 
 91         mainTable      = new JTable(baseTableModel, mainColumnModel);
 92         mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
 93         
 94         // Ensure that both tables have common selections.
 95         // Save the row selection model for later.
 96         ListSelectionModel rowSelectionModel = mainTable.getSelectionModel();
 97         rowHeaderTable.setSelectionModel(rowSelectionModel);
 98         rowHeaderTable.setPreferredScrollableViewportSize(rowHeaderTable.getPreferredSize());
 99         
100         // This puts the column headers in the upper left corner above the row
101         // header columns. This is the same thing that happens when a JTable configures
102         // the containing JScrollPane.
103         setCorner(JScrollPane.UPPER_LEFT_CORNER, rowHeaderTable.getTableHeader());
104         
105         JViewport rowHeaderViewPort = new JViewport();
106         rowHeaderViewPort.setView(rowHeaderTable);
107         setRowHeader(rowHeaderViewPort);
108 
109         // Listen for resize events on the row header, and rexize the main table as appropriate.
110         RowHeaderResizeListener rowHeaderResizeListener = new RowHeaderResizeListener(this,           rowHeaderViewPort,
111                                                                                       rowHeaderTable, mainTable);
112         rowHeaderTable.addComponentListener(rowHeaderResizeListener);
113 
114         setViewportView(mainTable);
115         
116        installKeybordActions(rowHeaderTable,                      mainTable,
117                               rowSelectionModel,                   rowHeaderColumnModel.getSelectionModel(),
118                               mainColumnModel.getSelectionModel());
119     }
120     
121     protected void installKeybordActions(JTable             rowHeaderTable,           final JTable       mainTable,
122                                          ListSelectionModel rowSelectionModel,        ListSelectionModel headerColumnSelectionModel,
123                                          ListSelectionModel mainColumnSelectionModel)
124     {
125         InputMap rowHeaderInputMap
126                     = rowHeaderTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
127         ActionMap rowHeaderActionmap = rowHeaderTable.getActionMap();
128         ActionMap mainActionMap      = mainTable.getActionMap();
129 
130 
131          // Tab action handler, shift forward one cell, with wrapping
132         TabKeyActionHandler tabKeyActionHandler
133                     = new TabKeyActionHandler(rowHeaderTable,           mainTable,
134                                               rowSelectionModel,        headerColumnSelectionModel,
135                                               mainColumnSelectionModel, 1,
136                                               true);
137         // Put the action handler into the appropriate place for both the
138         // row header and the main table.
139         rowHeaderActionmap.put(TAB_ACTION_NAME, tabKeyActionHandler);
140         mainActionMap.put(TAB_ACTION_NAME,      tabKeyActionHandler);
141 
142         // Right arrow action handler, shift forward one cell, no wrapping.
143         tabKeyActionHandler
144                     = new TabKeyActionHandler(rowHeaderTable,           mainTable,
145                                               rowSelectionModel,        headerColumnSelectionModel,
146                                               mainColumnSelectionModel, 1,
147                                               false);
148 
149         rowHeaderActionmap.put(RIGHT_ARROW_ACTION_NAME, tabKeyActionHandler);
150         mainActionMap.put(RIGHT_ARROW_ACTION_NAME,      tabKeyActionHandler);
151 
152         // Shift-Tab action handler, shift backward one cell, with wrapping
153         tabKeyActionHandler
154                     = new TabKeyActionHandler(rowHeaderTable,           mainTable,
155                                               rowSelectionModel,        headerColumnSelectionModel,
156                                               mainColumnSelectionModel, -1,
157                                               true);
158 
159         rowHeaderActionmap.put(SHIFT_TAB_ACTION_NAME, tabKeyActionHandler);
160         mainActionMap.put(SHIFT_TAB_ACTION_NAME,      tabKeyActionHandler);
161 
162         // Left arrow action handler, shift backward one cell, no wrapping.
163         tabKeyActionHandler
164                     = new TabKeyActionHandler(rowHeaderTable,           mainTable,
165                                               rowSelectionModel,        headerColumnSelectionModel,
166                                               mainColumnSelectionModel, -1,
167                                               false);
168 
169         rowHeaderActionmap.put(LEFT_ARROW_ACTION_NAME, tabKeyActionHandler);
170         mainActionMap.put(LEFT_ARROW_ACTION_NAME,      tabKeyActionHandler);
171         
172         // map the row header page down to the main table's page down.
173         Object actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0));
174         rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey)));
175         
176         // and map the page up too
177         actionMapKey = rowHeaderInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0));
178         rowHeaderActionmap.put(actionMapKey, new RowHeaderActionProxy(mainTable, mainActionMap.get(actionMapKey)));
179     }
180     
181     /**
182      * Return the index of the selected row. Both tables have the same selection
183      * model, so the index is consistent.
184      * @return An int giving the index of the selected row in the identity table.
185      */
186     public int getSelectedRow()
187     {
188         return mainTable.getSelectedRow();
189     }
190     
191     /**
192      * Return the row header table.
193      * @return A Jtable containing the identity columns from the original table
194      *         model.
195      */
196     public JTable getRowHeaderTable()
197     {
198         return rowHeaderTable;
199     }
200     
201     /**
202      * Fetch the table containing the horizontally scrolling columns.
203      * @return A JTable containing the columns from the initial table model
204      *         other than the identity columns.
205      */
206     public JTable getMainTable()
207     {
208         return mainTable;
209     }
210     
211     /**
212      * Sets a default cell renderer for both the row header and the main tables. Be careful
213      * as it is also possible to obtain the tables individually and set different default
214      * renderers on each.
215      *
216      * @param columnClass A class, which is matched against the columntype. Columns whose types match
217      *                    the class will use the given renderer.
218      * @param renderer    A TableCellRenderer used to render cells whose column type matches the given class.
219      */
220     public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer)
221     {
222         mainTable.setDefaultRenderer(columnClass, renderer);
223         rowHeaderTable.setDefaultRenderer(columnClass, renderer);
224     }
225     
226     /**
227      * Return the default cell renderer for the given class from the main table. Be careful
228      * as it is also possible to obtain the tables individually and set different default
229      * renderers on each.
230      */
231     public TableCellRenderer getDefaultRenderer(Class columnClass)
232     {
233         return mainTable.getDefaultRenderer(columnClass);
234     }
235     
236     /**
237      * 
238      * @return A Table model containing the column model for the main table.
239      */
240     public TableColumnModel getMainColumnModel()
241     {
242         return mainColumnModel;
243     }
244 
245     /**
246      * 
247      * @return A TableColumnModel containing the column model for the row header.
248      */
249     public TableColumnModel getRowHeaderColumnModel()
250     {
251         return rowHeaderColumnModel;
252     }
253 }
254 
255 

Notes