Simple Tips for More Powerful Tables
- Assign ToolTips to Column Headers
- Change a Column's Cursor
- Color Cells
- Move Columns from the Keyboard
The previous article explored Swing's table component by walking you through Swing's JTable class and related classes. In addition to teaching you about JTable's inner structure, that article showed you how to accomplish some basic things with a JTable by calling various JTable methods. This article expands on that knowledge by introducing four simple tips that can help you build more powerful table components.
Assign ToolTips to Column Headers
Occasionally, you might find it helpful to assign ToolTips to your table component's column headers. ToolTips provide long descriptive names that, when assigned to column headers, offer additional feedback about the purpose of table component columns. That feedback benefits the table component's users by allowing them to more clearly understand a column's purpose and how to interact with that column's cells. Figure 1 illustrates a simple demonstration table component that displays a column-specific ToolTip when the mouse cursor's hotspotthat part of a mouse cursor icon used by Java to determine whether the mouse cursor is over a componententers that column's header.
Figure 1 The third column header's ToolTip appears when the mouse cursor's hotspot enters that column's header.
Assigning ToolTips to column headers requires you to subclass the JTableHeader class (located in the javax.swing.table package) and to override its getToolTipText() method. Code within that method identifies the column that a mouse cursor's hotspot is positioned over and returns an appropriate String object that contains that column's ToolTip text. For a detailed look at how those tasks are accomplished, examine Listing 1's HeaderToolTips source code.
Listing 1: HeaderToolTips.java
// HeaderToolTips.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; class HeaderToolTips extends JFrame { HeaderToolTips (String title) { // Pass the title to the JFrame superclass so that it appears in // the title bar. super (title); // Tell the program to exit when the user either selects Close // from the System menu or presses an appropriate X button on the // title bar. setDefaultCloseOperation (EXIT_ON_CLOSE); // Create a default table model consisting of headersText columns // and 10 rows. String [] headersText = { "Header #1", "Header #2", "Header #3", "Header #4", "Header #5", "Header #6" }; DefaultTableModel dtm = new DefaultTableModel (headersText, 10); // Create a table using the previously created default table // model. JTable jt = new JTable (dtm); // Obtain the table's column model and pass it to the constructor // of a new TTHeader (ToolTip Header) object. The column model // contains all information on the table's columns. TTHeader tth = new TTHeader (jt.getColumnModel ()); // Assign myToolTipsText to the TTHeader object. String [] myToolTipsText = { "", "Header #2 ToolTip Text", "Header #3 ToolTip Text", "", "Header #5 ToolTip Text", "" }; tth.setToolTipsText (myToolTipsText); // Assign default ToolTip text for those headers that do not have // their own ToolTip text (as indicated by "" in myToolTipsText), // to the TTHeader object. tth.setToolTipText ("Default ToolTip text"); // Assign the TTHeader to the JTable object as that table's // header. jt.setTableHeader (tth); // Place the table in a JScrollPane object (to allow the table to // be vertically scrolled and display scrollbars, as necessary). JScrollPane jsp = new JScrollPane (jt); // Add the JScrollPane object to the frame window's content pane. // That allows the table to be displayed within a displayed // scroll pane. getContentPane ().add (jsp); // Establish the overall size of the frame window to 400 // horizontal pixels by 225 vertical pixels. setSize (400, 225); // Display the frame window and all contained // components/containers. setVisible (true); } public static void main (String [] args) { // Create a HeaderToolTips object, which creates the GUI. new HeaderToolTips ("Header ToolTips"); } } class TTHeader extends JTableHeader { // The following String array holds all ToolTip text, with one entry // for each table column. If a column is to display default ToolTip // text, the corresponding entry is "". private String [] allToolTipsText; TTHeader (TableColumnModel tcm) { // Pass the TableColumnModel object to the superclass, which // takes care of that object. super (tcm); } // The following method is automatically called when the mouse // cursor hotspot moves over any one of the header rectangles in a // table header. public String getToolTipText (MouseEvent e) { // Return the pixel position of the mouse cursor hotspot. Point p = e.getPoint (); // Convert the pixel position to the zero-based column index of // the table header column over which the mouse cursor hotspot is // located. The result is a view-based column index. int viewColumnIndex = columnAtPoint (p); // Retrieve a reference to the JTable object associated with the // table header. JTable jt = getTable (); // Convert the view-based column index to a model-based column // index. int modelColumnIndex = jt.convertColumnIndexToModel (viewColumnIndex); // If model's ToolTip text is not present in allToolTipsText, // that means the default ToolTip text should be returned. // Otherwise, return the actual ToolTip text. if (allToolTipsText [modelColumnIndex].length () == 0) return super.getToolTipText (e); else return allToolTipsText [modelColumnIndex]; } void setToolTipsText (String [] myToolTipsText) { // Save the ToolTips text array for use by getToolTipText(). allToolTipsText = myToolTipsText; } }
HeaderToolTips associates a column header's name with appropriate ToolTip text by way of its headersText and myToolTipsText String arrays. Each entry at a given index in headersText maps to an entry at the same index in myToolTipsText. For example, Header #2 at index 1 in headersText maps to Header #2 ToolTip Text at index 1 in myToolTipsText.
The myToolTipsText array is accessed from the getToolTipsText() method via the allToolTipsText reference variable in the TTHeader class. Because a TTHeader object registers with a JTable object via a call to JTable's setTableHeader() method in HeaderToolTips's constructor (so that the TTHeader object can serve as the table component's header), Swing calls TTHeader's getToolTipText() methodwith a MouseEvent argument that identifies the current mouse positionwhen the mouse cursor's hotspot moves over a table component column header. In response, getToolTipText() returns a String identifying the column header's ToolTip text. Swing will render that text in a window (behind the scenes), and a ToolTip will appear.
The first task that getToolTipText() performs is to convert the pixel coordinates in its MouseEvent object argument (as referenced by e) to a column index by calling JTableHeader's columnAtPoint() method. The integer returned from columnAtPoint() represents the zero-based index of the column over which the mouse cursor's hotspot appears. Furthermore, the returned index is view-specific. What does that mean? It means that index 0 always refers to the leftmost column, index 1 always refers to the next-to-leftmost column, and so forth. That has ramifications for mapping ToolTip text to column headers. For example, if you associate ToolTip text with view-specific column index 1, that text associates with whatever column appears in the next-to-leftmost column position. Therefore, if you drag the column at view-specific column index 1 (by holding down the left mouse button while the mouse cursor's hotspot appears over that column's header and by moving the mouse) to a different column position, some other column would occupy the position at view-specific column index 1 and use the same ToolTip text as the column just dragged. Furthermore, the column just dragged would acquire its new position's ToolTip text.
CAUTION
Don't use view-specific column indexes to index into the ToolTip text array. If you do, the resulting text associates with a view-specific column, not the actual column. When you drag that column to another portion of the table component, the ToolTip text does not follow that column. Moving the mouse cursor's hotspot over the column header in its new position reveals that position's ToolTip, not the column's ToolTip.
JTable's convertColumnIndexToModel() method is called to convert the view-specific column index to a model-specific column index. The resulting index associates with a specific column based on the table component's model, not on a view of that table component. Once obtained, the model-specific column index is used to obtain the associated ToolTip text String from allToolTipsText. If the String's length is 0, a call is made to super.getToolTipText (e); to retrieve the default ToolTip text, which returns to the caller of the overridden getToolTipText() method. (What sets the default ToolTip text? The answer is tth.setToolTipText ("Default ToolTip text");, which simply calls JTableHeader's setToolTipText() method to assign default text. That text returns from super.getToolTipText(e);.) If the String's length is not 0, the appropriate entry in the allToolTipsText array returns. And that is how you assign ToolTips to column headers.
TIP
If you are unclear on why it is not good to associate ToolTip text with a view-specific index, comment out int modelColumnIndex = jt.convertColumnIndexToModel (viewColumnIndex); in Listing 1's getToolTipText() method and insert int modelColumnIndex = viewColumIndex;. Recompile and run the application. View the ToolTip text for each column and try dragging one of those columns with nondefault ToolTip text. It shouldn't take long for you to see why it is not a good idea to associate ToolTip text with a view-specific column index.