15.3 Table Data


We've seen the TableColumnModel, which stores a lot of information about the structure of a table but doesn't contain the actual data. The data that's displayed in a JTable is stored in a TableModel. The TableModel interface describes the minimum requirements for a model that supplies the information necessary to display and edit a table's cells and to show column headers. The AbstractTableModel fills out most of the TableModel interface, but leaves the methods for retrieving the actual data undefined. The DefaultTableModel extends AbstractTableModel and provides an implementation for storing data as a vector of vectors. We'll look at both the abstract and default table models in more detail later in this chapter.

15.3.1 The TableModel Interface

All of the table models start with this interface. A table model must be able to give out information on the number of rows and columns in the table and have access to the values of the cells of the table. The TableModel interface also has methods that can be used to encode information about the columns of the table (such as a localized name or class type) separate from the column model.

15.3.1.1 Properties

The TableModel interface supports the properties shown in Table 15-9. The columnCount is the number of columns in the data model. This does not have to match the number of columns reported by the column model. Likewise, rowCount is the number of rows in the data model. columnName and columnClass are indexed properties that let you retrieve the name of the column and the class of objects in the column. The name used in the table model is distinct from anything used in the TableColumn class. For both properties, remember that the index refers to the table model, regardless of where the column appears on the screen.

Table 15-9. TableModel properties

Property

Data type

get

is

set

Default value

columnCount

int

·

     

rowCount

int

·

     

15.3.1.2 Events

As you may have come to expect from other models in the Swing package, the TableModel has its own event type, TableModelEvent, generated whenever the table changes. A full discussion of the TableModelEvent class and the TableModelListener appears later in this chapter.

public void addTableModelListener(TableModelListener l)
public void removeTableModelListener(TableModelListener l)

Add or remove listeners interested in receiving table model events.

15.3.1.3 Cell methods

These methods let you obtain and change the values of individual cells:

public Object getValueAt(int rowIndex, int columnIndex)

Return the value of the cell at (rowIndex, columnIndex). Base types (int, float, etc.) are wrapped in an appropriate Object.

public boolean isCellEditable(int rowIndex, int columnIndex)

Return true if the cell at (rowIndex, columnIndex) can be edited.

public void setValueAt(Object aValue, int rowIndex, int columnIndex)

Set the value of the cell at (rowIndex, columnIndex) to aValue. As with the getValueAt( ) method, you may need to wrap primitive data types in an Object (like Integer) before using them to set the value of a cell.

15.3.2 The AbstractTableModel Class

This class implements many of the methods of the TableModel interface, leaving the really important ones to you. If you want to build your own table model, this is the place to start. (In fact, the documentation shipped with the Swing package even recommends starting here, rather than with the DefaultTableModel presented in the next section.) The three unimplemented methods from TableModel are:

public abstract int getColumnCount( )
public abstract int getRowCount( )
public abstract Object getValueAt(int row, int col)

With these methods, you can build your own table model better suited to the kinds of data you want to display. You can extend this model to support databases and even dynamic data.

Other properties available through the AbstractTableModel are shown in Table 15-10.

Table 15-10. AbstractTableModel properties

Property

Data type

get

is

set

Default value

cellEditableii, o

boolean

 

·

 

false

columnClassi

Class

·

   

Object.class

columnCounto

int

·

   

Abstract

columnNamei

String

·

   

"A" for column 1, "B" for column 2, etc. "" for an invalid index.

rowCounto

int

·

   

Abstract

valueAtii, o

Object

·

 

·

Getter is abstract. Setter is empty implementation (creating a read-only table by default).

iindexed, iidouble indexed (by row and column), ooverridden

As a starting point, let's look at recreating the file list table with our own data model. For fun, we'll throw in support for the column headers and column types as well. (This is not one of the requirements of a minimal table model, but it makes the table look more professional.) Here's the source code for the model:

// FileModel.java // A custom table model to display information on a directory of files // import javax.swing.table.*; import java.util.Date; import java.io.File; public class FileModel extends AbstractTableModel {   String titles[] = new String[] {     "Directory?", "File Name", "Read?", "Write?", "Size", "Last Modified"   };   Class types[] = new Class[] {      Boolean.class, String.class, Boolean.class, Boolean.class,      Number.class, Date.class   };          Object data[][];   public FileModel( ) { this("."); }   public FileModel(String dir) {     File pwd = new File(dir);     setFileStats(pwd);   }   // Implement the methods of the TableModel interface we're interested   // in. Only getRowCount( ), getColumnCount( ), and getValueAt( ) are   // required. The other methods tailor the look of the table.   public int getRowCount( ) { return data.length; }   public int getColumnCount( ) { return titles.length; }   public String getColumnName(int c) { return titles[c]; }   public Class getColumnClass(int c) { return types[c]; }   public Object getValueAt(int r, int c) { return data[r][c]; }   // Our own method for setting/changing the current directory   // being displayed. This method fills the data set with file info   // from the given directory. It also fires an update event, so this   // method could also be called after the table is on display.   public void setFileStats(File dir) {     String files[] = dir.list( );     data = new Object[files.length][titles.length];     for (int i=0; i < files.length; i++) {       File tmp = new File(files[i]);       data[i][0] = new Boolean(tmp.isDirectory( ));       data[i][1] = tmp.getName( );       data[i][2] = new Boolean(tmp.canRead( ));       data[i][3] = new Boolean(tmp.canWrite( ));       data[i][4] = new Long(tmp.length( ));       data[i][5] = new Date(tmp.lastModified( ));     }     // Just in case anyone's listening     fireTableDataChanged( );   } }

And here's the source for the simple application that creates and displays the JTable. Notice how simple it is to make the JTable with a custom model:

// FileTable.java // A test frame for the custom table model import java.awt.*; import javax.swing.*; import java.util.Date; import java.io.File; public class FileTable extends JFrame {   public FileTable( ) {     super("Custom TableModel Test");     setSize(300, 200);     setDefaultCloseOperation(EXIT_ON_CLOSE);     FileModel fm = new FileModel( );     JTable jt = new JTable(fm);     jt.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);     jt.setColumnSelectionAllowed(true);     JScrollPane jsp = new JScrollPane(jt);     getContentPane( ).add(jsp, BorderLayout.CENTER);   }   public static void main(String args[]) {     FileTable ft = new FileTable( );     ft.setVisible(true);   } }

Instead of supplying the data and headers in the JTable constructor, we built them into our own table model, and then created the JTable with our own model. That also gave us control over the column types. Figure 15-5 shows the results of our custom model.

Figure 15-5. A JTable built with a custom TableModel class
figs/swng2.1505.gif
15.3.2.1 Events
public void addTableModelListener(TableModelListener l)
public void removeTableModelListener(TableModelListener l)

Add or remove listeners for table model events coming from this model. The listeners are used in the various event-firing methods, which are presented next.

public void fireTableDataChanged( )
public void fireTableStructureChanged( )
public void fireTableRowsInserted(int first, int last)
public void fireTableRowsUpdated(int first, int last)
public void fireTableRowsDeleted(int first, int last)
public void fireTableCellUpdated(int row, int col)

These methods call fireTableChanged( ) after constructing an appropriate TableModelEvent object. fireTableDataChanged( ) indicates that all of the cells in the table might have changed, but the columns of the table are still intact. On the other hand, the fireTableStructureChanged( ) method indicates that the actual columns present in the model may have changed (in name, type, or even number) as well.

public void fireTableChanged(TableModelEvent e)

Reports event e to any registered listeners. Programmers usually start with one of the preceding fireTable...( ) methods.

15.3.2.2 Another useful method

The TableModel interface has one other interface, shown here with its implementation:

public int findColumn(String columnName)

An easy mechanism for looking up columns by their associated names. This is a linear search, so large tables may need to override this.

15.3.3 The DefaultTableModel Class

While you will most likely create your own table models by extending the AbstractTableModel class, as we did earlier in this chapter, the Swing package includes a DefaultTableModel class that contains a Vector of Vector objects to house the data. The class itself extends AbstractTableModel and provides a few methods for manipulating the data. The default model presumes that every cell is editable.

15.3.3.1 Properties

The DefaultTableModel class provides default values to the properties inherited from the TableModel interface. These values are shown in Table 15-11.

Table 15-11. DefaultTableModel properties

Property

Data type

get

is

set

Default value

cellEditableii, o

boolean

 

·

 

true

cellSelectedii

boolean

 

·

 

false

columnClassi, o

Class

·

   

Object.class

columnCounto

int

·

   

0

columnNamei, o

String

·

   

"A" for column 1, "B" for column 2, etc. "" for an invalid index.

columnSelectedi

boolean

 

·

 

false

rowCounto

int

·

   

0

rowSelectedi

boolean

 

·

 

false

valueAtii, o

Object

·

 

·

Data-dependent

iindexed, iidouble indexed (by row and column), ooverridden

15.3.3.2 Events

The DefaultTableModel class does not support any new event types, but since it does contain real data, it provides the following helper methods to generate events:

public void newDataAvailable(TableModelEvent e)
public void newRowsAdded(TableModelEvent e)
public void rowsRemoved(TableModelEvent e)

Fire the appropriate table model events. If e is null, the model assumes that all the associated data has changed and creates an appropriate event.

15.3.3.3 Constructors
public DefaultTableModel( )

Build a DefaultTableModel with zero rows and zero columns.

public DefaultTableModel(int numRows, int numColumns)

Build a DefaultTableModel with the specified number of rows and columns.

public DefaultTableModel(Vector columnNames, int numRows)
public DefaultTableModel(Object[] columnNames, int numRows)

Build a DefaultTableModel with the specified number of rows. The number of columns matches the number of elements in the columnNames vector (or array), which also supplies the names for the columns in the column header.

public DefaultTableModel(Vector data, Vector columnNames)
public DefaultTableModel(Object[] [] data, Object[] columnNames)

Build a DefaultTableModel with the number of rows determined by the data object and the number of columns determined by the columnNames object. The data vector columns are padded or truncated to match the number of columns dictated by the columnNames vector.

The Object arrays are converted to vectors (or vectors of vectors in the case of Object[][]).

15.3.3.4 Other useful methods

With the DefaultTableModel class, you can get and set the data directly. These methods work in addition to the usual getValueAt( ) and setValueAt( ) methods for individual cells.

public Vector getDataVector( )

Return the row vector, which itself contains vectors representing the collection of cells (one for each column) for each row.

public void setDataVector(Object[][] newData, Object[] columnIDs)
public void setDataVector(Vector newData, Vector columnIDs)

Set the new data (as a vector of vectors) for the model. The columnIDs field can be null, or it can contain the names of columns that are returned by getColumnName( ). (Although you can create column IDs that are not of type String, the getColumnName( ) method converts them to strings using the toString( ) method.) The first column ID is mapped to the first column in newData, the second column ID to the second column, and so on.

public void addColumn(Object columnID)
public void addColumn(Object columnID, Object[] columnData)
public void addColumn(Object columnID, Vector columnData)

Add a new column to the model. The first version inserts null values into the rows currently in the table. Both the second and third versions insert values into the current rows up to their size or the number of rows, and null values after that if more rows exist. (If, for example, you had 20 rows but supplied a vector of 18 objects, the last 2 rows would receive null values.) The columnID field must not be null.

public void addRow(Object[] rowData)
public void addRow(Vector rowData)

Append new rows to the table. Like the padding that occurs with adding columns, the row's data is truncated or extended as necessary to match the current number of columns in the table.

public void insertRow(int row, Object[] rowData)
public void insertRow(int row, Vector rowData)

Insert a new row at row in the table. As with the addRow( ) methods, the size of the rowData vector is adjusted to match the number of columns in the table.

public void moveRow(int startIndex, int endIndex, int toIndex)

Move a range of rows (from startIndex to endIndex inclusive) to a new location (toIndex). The other rows are shifted accordingly.

public void removeRow(int index)

Delete the row at index from the table.

public void setColumnIdentifiers(Object[] columnIDs)
public void setColumnIdentifiers(Vector columnIDs)

Set the identifiers for the columns in the table to columnIDs. The number of identifiers passed in dictates the number of columns in the table.

public void setNumRows(int newSize)

Change the number of rows in the current table. If newSize is less than the current table size, the extra rows are truncated. If newSize is larger than the current table size, extra rows (new Vector(getColumnCount( )) objects) are added to pad the table to newSize rows.

15.3.4 The TableModelEvent Class

Several methods of the AbstractTableModel class help you fire events to report changes in your data model. All of the methods build an appropriate TableModelEvent object and send it to any registered listeners through the fireTableChanged( ) method.

15.3.4.1 Properties

The TableModelEvent class encompasses the properties listed in Table 15-12.

Table 15-12. TableModelEvent properties

Property

Data type

get

is

set

Default value

column

int

·

     

firstRow

int

·

     

lastRow

int

·

     

type

int

·

     

The column property shows the column affected by this event, which could be a specific column or ALL_COLUMNS. Likewise, firstRow and lastRow identify the first and last row in a range of rows affected by this event; either property may be a specific column or HEADER_ROW. lastRow is always greater than or equal to firstRow. Finally, type indicates which type of event occurred; its value is one of the constants INSERT, UPDATE, or DELETE.

15.3.4.2 Constants

The properties and constructors can use several constants, defined in the TableModelEvent class and shown in Table 15-13.

Table 15-13. TableModelEvent constants

Constant

Data Type

Description

ALL_COLUMNS

int

Indicates that the event is not localized on one column.

DELETE

int

One of the values that can be returned by the getType( ) call; indicates that rows have been deleted from the model.

HEADER_ROW

int

This constant can be used in place of a normal first row value to indicate that the metadata (such as column names) of the table has changed.

INSERT

int

One of the values that can be returned by the getType( ) call; indicates that rows have been inserted into the model.

UPDATE

int

One of the values that can be returned by the getType( ) call; indicates that data in some of the rows has changed. The number of rows and columns has not changed.

15.3.5 The TableModelListener Interface

As you saw in previous examples, the JTable class listens to model events to keep the view of the table consistent with the model. If you want to monitor changes in a table yourself, implement this interface. Only one method exists for event notification, and you register the listener with the model itself, not with the JTable.

public void tableChanged(TableModelEvent e)

Called for any table model events your model generates. You can use getType( ) to distinguish events of different types.

15.3.6 Dynamic Table Data

Since we've already seen some simple examples of table models at work in a JTable object, let's look at some more interesting ones.

You can take advantage of the convenience methods that generate TableModelEvent objects to create a table model that responds to dynamic data. A classic example of dynamic table data is stock market quotes. Of course, you have to pay for the quote feed, but if you've done that, you can use a JTable to show off your portfolio.

Just to try out dynamic data, we'll simulate a stock market where Swing components are the traded commodities. The heart of this simulator is MYOSM, which sets up a thread to play with the values of the components so that we have changing data to look at. The example contains two different constructors. One constructor for MYOSM simply starts the updater thread. This is the version our final application uses since the JTable we build is showing off the stock quotes; the other constructor creates its own JFrame that monitors and displays the changes. If you run MYOSM as an application, that's the version you'll see.

Here's the code for our stock market. (The Stock class is available online, but isn't particularly interesting, so we didn't list it.)

// MYOSM.java // Make Your Own Stock Market: a simple stock market simulator that contains a // few stocks and their current prices (and deltas). It randomly adjusts the  // prices on stocks to give a dynamic feel to the data. // import javax.swing.*; import java.awt.*; import java.util.*; public class MYOSM extends JFrame implements Runnable {   Stock[] market = {     new Stock("JTree", 14.57),     new Stock("JTable", 17.44),     new Stock("JList", 16.44),     new Stock("JButton", 7.21),     new Stock("JComponent", 27.40)   };       boolean monitor;   Random rg = new Random( );   Thread runner;   public MYOSM( ) {     // Not meant to be shown as a real frame     super("Thread only version . . . ");     runner = new Thread(this);     runner.start( );   }   // This version creates a real frame so that you can see how the typical stocks   // are updated. It's not meant to be used with other programs, but rather as a   // debugging tool to make sure the market runs smoothly.   public MYOSM(boolean monitorOn) {     super("Stock Market Monitor");     setSize(400, 100);     setDefaultCloseOperation(EXIT_ON_CLOSE);     monitor = monitorOn;     getContentPane( ).add(new JLabel("Trading is active.  " +            "Close this window to close the market."),            BorderLayout.CENTER);     runner = new Thread(this);     runner.start( );   }   // Here's the heart of our stock market. In an infinite loop, just pick a   // random stock and update its price. To make the program interesting, we'll   // update a price every second.   public void run( ) {     while(true) {       int whichStock = Math.abs(rg.nextInt( )) % market.length;       double delta = rg.nextDouble( ) - 0.4;       market[whichStock].update(delta);       if (monitor) {         market[whichStock].print( );       }       try {         Thread.sleep(1000);       }       catch(InterruptedException ie) {       }     }   }   public Stock getQuote(int index) {     return market[index];   }   // This method returns the list of all the symbols in the market table.   public String[] getSymbols( ) {     String[] symbols = new String[market.length];     for (int i = 0; i < market.length; i++) {       symbols[i] = market[i].symbol;     }     return symbols;   }   public static void main(String args[]) {     MYOSM myMarket = new MYOSM(args.length > 0);     myMarket.setVisible(true);   } }

With this stock market class producing dynamic data, you need a model that can listen for that data. Use a polling method to extract new data at intervals. If your data source generated events, you could also create a table model that listened for the events and updated your table immediately after receiving a change event. (If updating your table is costly, polling might make more sense.)

Here's the table model that works in conjunction with the dynamic data source. Notice that we implement Runnable so that we can start a thread to control the polling frequency. Apart from that, the data model handles the same tasks as the previous data models. We return the appropriate column names and values for individual cells.

//  MarketDataModel.java // import javax.swing.table.*; import javax.swing.*; public class MarketDataModel extends AbstractTableModel implements Runnable {   Thread runner;   MYOSM market;   int delay;   public MarketDataModel(int initialDelay) {     market = new MYOSM( );     delay = initialDelay * 1000;     Thread runner = new Thread(this);     runner.start( );   }   Stock[] stocks = new Stock[0];   int[] stockIndices = new int[0];   String[] headers = {"Symbol", "Price", "Change", "Last updated"};   public int getRowCount( ) { return stocks.length; }   public int getColumnCount( ) { return headers.length; }   public String getColumnName(int c) { return headers[c]; }   public Object getValueAt(int r, int c) {     switch(c) {     case 0:       return stocks[r].symbol;     case 1:       return new Double(stocks[r].price);     case 2:       return new Double(stocks[r].delta);     case 3:       return stocks[r].lastUpdate;     }     throw new IllegalArgumentException("Bad cell (" + r + ", " + c +")");   }   public void setDelay(int seconds) { delay = seconds * 1000; }   public void setStocks(int[] indices) {     stockIndices = indices;     updateStocks( );     fireTableDataChanged( );   }   public void updateStocks( ) {     stocks = new Stock[stockIndices.length];     for (int i = 0; i < stocks.length; i++) {       stocks[i] = market.getQuote(stockIndices[i]);     }   }   public void run( ) {     while(true) {       // Blind update . . . we could check for real deltas if necessary       updateStocks( );       // We know there are no new columns, so don't fire a data change; fire only a       // row update. This keeps the table from flashing       fireTableRowsUpdated(0, stocks.length - 1);       try { Thread.sleep(delay); }       catch(InterruptedException ie) {}     }   } }

Most of the code is fairly simple. getValueAt( ) merely looks up the appropriate value from the table's data, taking into account the column requested so it can return an appropriate type of object. The one trick is that our model doesn't necessarily track all the stocks simulated by MYOSM. The model provides a setStocks( ) method that lets you select the stocks that interest you and populates the model's data accordingly. setStocks( ) fires a TableModelEvent indicating that the table's data has changed; in particular, rows (but not columns) may have been added or deleted. The model's run( ) method fires a similar event with each update, indicating that the data in the rows has been updated. With this model in place, we can create a table using the same simple code, but this time, we update the table every five seconds. Figure 15-6 shows the results.

Figure 15-6. A table model that generates dynamic (and precise!) data
figs/swng2.1506.gif

Just to be complete, here's the code for this application that displays our market simulator. Notice that only the model passed to the JTable constructor really changed from the previous table application.

// MarketTable.java // A test of the JTable class using default table models and a convenience // constructor // import java.awt.*; import javax.swing.*; public class MarketTable extends JFrame {   public MarketTable( ) {     super("Dynamic Data Test");     setSize(300, 200);     setDefaultCloseOperation(EXIT_ON_CLOSE);     // Set up our table model with a 5-second polling delay.     MarketDataModel mdm = new MarketDataModel(5);     // Pick which stocks we want to watch . . .      mdm.setStocks(new int[] { 0, 1, 2 });     //  . . . and pop up the table.     JTable jt = new JTable(mdm);     JScrollPane jsp = new JScrollPane(jt);     getContentPane( ).add(jsp, BorderLayout.CENTER);   }   public static void main(String args[]) {     MarketTable mt = new MarketTable( );     mt.setVisible(true);   } } 

15.3.7 Database Data

Another popular source of information for table displays is database records. You can create a table model that connects to a database and produces rows and columns based on the results of queries you send. Figure 15-7 shows a simple application that passes any query you type to the database. The table displays the results from your query. The column headings (and even the number of columns) are taken directly from the database and depend entirely on the query and the database contents.

Figure 15-7. A database query result table example
figs/swng2.1507.gif

In this example, each new search causes a fireTableChanged( ), since the query may have new columns. If we could count on the columns remaining the same, we could use the fireTableRowsUpdated( ) method, like we did with the dynamic data example.

Here is the code to build this application. Most of the work is setting up the labels and text fields that serve as our graphical interface, but take a look at the anonymous event handler for the Search button. This is where we pass the database URL and query to our model. We'll use the URL and query as the starting point for discussing the model code below.

// DatabaseTest.java // import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; public class DatabaseTest extends JFrame {      JTextField hostField;   JTextField queryField;   QueryTableModel qtm;   public DatabaseTest( ) {     super("Database Test Frame");     setDefaultCloseOperation(EXIT_ON_CLOSE);     setSize(350, 200);     qtm = new QueryTableModel( );     JTable table = new JTable(qtm);     JScrollPane scrollpane = new JScrollPane(table);     JPanel p1 = new JPanel( );     p1.setLayout(new GridLayout(3, 2));     p1.add(new JLabel("Enter the Host URL: "));     p1.add(hostField = new JTextField( ));     p1.add(new JLabel("Enter your query: "));     p1.add(queryField = new JTextField( ));     p1.add(new JLabel("Click here to send: "));     JButton jb = new JButton("Search");     jb.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent e) {         qtm.setHostURL(hostField.getText( ).trim( ));         qtm.setQuery(queryField.getText( ).trim( ));       }     } );     p1.add(jb);     getContentPane( ).add(p1, BorderLayout.NORTH);     getContentPane( ).add(scrollpane, BorderLayout.CENTER);   }   public static void main(String args[]) {     DatabaseTest tt = new DatabaseTest( );     tt.setVisible(true);   } }

Following is the code for the query model. Rather than hold a vector of vectors, we'll store a vector of String[] objects to facilitate retrieving the values. This query table contains all of the code required to connect to a database server using a JDBC driver. The server and driver we are using were both written by Brian Cole and are available along with the rest of the code in this chapter at http://www.oreilly.com/catalog/jswing2.

We have code similar to all of our previous examples for the basic methods from the AbstractTableModel class, which we extend. But we have to add support for a changing host and query. If the host stays the same from the last query that was run, we can continue to use the same connection. If not, we close the old connection and build a new one. When we set the query, however, we have to send it to the database server, and then update the table once we get the response. Notice that this example fires a full table-changed event at the end of the setQuery( ) call. With our open-ended query form, chances are we'll get back some very different results from query to query, so we don't bother trying to send only modification events. If you're unfamiliar with SQL or the JDBC code throughout this class, check out Database Programming with JDBC and Java by George Reese (O'Reilly).

// QueryTableModel.java // A basic implementation of the TableModel interface that fills out a Vector of // String[] structure from a query's result set // import java.sql.*; import java.io.*; import java.util.Vector; import javax.swing.*; import javax.swing.table.*; public class QueryTableModel extends AbstractTableModel {   Vector cache;  // Will hold String[] objects   int colCount;   String[] headers;   Connection db;   Statement statement;   String currentURL;   public QueryTableModel( ) {     cache = new Vector( );     new gsl.sql.driv.Driver( );   }       public String getColumnName(int i) { return headers[i]; }   public int getColumnCount( ) { return colCount; }   public int getRowCount( ) { return cache.size( );}   public Object getValueAt(int row, int col) {      return ((String[])cache.elementAt(row))[col];   }   public void setHostURL(String url) {     if (url.equals(currentURL)) {       // Same database; we can leave the current connection open       return;     }     // Oops . . . new connection required     closeDB( );     initDB(url);     currentURL = url;   }   // All the real work happens here; in a real application, we'd probably perform the   // query in a separate thread.   public void setQuery(String q) {     cache = new Vector( );     try {       // Execute the query and store the result set and its metadata.       ResultSet rs = statement.executeQuery(q);       ResultSetMetaData meta = rs.getMetaData( );       colCount = meta.getColumnCount( );       // Now we must rebuild the headers array with the new column names.       headers = new String[colCount];       for (int h=1; h <= colCount; h++) {         headers[h-1] = meta.getColumnName(h);       }       // Now we must file the cache with the records from our query. This would not       // be practical if we were expecting a few million records in response to our       // query, but we aren't, so we can do this.       while (rs.next( )) {         String[] record = new String[colCount];         for (int i=0; i < colCount; i++) {           record[i] = rs.getString(i + 1);         }         cache.addElement(record);       }       fireTableChanged(null); // Notify everyone that we have a new table.     }     catch(Exception e) {       cache = new Vector( );   // Blank it out and keep going.       e.printStackTrace( );     }   }   public void initDB(String url) {     try {       db = DriverManager.getConnection(url);       statement = db.createStatement( );     }     catch(Exception e) {       System.out.println("Could not initialize the database.");       e.printStackTrace( );     }   }   public void closeDB( ) {     try {       if (statement != null) { statement.close( ); }       if (db != null) {        db.close( ); }     }     catch(Exception e) {       System.out.println("Could not close the current connection.");       e.printStackTrace( );     }   } }

This model does not support database updates using any of the entries you see in the table. You could certainly support that feature if you needed to. The Swing package has a more complete result-set model called the JDBCAdapter in the table examples directory if you want another example of database and JTable communication.

15.3.8 Yet More Useful Methods

public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer)

Add a new renderer for a particular type of data, given by the columnClass.

public void addColumnSelectionInterval(int index0, int index1)
public void addRowSelectionInterval(int index0, int index1)

Programmatically add an interval of rows or columns to the current selection. The appropriate selectionAllowed property must be set to true for this to work.

public void clearSelection( )

Clear any selection that might exist on the table. Nothing happens if no selection exists.

public void removeColumnSelectionInterval(int index0, int index1)
public void removeRowSelectionInterval(int index0, int index1)

Remove row or column intervals from the current selection.

public void selectAll( )

Select the entire table.

public void setColumnSelectionInterval(int index0, int index1)
public void setRowSelectionInterval(int index0, int index1)

Set the selection on the table to the given column or row interval.

public void moveColumn(int column, int targetColumn)

Move the column at the index given by column to the new targetColumn index by delegating the request to the column model. Other columns are shifted as needed to make room for (and close the gap left by) the relocated column.

15.3.9 The JTableHeader Class

The JTableHeader class is an extension of JComponent and serves as the header component for tables. It not only dictates the basic color and font used for the header, but also the resizability and relocatability of the columns in the table. If you have an appropriate renderer for the header, you can also enable tooltips for the header. An example of custom renderers appears in Chapter 16.

15.3.9.1 Properties

The JTableHeader class has the properties listed in Table 15-14.

Table 15-14. JTableHeader properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

   

JTableHeader.AccessibleJTableHeader( )

columnModel

TableColumnModel

·

 

·

DefaultColumnModel( )

draggedColumn

TableColumn

·

 

·

null

draggedDistance

int

·

 

·

0

opaqueo

boolean

 

·

·

true

reorderingAllowed

boolean

·

 

·

true

resizingAllowed

boolean

·

 

·

true

resizingColumn

TableColumn

·

 

·

null

table

JTable

·

 

·

null

UIb

TableHeaderUI

·

 

·

From L&F

UIClassIDo

String

·

   

"TableHeaderUI"

updateTableInRealTime

boolean

·

 

·

true

bbound, ooverridden

See also properties from the JComponent class (Table 3-6).

The columnModel property is the TableColumnModel in place for the header. This is normally set through the constructor during the JTable initializeLocalVars( ) call. The draggedColumn and resizingColumn properties return the TableColumn object that the user has moved or resized. You can control whether the user is allowed to move or resize columns using the reorderingAllowed and resizingAllowed properties. The updateTableInRealTime property dictates whether the column being moved or resized is visually updated during the move or after. If this property is false, only the column headers move until the action is complete, and then the table is updated. The table property represents the companion table for the header.



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