HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

Using The Jtable, by Bob Hendry

In Part 2 I continue my discussion on the use of the JTable. (Part 1, "Mastering the JTable," can be found in the January issue of JDJ, [Vol. 6, issue 1].) I'll briefly review the three major classes you'll need while working with data within the JTable.

1. JTable: Controls the visual presentation of the data, however, it has limited control over where the data comes from. In the simplest of circumstances, the JTable can populate itself with data only if the data is static and doesn't come from a database. In the above case, it can be used without any supporting classes.

Usually the data to be populated into a JTable comes from a database. In this case, the JTable must work with the JTableModel class; rarely does it work alone.

2. AbstractTableModel: Controls where the data comes from and how it behaves within the JTable. Although the data can come from just about anywhere, this class is almost always used when the data comes from (via JDBC) a database. AbstractTableModel is used by an extension class that you define and is never used directly. It has some interesting default methods, similar to listeners, that automatically fire when data is altered. Knowing when and how these methods are fired is important to understanding how the AbstractTableModel works. I'll discuss them later.

3. TableModelListener: Not really a class but an interface that's used when you want some action to be performed while the user is interfacing with the data in the JTable. It's misnamed - in general, listeners are automatically fired when some action is performed on a control. A programmer-defined class that implements the TableModelListener isn't really a listener at all as it's never automatically fired. It must be manually fired by the programmer via the fireTableDataChanged method for the TableModel. Since TableModelListeners must be fired manually, they can't be considered a true listener. Not that they aren't valuable. I think the powers that be at Sun purposely made it this way so the programmer controls when the listener is fired.

The code placed in the listener can vary. Popular uses include adding code to update the database. Note: The use of this interface is optional. If you don't really care when data is changed, you don't have to use it.

Figure 1 illustrates the relationship between the three major players.

Figure 1

Part 1 focused on the use and implementation of the JTable. Part 2 covers the use of the table model in depth.

The AbstractTableModel Class
As stated earlier, the job of the AbstractTableModel is to provide the source for the data in the JTable and some built-in methods that are automatically fired when certain things happen within the Table (more on this later). This class isn't used directly but as an ancestor in a programmer-created class. When a programmer-defined class extends the AbstractTableModel class, in Javaspeak we're using a Table Model.

The Table Model can get the data from several places. I'll start with a simple Table Model example and get more complex as we go. Your Table Model might obtain its data in an array, vector, or hashtable, or it might get the data from an outside source such as a database (the most flexible and most complex). If needed, it can even generate data at runtime. In Listing 1 (Listings 1-6 can be found below), the Table Model populates itself with an array of strings. The resulting frame window is displayed in Figure 2.

Figure 2

Table Model Dissected
There are a few immediate advantages to using a Table Model instead of using the JTable alone. In addition to the data, the Table Model informs the JTable of the data type of the column, which leads to some desired formatting behavior. For example, since the JTable knows that a column is numeric, it'll right-justify it within the cell. Also, Boolean values will display as a checkbox (since there are only two possible values). Since the JTable is using a Table Model, it'll display the data in a user-friendly way. In Listing 2 the application doesn't use a Table Model. Notice the difference in how the data is represented (see Figure 3).

Figure 3

Table Model Methods
The methods shown in Listing 1 are used internally by the Table Model. Similar to listeners, these methods are fired when needed, and the programmer can choose to implement them at will. You can use all or none of them; Java doesn't care. The important point is that these methods are fired automatically and usually never by the programmer. Similar to other automatically fired methods, Java lets you decide what kind of code to execute. Java provides the method, but you (surprise!) decide the functionality. The automatic table methods are described below.

getColumnCount() This method is fired once - at the time the JTable is created. Its sole purpose is to tell the JTable how many columns it has. If the data to be passed to the JTable is contained in an array, simply return its length (see Listing 1). If the data comes from a database, the ResultSetMetaData class can determine how many columns exist by using the following code:

// myResultSet is of the Java type ResultSet and has already been
// filled with data in the program.
int li_cols;
ResultSetMetaData myMetaData = myResultSet.getMetaData();
li_cols = myMetaData.getColumnCount();
return li_cols;

getRowCount()
As with getColumnCount(), this method is fired once - at the time the JTable is created. Its sole purpose is to tell the JTable how many rows it has. If the data to be passed to the JTable is contained in an array, simply return its length (see Listing 1). If the data comes from a database, there are a few options. Later I'll show how to populate database data into a vector. The following code can be used to return the number of rows in a vector. I'll show you how to populate it later.

// Get the number of rows that will be used to build the JTable. allRows if of the Java type vector
return allRows.size();

getColumnName(int col)
After the Table Model fires off the getColumnCount() method, it calls this method for each column that's been found. For example, if the getColumnCount() method returned a seven (for seven columns), the Table Model will fire this method seven times, passing it the number of the column that it must resolve the column name for each time. The method returns a string that represents the column name. For example:

// colnames is an array of Strings representing the column names for
// the JTable.
return colNames[col];

getValueAt(int row int col)
After the Table Model determines the number of rows and columns, it needs to populate them with data. This is the job of the getValueAt function, which is called for every column and row intersection (called a cell) that exists in the Table Model. If there are 30 rows with five columns, this method will automatically be fired 150 times - one for each cell. As in the previous methods, you determine which code is executed in this method. Remember, Java provides the method, you provide the functionality. Listing 1 returns objects from a two-dimensional array. The following code can be used if you're returning data from a vector.

// row and allrows are vectors
row = (Vector) allRows.elementAt(row);
return row.elementAt(col);

getColumnClass(int col)
This method is automatically fired and gets the class for each column of the Table Model. The following code gets the data type of each column.

// Return the class for this column
return getValueAt(0, col).getClass();

Next, the table compares the column's data type with a list of data types for which cell renders (more about renders in Part 3) are registered. The default classes are:

  • Object: Left-aligned label that displays the object's string value
  • Boolean: Displayed as a check box
  • mageIcon: Displayed by a centered label
  • Number: Displayed as a right-aligned label
Remember, if you don't specify a table model for a JTable, Java will give it a "default" table model and treat the data types of all columns as the object data type. In this case, all the row data will display as strings and be left-justified.

isCellEditable(int row, int col)
This method determines which rows and columns the user is allowed to modify. Since this method returns a Boolean, if all cells are editable it simply returns a true. To prevent a JTable from editing a particular column or row value, it returns a false from this method. The following code enables only column one to display while allowing the rest of the columns to be modified.

// Make column one noneditable
while allowing the user to edit at
all // other columns.
If (col == 1){
return false;
}
else{
return true;
}

public void setValueAt(Object value, int row, int col)
When the user makes changes to an editable cell, the Table Model is notified via this method. The new value, as well as the row and column it occurred in, is passed as arguments to this method. If the original data is coming from a database, this method becomes important. As you'll see, data retrieved from a database is held locally within the Table Model, usually as vectors. When the user changes a cell value in a JTable, the corresponding data in the Table Model isn't automatically changed. It's your responsibility to add code in this event to ensure that the data in the Table Model is the same as the data in the JTable. This becomes important when code is added to update the database. The following code updates the data (held in an array of objects) in the Table Model with the new value that the user just entered in the JTable.

// Update the array of objects with
the changes the user has just entered in a cell.
Then notify all listeners (if any) what column
and row has changed. Further processing may take place there.
rowData[row][col] = value;
fireTableDataChanged();

Taking the Model Further
I have a promising example of Table Models that use JDBC as their data source. By far the most powerful implementation of the JTable with a Table Model uses data from a database (via JDBC). The good news is that adding the JDBC element doesn't significantly change what we've already learned. Admittedly, it does add more complexity to our task, but it doesn't change the way we treat the JTable/Table Model relationship.

Where Does the Data Come From?
Any program written in Java uses JDBC to connect to a database. JDBC is a complex topic in its own right so I'll save the comprehensive overview for a later date. Here I'll explain the relationship between the Table Model and JDBC. The examples will use a Microsoft Access database that can be reached via a locally configured ODBC data source. For simplicity, the data contained within the database will be the same as the data in the previous examples that used arrays as the data source.

Creating the Table Model
The first step is to declare the Table Model and all instance variables that will be needed. All classes needed as well as a description of the instance variables are included in the code below:

// Table Model
import javax.swing.table.AbstractTableModel;
import java.sql.*;
import java.util.Vector;

public class MyTableModel extends AbstractTableModel {

Connection myConnect; // Hold the JDBC Connection
Statement myStatement; // Will contain the SQL statement
ResultSet myResultSet; // Contains the result of my SQL statement
int li_cols; // Number of database columns
Vector allRows; // Contains all rows of the result set
Vector row; // Individual row for a result set

The next step is to add a constructor to the class. The code in the example constructor is broken into two functions. One secures a connection to the database while the other performs the retrieval. This division of functionality is intentional. When this class is instantiated we want to perform the database connection and the data retrieval. However, during the life cycle of this object, we're only interested in the data retrieval since the connection needs to be performed only once. That's why the connection logic is separate from the retrieval logic. For the sake of simplicity, this example is only interested in retrieving data. The actual update of the data will be a topic in Part 3. The code for the constructor is:

public MyTableModel()  {
//connect to database
try{
connect();
getData();
}
catch(Exception ex){
System.out.println("Error!");
}
}

The first method called within the constructor provides the connection to the database. The primary purpose of this method is to provide a connection to our ODBC data source. Since our connection (myConnect) is an instance variable, the database can be accessed as long as it's instantiated.

void connect() throws SQLException{
try{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch(ClassNotFoundException cnf){
System.out.println("Driver does not exist!");
}
String url = "jdbc:odbc:BradyGirls";
myConnect = DriverManager.getConnection(url);
}

The second method called in the constructor retrieves data from the database and puts it into a vector (see Listing 3). I won't go into detail about vectors as they're a topic in their own right. Vectors are basically "growable" arrays and can also hold any type of object. The function uses two vectors: one holds an individual row of the result set (result of the SQL statement), the other contains all the "row" vectors and is essentially the "vector of vectors." When we connect a JTable with this table model, the vectors will be used to provide the data to be displayed within the table.

The last portion of the Table Model involves the automatic Table Model methods. The code placed in these methods is important. It informs the JTable about the contents of the data and what the data looks like. The automatic Table Model methods are provided in Listing 4.

Building the JTable
If you followed the steps provided, you have a Table Model ready to be used. The next (and last) step is to write a Java program that will use it. Listing 5 instantiates a JTable based on the Table Model that's just been built. Listing 6 contains the completed Table Model.

Conclusion
You should now have a basic understanding of how custom Table Models work and how to use JDBC within a Table Model. We're still far away from what I'd consider a robust application, however. Next month I'll focus on listeners and updating the database with changes to the data.

Author Bio
Bob Hendry is a Java instructor at the Illinois Institute of Technology. He is the author of Java as a First Language. [email protected]

Download Assoicated Source File (~ 36.0 KB)

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.