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
 

Mastering the JTable, by Bob Hendry

JBuilder Data Express controls enable JBuilder developers to use prebuilt objects to provide the user with an interface in which to view and manipulate data. For the most part, the use of Data Express components simplifies our task of programming data access functionality into our applets/applications. One drawback of using these components is that you're restricted to using only functions and changing properties that are supported by that specific control. In other words, although JBuilder simplifies your task, you can use only prewritten functionality.

What if you wanted total control over your data? What if you wanted to control every aspect of how your data is formatted, displayed, edited, and up- dated? The answer to this is knowing how to use the native Java JTable. Mastering the use of this class is your key to exercising total control over data within your applet/application - albeit with a considerable tradeoff in added complexity.

Over the next few issues I'll cover the use of the JTable and help you master this powerful Java component.

The JTable
Before Data Express there was the JTable, a Java Swing Component with the Swing JComponent as its immediate ancestor. The JTable is used primarily to provide users with a way to view and manipulate data in a columnar (or grid) format. With this functionality, users are allowed to edit and scroll through many records of data rather than editing one record at a time.

Although the JTable can obtain its data from many sources, it's most useful when it holds data retrieved from a database via JDBC. Interestingly, the JTable is the only native Java control that can be populated directly from JDBC that allows the user to manipulate data in a columnar format. As you might guess, the JTable is an extremely complex component - in my opinion, the most complex in the entire Java language. Since books can be written on its use, it's amazing that the JTable is the most underdocumented or written about Java component. Even the best of Java books barely give it a mention. I hope to help change all that.

Understanding the Basics
Like most things in Java, matters appear more difficult than they really are. So it seems with the JTable. Understanding its functionality first requires understanding related classes. As you'll soon learn, with all of its robust functionality the JTable relies on other Java classes to perform most of the work. Often the relationship between these related classes is a source of confusion; understanding it is key to understanding the JTable. Learning it can be daunting, but it's made easier if spoon-fed one piece at a time.

The first class that needs to be understood is the JTable itself. This class contains a two-dimensional view of the data to be displayed. In other words, when the user is looking at the GUI, what he or she sees is the JTable class. Simple uses of the JTable are relatively easy to set up. However, in complex applications use of the JTable without any of its supporting classes is very limited. For example, if you need to automatically populate the JTable with rows and columns from a JDBC data source, the use of a JTable alone will not suffice.

If used alone, the data contained within a JTable needs to be passed as an array of objects or as a vector to the JTable constructor. This may be okay if you want the JTable to contain static values. More than likely you'll want the data to come from a database. In a nutshell the JTable contains the visual portion of the data and generally doesn't control where it comes from or how it behaves (e.g., what happens if it changes). If you have the data ahead of time and want to simply display it to the user (without regard to changes in data) use the simple JTable constructors listed below:

JTable()
This constructs a "basic" JTable that's initialized with a default table model, a default column model, and a default selection model (these classes will be discussed later in this series). This constructor is of limited use because the number of rows and columns haven't been specified. Also, the JTable receives limited functionality because the default models will be used. Since you haven't built custom models (or don't yet know how!), don't expect the default models to do much. This constructor isn't used very often, so I won't offer a code example.

JTable(int numberofRows,int numberofColumns)
This constructs a JTable with numberofRows and numberofColumns of empty cells using the default models. Since no column names can be specified in the constructor, Java will produce generic ones in the form "A", "B", "C", "D", and so on. Ironically, you can add and remove columns after the table has been constructed, but you can't add or remove rows. Like the first constructor, this one doesn't directly accept data.

The code in Listing 1 illustrates the use of this constructor. In the listing, the class constructor builds a JTable (with three rows and two columns) and adds it to a JScrollPane. When a JTable is added to a JScrollPane, the JScrollPane automatically obtains the JTable's header, which displays the column names, and puts it on top of the table. When the user scrolls down, the table's header remains visible at the top of the viewing area. Next, the program's main method places the JScrollPane on a JFrame via a constructor call. Finally, the program uses the setValueAt method to set the value for a single cell in the JTable. Three names and ages are added. Here's the syntax for setValueAt:

public void setValueAt(object value, int row, int column)

  • Value: The new value to be placed in the cell
  • row: The row in the JTable to be changed
  • column: The column in the JTable to be changed
Notice that the setValueAt method doesn't care what data type the value is. In the above example I used the same method to set names (strings) and ages (integers). You may think that the setValueAt is an overloaded method. It isn't. It takes the value argument as type Object so the programmer doesn't have to differentiate between data types. Also rows and columns start at the number "0", not "1".

As noted earlier, data can't be included in the constructor JTable- (rows, columns). However, this isn't to prevent you from crafting creative schemes in your data-getting endeavors. The previous example illustrates that the setValueAt method can be used to set the value of a single cell within a JTable. So why can't the data come from a database rather than a hard-coded value? Well, it can. The current constructor doesn't support the use of database data but nothing is preventing you from doing the legwork yourself.

Consider the program in Listing 2. This listing is similar to the previous one, the main difference being that the data within the setValueAt method comes from data in a database. The focus of this series of articles is on the use of JTables and not necessarily JDBC. However, the code does warrant a brief explanation. For the sake of brevity I'll discuss only the differences from the first example.

try {
Class.forName("sun.jdbc.odbc.Jdbc
OdbcDriver");
} catch(java.lang.ClassNotFoundException e) {
System.err.print("ClassNotFoundException: ");
System.err.println(e.getMessage());
}

This first block of code loads the "Level 1" Java database driver. The Level 1 driver is often called the JDBC-ODBC Bridge and is used to connect to local databases that support the ODBC interface.

String url = "jdbc:odbc:bradygirls";
Ex1Con= DriverManager.getConnection(url, "", "");
Ex1Stmt = Ex1Con.createStatement();
Ex1rs = Ex1Stmt.executeQuery( "SELECT name, age FROM bradygirls ORDER BY name");

Next, the string containing the URL is built; jdbc:odbc: is specified because the JDBC-ODBC bridge is being used. :bradygirls is the name of the ODBC data source. Note: For this to work, an ODBC data source named bradygirls must be set up on your local machine. This can be achieved via the control panel on Windows machines. In my case, I'm using an MS Access database.

After the database connection has been established a statement class is created, then an executeQuery method is fired. This method takes a string containing a valid SQL statement for an argument. The results of the SQL statement are read into a ResultSet class. Now we're ready to read the contents of the result set and put the data into our JTable.

int li_row = 0;
while (Ex1rs.next()) {
myTable.setValueAt(Ex1rs.getString(1),li_row,0);
myTable.setValueAt(Ex1rs.getNumber(2),li_row,1);
li_row ++;
} // while

The above while block will loop through the result set one row at a time and populate the JTable. The methods used to extract data from the result set are commonly called the getXXXX() methods (with the XXXX being the data type of the column in the result set).

Notice that the first column in a result set is column "1". This is a direct contrast to the first column being "0" in a JTable. This discrepancy can be confusing for a while, but you'll get used to it. Believe me, it's not the only discrepancy you'll find in Java!

Although the above example uses a database to populate the JTable, keep in mind that it's provided as a work-around (instead of using Table Models). Table Models (discussed later) provide a better way to automatically populate a JTable directly from a database. But until you understand Table Models, this code example should keep you pretty busy - especially if you're new to JDBC. The results of the above program are displayed in Figure 1.

Figure 1
Figure  1:

JTable(Object[][],rowscolData,Object[] columnNames)

Unlike the previous two constructors, this one is used when the rows, columns, and headings are known at the time the JTable is instantiated. Data for the rows and columns are passed in a two-dimensional array of objects. The column headings are passed as a single array of objects. (FYI: There's a similar constructor that uses vectors in place of objects.) The following code snippet uses arrays of objects to instantiate and populate a JTable.

Object[] [] data =
{ {"Marsha", new Integer(18)},
{"Jan", new Integer(17)},
{"Cindy", new Integer(16)} };
String[] colNames = {"First Name","Age");
JTable myTable = new JTable(data,colNames);

All of the simple constructors discussed here are easy to use. However, as I mentioned, these constructors also have a few significant limitations. For example, they automatically make every cell (and row) editable. This is misleading to the user because if you're allowed to edit a value, you're implying that changes can be saved; and remember, the JTable isn't even connected to a database. Furthermore, all of the simple constructors treat all data types as strings. This can be annoying in two ways. First, when aligning data, strings are generally left-aligned and numbers are right-aligned. Since the JTable treats everything as a string, the ages of the Brady Girls (Figure 1) appear to be aligned incorrectly. Second, the JTable has the ability to use other edit styles besides the ones that handle only text. For example, if a value to place into a cell is Boolean, the JTable has the ability to display the data in a check box. After all, a Boolean has only two values, so a check box seems appropriate. However, if you use one of the JTable constructors listed previously, a Boolean column will be displayed as a string. Finally, the largest limitation is that they don't automatically "link" the JTable to a database. Although there are a few limited ways to get around this, one way is to read database columns into an array of objects or vectors, then pass those objects to the JTable constructor. A better way is to implement your own custom table model, which will be next month's focus.

Default Behavior
Whatever constructor you use, be aware of the following default behavior:

  • All columns in the JTable begin with equal widths, and the columns automatically fill the entire width of the JTable.
  • If the container is resized (made larger), all the cells within the JTable become larger, expanding to fill any extra space.
  • When a cell is selected (usually by double-clicking on it), the entire row becomes selected. The cell that was double-clicked becomes highlighted.
  • Users can rearrange columns by dragging or dropping them to the left or to the right.
  • Columns are resizable by dragging the column header to the left or to the right. This doesn't adjust the size of the JTable itself; the other columns will automatically resize to fill in unused space. Columns can be set to a default size by calling the setPreferredWidth() method for the column model. More on column models next month.
Some Fun with JTables
The program in Table 1 contains additional methods on a JTable that can be used to modify its appearance.

Table 1

Coming Up Next Issue
As you can see, the JTable controls how the data is presented but has little control over how it's populated. This job is the responsibility of the TableModel, which defines where the data comes from, what the user is allowed to do with it, and what happens if it changes. This model is a Java class you create that extends the Java class AbstractTableModel. This class is fairly complex and can be a bit puzzling. I'll cover it in more detail in the next issue.

Another large piece of the puzzle is the TableModelListener. Its job is to execute when any of the data has changed in the TableModel. A TableModelListener is implemented whenever you create a class extending the Java class TableModelListener, or it can be implemented in an inner class. Of course, the functionality provided by the listener is completely up to you. Many programmers place code to update the database within a TableModelListener.

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]

	

Listing 1
import javax.swing.*;
import java.awt.*;

public class BradyGirls extends JPanel{
   static JTable myTable;

BradyGirls(){
   myTable = new JTable(3,2);
   JScrollPane myPane = new JScrollPane(myTable,
   JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
 		JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
   add(myPane);
   myTable.setPreferredScrollableViewportSize(new Dimension(500, 70));


} 

public static void main(String args[]){
   JFrame myFrame = new JFrame("Brady Bunch Girls");
   myFrame.getContentPane().add(new BradyGirls());
   myFrame.setVisible(true);
   myFrame.pack();
   myTable.setValueAt("Marsha",0,0);  
   myTable.setValueAt("Jan",1,0);
   myTable.setValueAt("Cindy",2,0);
   myTable.setValueAt(new Integer(18),0,1);  
   myTable.setValueAt(new Integer(17),1,1);
   myTable.setValueAt(new Integer(16),2,1);
   } 
}
 
Listing 2

import javax.swing.*;
import java.awt.*;
import java.sql.*;

public class BradyGirls extends JPanel{
   static Connection Ex1Con;
   static Statement Ex1Stmt;
   static ResultSet Ex1rs;
   static JTable myTable;
  
BradyGirls(){
   myTable = new JTable(3,2);
   JScrollPane myPane = new JScrollPane(myTable,
   JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
   JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
   add(myPane);
   myTable.setPreferredScrollableViewportSize(new Dimension(500, 70));
   }

public static void main(String args[]) throws SQLException{
   JFrame myFrame = new JFrame("Brady Girls Table");
   myFrame.getContentPane().add(new BradyGirls());
   myFrame.setVisible(true);
   myFrame.pack();

   //Initialize and load the JDBC-ODBC driver.
   try {
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
   } catch(java.lang.ClassNotFoundException e) {
      	   System.err.print("ClassNotFoundException: ");
     		   System.err.println(e.getMessage());
   }
   String url = "jdbc:odbc:bradygirls";
   Ex1Con= DriverManager.getConnection(url, "", "");
   Ex1Stmt = Ex1Con.createStatement();
   Ex1rs = Ex1Stmt.executeQuery( "SELECT name, age FROM bradygirls ORDER BY name");
   int  li_row = 0;
   while (Ex1rs.next()) {
      myTable.setValueAt(Ex1rs.getString(1),li_row,0);
         myTable.setValueAt(Ex1rs.getString(2),li_row,1);
         li_row ++;
      } // while
   } 
} 



  
 
 

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.