Programming with JTable

   

Java™ 2 Primer Plus
By Steven Haines, Steve Potts

Table of Contents
Chapter 17.  Advanced Swing


Programming with JTable

One of the most common uses of Graphical User Interfaces (GUIs) is to display data. This data is normally displayed in a grid or table format. Each row represents a row in the database, and each column represents one field name.

Swing introduced a new GUI control class called JTable to address this need. The JTable class is really a display-oriented object. Another invisible class called the model performs the handling of the underlying data. The model is responsible for storing and retrieving data in the database or data file. In addition, it contains a number of other pieces of information about the table, such as

  • The number of rows in the table

  • The number of columns

  • The data type of each column

  • The column headings

  • Whether a column is editable

The easiest way to create a model that can be displayed in a table is to create a class that extends the javax.swing.table.AbstractTableModel class. This class implements the TableModel interface and provides implementations for all but three of the methods: getRowCount(), getColumnCount(), and getValueAt(). Figure 17.1 shows the relationship between the Table Model and the JTable's visual representation of that data.

Figure 17.1. The JTable class controls the visual appearance of the data contained in the Table Model.

graphics/17fig01.gif

The Table Model is responsible for extracting data from and putting data into the database. The JTable class retrieves the data from the Model and displays it.

In this chapter, we will use a model that has been created using static data. In Chapter 19, "JDBC," we will modify this example so that it gets its data from a database. Listing 17.1 shows a simple class that extends the AbstractTableModel.

Listing 17.1 The TestTableModel1.java File
 /*   * TestTableModel1.java   *   * Created on September 3, 2002, 11:34 AM   */  package ch17;  /**   *   * @author  Stephen Potts   */  import java.util.Calendar;  import java.util.GregorianCalendar;  import javax.swing.table.AbstractTableModel;  public class TestTableModel1 extends AbstractTableModel  {      public final static int FIRST_NAME = 0;      public final static int LAST_NAME = 0;      public final static int DOB = 0;      public final static int CERTIFICATION = 0;      public final static int SALARY = 0;      public final static boolean PROGRAMMER = true;      public final static boolean INSTRUCTOR = false;      public Object[][] dataValues =      {          {              "Bush", "Bob",              new GregorianCalendar(1954, Calendar.JULY, 4).getTime(),              new Boolean(INSTRUCTOR), new Float(90000)          },          {              "Ezaguirre", "Art",              new GregorianCalendar(1959, Calendar.DECEMBER, 25).getTime(),              new Boolean(PROGRAMMER), new Float(80000)           },          {              "Jenkins", "Lewellen",              new GregorianCalendar(1961, Calendar.AUGUST, 1).getTime(),              new Boolean(PROGRAMMER), new Float(70000)          },           {              "Wells", "Patricia",              new GregorianCalendar(1965, Calendar.JUNE, 20).getTime(),              new Boolean(INSTRUCTOR), new Float(60000)          },          {              "Fleming", "Terry",              new GregorianCalendar(1953, Calendar.NOVEMBER, 4).getTime(),              new Boolean(INSTRUCTOR), new Float(66000)          },          {              "Tippets", "Rick",              new GregorianCalendar(1959, Calendar.SEPTEMBER, 25).getTime(),              new Boolean(PROGRAMMER), new Float(80000)          },          {              "Hall", "Andy",              new GregorianCalendar(1956, Calendar.DECEMBER, 1).getTime(),              new Boolean(PROGRAMMER), new Float(100000)          },          {              "Gummer", "Steve",              new GregorianCalendar(1967, Calendar.JUNE, 20).getTime(),              new Boolean(INSTRUCTOR), new Float(65000)          }      };      /** Creates a new instance of TestTableModel1 */      public TestTableModel1()      {      }      public int getColumnCount()      {          return dataValues[0].length;      }      public int getRowCount()      {          return dataValues.length;       }      public Object getValueAt(int row, int column)      {          return dataValues[row][column];      }  } 

Our table model class extends the AbstractTableModel class. This class provides the functionality that the JTable class is expecting to be present.

 public class TestTableModel1 extends AbstractTableModel 

The data is contained in a two-dimensional array called dataValues.

 public Object[][] dataValues = 

We must implement the following three methods so that the JTable will be able to extract the information that it needs from our class.

 public int getColumnCount()  {      return dataValues[0].length;  }  public int getRowCount()  {      return dataValues.length;  }  public Object getValueAt(int row, int column)  {      return dataValues[row][column];  } 

Before we can run a program that uses this model, we need to create JTable application. Listing 17.2 shows this application.

Listing 17.2 The TestJTable.java File
 /*   * TestJTable.java   *   * Created on September 3, 2002, 12:23 PM   */  package ch17;  import java.awt.*;  import javax.swing.*;  /**   *   * @author  Stephen Potts   */  public class TestJTable extends JFrame  {      JTable myTable;       /** Creates a new instance of TestJTable */      public TestJTable()      {          Container pane = getContentPane();          pane.setLayout(new BorderLayout());          TestTableModel1 tm1 = new TestTableModel1();          myTable = new JTable(tm1);          pane.add(myTable, BorderLayout.CENTER);       }      public static void main(String[] args)      {          TestJTable tjt = new TestJTable();          tjt.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);          tjt.setTitle("Using a JTable");          tjt.setSize(500, 150);          tjt.setVisible(true);      }  } 

First, we create an instance of JTable.

 JTable myTable; 

Next, we create an instance of the TestTableModel1 class that we defined previously.

 TestTableModel1 tm1 = new TestTableModel1(); 

We instantiate the JTable by passing the table model in as a parameter.

 myTable = new JTable(tm1); 

Next, we add the JTable to the pane.

 pane.add(myTable, BorderLayout.CENTER); 

The result of running this example is shown in Figure 17.2.

Figure 17.2. The JTable class extracts the data from the table model and displays it on the screen.

graphics/17fig02.gif

You will immediately notice that this process was automatic. After the table model was created properly, the JTable class did the rest of the work.

This process was nice and simple, but the result is not completely satisfying. Specifically, there are four problems with this JTable. The first problem is that there is no way to see the data that is too big for the window; we need a way to scroll. The second problem is in the date format. There is no need to display a time in a date of birth. We want to be able to format it better. The third problem is that this table, and all JTables, are read-only by default. There are some applications that need to accept user input in a JTable. The fourth problem is that this table lacks column headers.

Adding Headers, Scrolling, and Formatting

We can cure several deficiencies in our example at the same time. Adding column headings, formatting the data in the columns, and adding scrolling is fairly straightforward. Listing 17.3 shows an improved version of our example.

Listing 17.3 The TestTableModel2.java File
 /*   * TestTableModel2.java   *   * Created on September 3, 2002, 11:34 AM   */  package ch17;  /**   *   * @author  Stephen Potts   */  import java.util.Calendar;  import java.util.GregorianCalendar;  import javax.swing.table.AbstractTableModel;   public class TestTableModel2 extends AbstractTableModel  {      public final static int FIRST_NAME = 0;      public final static int LAST_NAME = 1;      public final static int DOB = 2;      public final static int CERTIFICATION = 3;      public final static int SALARY = 4;      public final static boolean PROGRAMMER = true;      public final static boolean INSTRUCTOR = false;      public final static String[] columnHeaders =      {          "First Name", "Last Name", "DOB", "Programmer", "Salary"      };      public Object[][] dataValues =      {          {              "Bush", "Bob",              new GregorianCalendar(1954, Calendar.JULY, 4).getTime(),              new Boolean(INSTRUCTOR), new Float(90000)           },          {              "Ezaguirre", "Art",              new GregorianCalendar(1959, Calendar.DECEMBER, 25).getTime(),               new Boolean(PROGRAMMER), new Float(80000)          },          {              "Jenkins", "Lewellen",              new GregorianCalendar(1961, Calendar.AUGUST, 1).getTime(),              new Boolean(PROGRAMMER), new Float(70000)          },          {              "Wells", "Patricia",              new GregorianCalendar(1965, Calendar.JUNE, 20).getTime(),              new Boolean(INSTRUCTOR), new Float(60000)          },          {              "Fleming", "Terry",              new GregorianCalendar(1953, Calendar.NOVEMBER, 4).getTime(),              new Boolean(INSTRUCTOR), new Float(66000)          },          {              "Tippets", "Rick",              new GregorianCalendar(1959, Calendar.SEPTEMBER, 25).getTime(),              new Boolean(PROGRAMMER), new Float(80000)           },          {              "Hall", "Andy",              new GregorianCalendar(1956, Calendar.DECEMBER, 1).getTime(),              new Boolean(PROGRAMMER), new Float(100000)          },          {              "Gummer", "Steve",              new GregorianCalendar(1967, Calendar.JUNE, 20).getTime(),              new Boolean(INSTRUCTOR), new Float(65000)          }      };      /** Creates a new instance of TestTableModel2 */      public TestTableModel2()      {      }      public int getColumnCount()      {          return dataValues[0].length;      }      public int getRowCount()      {          return dataValues.length;      }      public Object getValueAt(int row, int column)      {          return dataValues[row][column];       }      public String getColumnName(int col)      {          return columnHeaders[col];      }      public Class getColumnClass(int col)      {          Class colDataType = super.getColumnClass(col);          if (col == DOB)              colDataType = java.util.Date.class;          if (col == CERTIFICATION)              colDataType = java.lang.Boolean.class;          if (col == SALARY)              colDataType = Float.class;           return colDataType;      }  } 

Creating an array of strings provides the column headings.

 public final static String[] columnHeaders =  {      "First Name", "Last Name", "DOB", "Programmer", "Salary"  }; 

We also need to add a method to return the correct column name.

 public String getColumnName(int col)  {      return columnHeaders[col];  } 

Specifying what the data type is for each column handles the formatting of the data.

 public Class getColumnClass(int col)  { 

We want to use the existing data type for this column as a default. Doing this relieves us of the necessity of having to specify every column.

 Class colDataType = super.getColumnClass(col); 

The Class class is used to return the type of the column.

 if (col == DOB)      colDataType = java.util.Date.class; 

A check box is the default for the Boolean class.

 if (col == CERTIFICATION)      colDataType = java.lang.Boolean.class; 

The Float class will eliminate the trailing zero for whole numbers.

 if (col == SALARY)      colDataType = Float.class; 

The scrolling of the JTable has to be added in the program that declares the JTable object. Listing 17.4 shows the updated version of the JTable program.

Listing 17.4 The TestJTable2.java File
 /*   * TestJTable2.java   *   * Created on September 3, 2002, 12:23 PM   */  package ch17;  import java.awt.*;  import javax.swing.*;  /**   *   * @author  Stephen Potts   */  public class TestJTable2 extends JFrame  {      JTable myTable;      /** Creates a new instance of TestJTable2 */      public TestJTable2()      {          Container pane = getContentPane();          pane.setLayout(new BorderLayout());          TestTableModel2 tm1 = new TestTableModel2();          myTable = new JTable(tm1);          JScrollPane jsp = new JScrollPane(myTable);          pane.add(jsp, BorderLayout.CENTER);      }      public static void main(String[] args)      {          TestJTable2 tjt = new TestJTable2();          tjt.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);          tjt.setTitle("Using a JTable");          tjt.setSize(500, 150);          tjt.setVisible(true);       }  } 

Instead of adding the JTable directly to the content pane, we add it to a JScrollPane first, and add the JScrollPane to the content pane.

 JScrollPane jsp = new JScrollPane(myTable);  pane.add(jsp, BorderLayout.CENTER); 

The result of running this program is shown in Figure 17.3.

Figure 17.3. The addition of scrollbars, formatted output, and column headings improves the appearance of a JTable.

graphics/17fig03.gif

Notice how much better this JTable looks than the one in Figure 17.2. This look is much more polished.

Programming with JTree

One of the best (relatively) new GUI controls to be introduced in Swing is the tree control. The tree control has been popular in operating systems for years to display hierarchical data like the structure of the file system. The fact that a tree can be combined with a detail pane makes it very handy for searching the contents of your file system. The additional feature of allowing you to expand and collapse the nodes on the tree enables the user to keep a manageable amount of data on the screen. Listing 17.5 shows a simple tree.

Listing 17.5 The TestJTree1.java File
 /*   * TestJTree1.java   *   * Created on September 5, 2002, 11:12 AM   */  package ch17;  import javax.swing.*;  import javax.swing.tree.*;  import javax.swing.event.*;  import java.awt.*;  import java.awt.event.*;   import java.util.*;  /**   *   * @author  Stephen Potts   */  public class TestJTree1 extends JFrame implements TreeSelectionListener   {      private JTree tree1;      private JTextField jtf;      /** Creates a new instance of TestJTree1 */      public TestJTree1()      {          jtf = new JTextField(15);          jtf.setEditable(false);          Object[] league = {"nl", "al"};          Vector nlV = new Vector()          {              public String toString()              {                  return "National League";              }          };          nlV.addElement("Braves");          nlV.addElement("Mets");          nlV.addElement("Cardinals");          nlV.addElement("Rockies" );          Vector alV = new Vector()          {              public String toString()              {                  return "American League";              }          };          alV.addElement("Rangers");          alV.addElement("Twins");          alV.addElement("A's");          alV.addElement("White Sox" );          league[0] = nlV;          league[1] = alV;           tree1 = new JTree(league);          tree1.setRootVisible(true);          tree1.expandRow(0);          tree1.addTreeSelectionListener(this);          getContentPane().add(new JScrollPane(tree1),                                   BorderLayout.CENTER);          getContentPane().add(jtf, BorderLayout.SOUTH);           setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);          setBounds(100, 100, 300, 300);          setVisible(true);          setTitle("Using a JTree");      }      public void valueChanged(TreeSelectionEvent tse)      {          DefaultMutableTreeNode dmtn =          (DefaultMutableTreeNode) tree1.getLastSelectedPathComponent();          String name1 = (String) dmtn.getUserObject();          jtf.setText("you selected: " + name1);           jtf.setForeground(Color.black);      }      public static void main(String args[])      {          TestJTree1 tjt1 = new TestJTree1();      }  } 

This tree will display the value of the node picked in a JTextField object, so it must listen for an event.

 public class TestJTree1 extends JFrame implements TreeSelectionListener 

The JTree will be used to display the hierarchy.

 private JTree tree1; 

The text field will be used to display the node that is highlighted.

 private JTextField jtf; 

We first create an array called league.

 Object[] league = {"nl", "al"}; 

Next, we create a couple of vectors to hold the names of the teams. The nlV will hold National League teams and the alV will hold the American League teams. The toString() methods are needed because the default toString() method implementation will return the name of the Vector class. We want it to return a nice human-readable name.

 Vector nlV = new Vector()  {      public String toString()      {          return "National League";      }  };  nlV.addElement("Braves");  nlV.addElement("Mets");  nlV.addElement("Cardinals");  nlV.addElement("Rockies" );     Vector alV = new Vector()  {      public String toString()      {          return "American League";      }  };  alV.addElement("Rangers");  alV.addElement("Twins");  alV.addElement("A's");  alV.addElement("White Sox" ); 

We assign the vectors to positions in the league array.

 league[0] = nlV;  league[1] = alV; 

We pass the league array as a parameter to the JTree constructor.

 tree1 = new JTree(league); 

We want to see the implicitly created root, so we tell the tree object to display it.

 tree1.setRootVisible(true); 

We want to define a listener for this tree.

 tree1.addTreeSelectionListener(this); 

We add the tree and the text field to the content pane of the JFrame.

 getContentPane().add(new JScrollPane(tree1),                           BorderLayout.CENTER);  getContentPane().add(jtf, BorderLayout.SOUTH); 

The TreeSelectionListener expects us to implement the valueChanged() method.

 public void valueChanged(TreeSelectionEvent tse) 

To learn which node was selected, we need to define a node to hold the selection.

 DefaultMutableTreeNode dmtn =  (DefaultMutableTreeNode) tree1.getLastSelectedPathComponent(); 

The getUserObject() method returns the string that we see beside the node.

 String name1 = (String) dmtn.getUserObject(); 

Finally, we set the text to show the node that we selected.

 jtf.setText("you selected: " + name1); 

The result of running this example is shown in Figure 17.4.

Figure 17.4. A JTree can be constructed using arrays and vectors.

graphics/17fig04.gif

These results are nice, but not ideal. For one thing, the word "root" is not very user friendly. Secondly, the "American League" and "National League" nodes are not really nodes, and they generate an error if you click them. What we are experiencing is the limitations of creating a tree without a Tree model. The TreeModel class enables us to exercise more control over the tree. This class is responsible for managing the data for the JTree class in much the same way that the TableModel manages data for the JTable class. Listing 17.6 creates the same tree as the previous example did, but it uses a TableModel to hold the data.

Listing 17.6 The TestJTree2.java File
 /*   * TestJTree2.java   *   * Created on September 5, 2002, 11:12 AM   */  package ch17;  import javax.swing.*;  import javax.swing.tree.*;  import javax.swing.event.*;  import java.awt.*;  import java.awt.event.*;  import java.util.*;  /**   *   * @author  Stephen Potts   */  public class TestJTree2 extends JFrame implements TreeSelectionListener  {      private JTree tree1;      private JTextField jtf;      /** Creates a new instance of TestJTree2 */      public TestJTree2()      {          jtf = new JTextField(15);          jtf.setEditable(false);           DefaultMutableTreeNode root =                         new DefaultMutableTreeNode("MLB");          DefaultMutableTreeNode al =                       new DefaultMutableTreeNode("American League");          DefaultMutableTreeNode nl =                       new DefaultMutableTreeNode("National League");          DefaultMutableTreeNode braves =                                new DefaultMutableTreeNode("Braves");          DefaultMutableTreeNode mets =                                  new DefaultMutableTreeNode("Mets");          DefaultMutableTreeNode cardinals =                             new DefaultMutableTreeNode("Cardinals");          DefaultMutableTreeNode rockies =                               new DefaultMutableTreeNode("Rockies");          DefaultMutableTreeNode rangers =                               new DefaultMutableTreeNode("Rangers");          DefaultMutableTreeNode twins =                                 new DefaultMutableTreeNode("Twins");          DefaultMutableTreeNode as = new DefaultMutableTreeNode("A's");          DefaultMutableTreeNode whiteSox =                             new DefaultMutableTreeNode("White Sox");          DefaultTreeModel dtm = new DefaultTreeModel(root);          dtm.insertNodeInto(al, root, 0);          dtm.insertNodeInto(nl, root, 1);          dtm.insertNodeInto(braves, nl, 0);          dtm.insertNodeInto(mets, nl, 1);          dtm.insertNodeInto(cardinals, nl, 2);          dtm.insertNodeInto(rockies, nl, 3);          dtm.insertNodeInto(rangers, al, 0);          dtm.insertNodeInto(twins, al, 1);          dtm.insertNodeInto(as, al, 2);          dtm.insertNodeInto(whiteSox, al, 3);          tree1 = new JTree(dtm);          tree1.setRootVisible(true);           tree1.expandRow(1);          tree1.addTreeSelectionListener(this);          getContentPane().add(new JScrollPane(tree1),                                   BorderLayout.CENTER);          getContentPane().add(jtf, BorderLayout.SOUTH);          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);          setBounds(100, 100, 300, 300);          setVisible(true);          setTitle("Using a JTree");      }       public void valueChanged(TreeSelectionEvent tse)      {          DefaultMutableTreeNode dmtn =          (DefaultMutableTreeNode) tree1.getLastSelectedPathComponent();          String name1 = (String) dmtn.getUserObject();          jtf.setText("you selected: " + name1);          jtf.setForeground(Color.black);      }      public static void main(String args[])      {          TestJTree2 tjt2 = new TestJTree2();      }  } 

The DefaultMutableTreeNode class is a general-purpose node in a tree structure. It can only have one parent, but zero or more children.

 DefaultMutableTreeNode root = new DefaultMutableTreeNode("MLB");  DefaultMutableTreeNode al = new DefaultMutableTreeNode("American League");  DefaultMutableTreeNode nl = new DefaultMutableTreeNode("National League");  DefaultMutableTreeNode braves = new DefaultMutableTreeNode("Braves");  DefaultMutableTreeNode mets = new DefaultMutableTreeNode("Mets");  DefaultMutableTreeNode cardinals = new DefaultMutableTreeNode("Cardinals");  DefaultMutableTreeNode rockies = new DefaultMutableTreeNode("Rockies");  DefaultMutableTreeNode rangers = new DefaultMutableTreeNode("Rangers");  DefaultMutableTreeNode twins = new DefaultMutableTreeNode("Twins");  DefaultMutableTreeNode as = new DefaultMutableTreeNode("A's");  DefaultMutableTreeNode whiteSox = new DefaultMutableTreeNode("White Sox"); 

The DefaultTreeModel is the easiest tree model to use. You can instantiate it by passing a handle to the root node in the constructor.

 DefaultTreeModel dtm = new DefaultTreeModel(root); 

The insertNodeInto() method takes the handle to a node, the handle to its parent node, and its index in the root.

 dtm.insertNodeInto(al, root, 0);  dtm.insertNodeInto(nl, root, 1); 

Next, we add nodes under the National League node.

 dtm.insertNodeInto(braves, nl, 0);  dtm.insertNodeInto(mets, nl, 1);  dtm.insertNodeInto(cardinals, nl, 2);  dtm.insertNodeInto(rockies, nl, 3); 

Then, we add nodes under the American League node.

 dtm.insertNodeInto(rangers, al, 0);  dtm.insertNodeInto(twins, al, 1);  dtm.insertNodeInto(as, al, 2);  dtm.insertNodeInto(whiteSox, al, 3); 

We then create the JTree object with the TreeModel object passed into the constructor.

 tree1 = new JTree(dtm);  tree1.setRootVisible(true); 

We specify that we want to see row 1 expanded.

 tree1.expandRow(1); 

We add the selection listener in the customary fashion.

 tree1.addTreeSelectionListener(this); 

We add the tree and the text field to the content pane also.

 getContentPane().add(new JScrollPane(tree1),                           BorderLayout.CENTER);  getContentPane().add(jtf, BorderLayout.SOUTH); 

The valueChanged() method is identical to the one used in the preceding example.

 public void valueChanged(TreeSelectionEvent tse)  {      DefaultMutableTreeNode dmtn =      (DefaultMutableTreeNode) tree1.getLastSelectedPathComponent();      String name1 = (String) dmtn.getUserObject();      jtf.setText("you selected: " + name1);      jtf.setForeground(Color.black);  } 

The result of running this example is shown here in Figure 17.5.

Figure 17.5. A JTree can be constructed using TreeModel objects.

graphics/17fig05.gif

Notice the improvements in this example over the array and vector example. The root now has a name, and the branch nodes are now clickable.


       
    Top
     



    Java 2 Primer Plus
    Java 2 Primer Plus
    ISBN: 0672324156
    EAN: 2147483647
    Year: 2001
    Pages: 332

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net