JTable testing framework

Nicholas Duchon: Dec 17, 2017

Outline:



Rendering



Notes about making this example work:
  1. JTable already understands Integer, Double, String, Boolean, JCheckBox, and JComboBox data types
  2. JTable has all kinds of defaults, some of which this code must override to get the rendering of JButton and JProgressBar to look right.
  3. This example does not make the buttons on the table work - the default table listeners prevent the simple JButton listeners from getting events.
  4. Sorting for the Integer, Double, String and Boolean columns works automatically, by clicking on the column headers.

Basic steps to implementing and testing this code:

  1. JTable needs two kinds of data
    1. A String array of column labels, called names01 in the example below.
    2. An Object 2D array. The number of columns MUST match the number of elements in the String array of labels.
    3. JTable is instantiated with a custom table model: NDTableModel01.
      1. The data is transferred to the default table model using the 2-parameter constructor.
      2. The purpose of this class, as noted below, is to get the class of the JButton and JProgressBar cells correctly specified for the rest of the JTable operations - particularly rendering the objects correctly.
  2. This code puts the JTable instance on a JScrollPane, and that scroll pane is put into the center of BorderLayout.
    1. This makes resizing and scrolling of large tables work a lot better.
  3. Then the code connects a custom cell renderer to the two class types of interest in this example: JButton and JProgressBar
    1. NDContainerRenderer01 is used to tell the JTable renderers to use the paint methods of the objects in the cells.
    2. The effect of this declaration and the casting done in the getTableCellRendererComponent method of the custom renderer class, NDContainerRenderer01, is to use the paint methods of the java.awt.Container objects.
  4. The code then adds the control buttons to a panel at the top of the frame to let the user change the values of the JProgressBar's.
    1. This is just as an example of how the values of the progress bars can be updated.
    2. The progress bars are declared and references to them are outside the JTable itself. In other words, the progress bars objects are referenced by the JTable, and in that sense, are actually outside the JTable structure.
    3. This demonstrates that the values of the bars can be changed if the code simply uses its own references to the bars.
    4. One problem is getting the display to update. One way to do that is calling repaint on the table, which means that the code doing the update needs to have a reference to the table somewhere.
  5. NDTableModel01 is the table model used in this example.
    1. The main purpose of this code is to override the default getColumnClass method of the DefaultTableModel class.
    2. The constructor must be explicitly implemented since the DefaultTableModel class does not have a no-parameter constructor, which would be the constructor called by default, if there is no explicit reference to a super constructor.

Code for the Rendering Example

// File: NDTable01.java
// Date: Dec 17, 2017
// Author: Nicholas Duchon
// Purpose: display Container objects within a JTable example
//   > NDContainerRenderer01 implements TableCellRenderer
//   > this example shows JButton and JProgressBar example
//   > connects button events to updates of progress bars
//   > the String constructor sets up the frame and table
//   > The SAME renderer will work for BOTH JButton and JProgressBar, at least getting them to paint correctly

import javax.swing.JFrame;       // used by FrameND
import javax.swing.JButton;      // used in example data, controls
import javax.swing.JProgressBar; // used in example data
import javax.swing.JScrollPane;  // put table onto scroll pane
import javax.swing.JPanel;       // used in top control panel
import java.awt.BorderLayout;    // organize layout, table in center, button panel on top
import java.awt.Component;       // type used in TableCellRenderer

import javax.swing.JTable;       // the target table
import javax.swing.table.DefaultTableModel; // used in NDTabelModel01 to get right class references
import javax.swing.table.TableCellRenderer; // interface used by NDTableCellRenderer

public class NDTable01 extends FrameND {
   public static final long serialVersionUID = 123; // ND: junk
  
   JTable jt;
  
   // Sample data:
   // Control buttons:  
   JButton updateProgressBarA = new JButton ("+ barA");
   JButton updateProgressBarB = new JButton ("+ barB");
   JButton updateProgressBarC = new JButton ("+ barC");
   JButton updateProgressBarD = new JButton ("+ barD");
  
   // buttons on table
   JButton jbA = new JButton ("button A");
   JButton jbB = new JButton ("button B");
   JButton jbC = new JButton ("button C");
   JButton jbD = new JButton ("button D");
  
   // progress bars on table
   JProgressBar jpbA = new JProgressBar (0, 50);
   JProgressBar jpbB = new JProgressBar (0, 50);
   JProgressBar jpbC = new JProgressBar (0, 50);
   JProgressBar jpbD = new JProgressBar (0, 50);
  
   // The data01 tables
   // the number of elements in names MUST match the number of columns in data
   // this data shows the automatic formatting and control of
   //    Integer, Double, String and Boolean data types
   // Then the manual control formatting JButton and JProgressBar
   String []  names01 = {"int", "double", "String", "Boolean", "JButton", "JProgressBar"};
   Object [][] data01 = {
       { 334, 12.43, "one"  , true, jbA, jpbA}
      ,{ 134, 15.43, "two"  , true, jbB, jpbB}
      ,{ 534, 17.43, "three", true, jbC, jpbC}
      ,{ 834, 11.43, "four" , true, jbD, jpbD}
   }; // end data01
  
   public NDTable01 (String st) {
      super (st); // use the String constructor of FrameND
     
      jt = new JTable (new NDTableModel01 (data01, names01));
      // MUST use JScrollPane, otherwise headers won't show
      JScrollPane jsp = new JScrollPane(jt);
      // CENTER is a good place to put resizable objects
      add(jsp, BorderLayout.CENTER);
     
      // custom renderer NDContainerRenderer01 connect to two class: JButton and JProgressBar
      // on the next two lines, without this, the default toString method
      // is used in the rendering, which is hardly very interesting.
      jt.setDefaultRenderer (JProgressBar.class, new NDContainerRenderer01 ());
      jt.setDefaultRenderer (JButton     .class, new NDContainerRenderer01 ());
     
      // JPanel for buttons that will update the progress bars
      JPanel controls = new JPanel ();
      controls.add (updateProgressBarA);
      controls.add (updateProgressBarB);
      controls.add (updateProgressBarC);
      controls.add (updateProgressBarD);
      add (controls, BorderLayout.NORTH);
     
      // Connecting the control buttons to the progress bar states
      // note that the table stuff does not show up here, except for:
      // repaint is one way to force an update the the display
      //   there are other ways, but most involve a direct reference to the table, jt
      updateProgressBarA.addActionListener (e -> {jpbA.setValue(jpbA.getValue() + 5); jt.repaint();});
      updateProgressBarB.addActionListener (e -> {jpbB.setValue(jpbB.getValue() + 5); jt.repaint();});
      updateProgressBarC.addActionListener (e -> {jpbC.setValue(jpbC.getValue() + 5); jt.repaint();});
      updateProgressBarD.addActionListener (e -> {jpbD.setValue(jpbD.getValue() + 5); jt.repaint();});
  
      validate (); // get stuff to show up at start
   } // end String constructor
  
   public static void main (String [] args) {
      NDTable01 nd = new NDTable01 ("table example one");
   } // end main
} // end class NDTable01

// This is a very general model, can be used pretty much as is in many applications
class NDTableModel01 extends DefaultTableModel {
   public static final long serialVersionUID = 345; // ND: junk
  
// must be implemented, otherwise call made to no-parameter super constructor
   public NDTableModel01 (Object[][] oa, String [] sa) {
      super (oa, sa);
   } // end String[], Object[][] constructor
 
// JTable uses this method to determine the default renderer
// editor for each cell.  If we didn't implement this method,
// then the columns would implement toString methods on classes
// other than Integer, Double, String, JCheckBox, and JComboBox

//          @SuppressWarnings("rawtypes") - better to use <?>
   public Class <?> getColumnClass(int c) {
      return getValueAt(0, c).getClass();
   } // end method getColumnClass
 
// Don't need to implement this method unless your table is editable.
// Note that the data/cell address is constant,
// no matter where the cell appears onscreen.
   public boolean isCellEditable(int row, int col) {
      if (col < 2) {
         return false;
      }
      else {
         return true;
      }
   } // end method isCellEditable
  
} // end class  NDTableModel01

// This is a rather trivial implementation of the
// getTableCellRendererComponent, basically just returns
// the component in the cell, assuming the component really is
// a subclass of java.awt.Component
class NDContainerRenderer01 implements TableCellRenderer {
   public static final long serialVersionUID = 1204; // ND: junk
  
   public Component getTableCellRendererComponent (
        JTable t,
        Object value, // reference to object at this cell location
        boolean isSelected,
        boolean hasFocus,
        int row,
        int col) {

      return (Component) value;
   } //end interface method getTableCellRendererComponent

} // end class MyTJPBar04

// The following class makes it easy to create multiple windows
// by an application, and end the application when the last one closes
// 3 constructors: (), (String), (String, int, int)
class FrameND extends JFrame {
   public static final long serialVersionUID = 892374; // ND: junk
   static final int WIDTH = 800, HEIGHT = 200;
   static final int DX = 20, DY = 20;
  
   static int windowCount = 0;
   static FrameND prev = null;
  
   public FrameND () {
      windowCount ++;
      setSize (WIDTH, HEIGHT);
      if (windowCount == 1) {
         setLocationRelativeTo (null);
      }
      else {
         java.awt.Point p = prev.getLocation();
         setLocation (p.x + DX, p.y + DY);
      } // end creating cascading frames
      prev = this;
      validate ();
      setTitle ("Frame Number:  " + windowCount);
      setVisible (true);
  
      addWindowListener (
         new java.awt.event.WindowAdapter () {
            public void windowClosing (java.awt.event.WindowEvent e) {
               windowCount --;
               if (windowCount == 0) System.exit (0);
            } // end closing event handler method
         } // end new inner adapter
         ); // end adding anonymous inner class listener
   } // end no-parameter constructor
  
   public FrameND (String s) {
      this ();
      setTitle (s);
   } // String constructor
  
   public FrameND (String s, int w, int h) {
      this (s);
      setSize (w, h);
   } // end String-int-int constructor
  
} // end class FrameND



Full Example:


Comments:

  1. This is an extension of the above example, adding active JButton's
  2. The JButton's connect to the JProgressBar's
  3. This code also implements sorting for the JProgressBar's useing a custom TableRowSorter
  4. Yellow highlighted code is the stuff that responds to the button presses.
  5. Cyan highlighted code is related to sorting.

Code for the Full Example:

// File: NDTable03.java
// Date: Dec 17, 2017
//       Jan  2, 2018
// Author: Nicholas Duchon
// Purpose: display Container objects within a JTable example
//   > NDContainerRenderer01 implements TableCellRenderer
//   > this example shows JButton and JProgressBar example
//   > connects button events to updates of progress bars
//   > the String constructor sets up the frame and table
//   > The SAME renderer will work for BOTH JButton and JProgressBar, at least getting them to paint correctly
// --- NDTable03 is extends NDTable02
//   remove button from top panel
//   not needed now since buttons in table are active

import javax.swing.JFrame;       // used by FrameND
import javax.swing.JButton;      // used in example data, controls
import javax.swing.JProgressBar; // used in example data
import javax.swing.JScrollPane;  // put table onto scroll pane
import javax.swing.JPanel;       // used in top control panel
import java.awt.BorderLayout;    // organize layout, table in center, button panel on top
import java.awt.Component;       // type used in TableCellRenderer

import javax.swing.JTable;       // the target table
import javax.swing.table.DefaultTableModel; // used in NDTabelModel03 to get right class references
import javax.swing.table.TableCellRenderer; // interface used by NDTableCellRenderer

import javax.swing.table.TableRowSorter;    // try sorting on JProgressBar value

public class NDTable03 extends FrameND {
   public static final long serialVersionUID = 123; // ND: junk
  
   JTable jt;
  
   // Sample data:

   // buttons on table
   JButton jbA = new JButton ("button A");
   JButton jbB = new JButton ("button B");
   JButton jbC = new JButton ("button C");
   JButton jbD = new JButton ("button D");
  
   // progress bars on table
   JProgressBar jpbA = new JProgressBar (0, 50);
   JProgressBar jpbB = new JProgressBar (0, 50);
   JProgressBar jpbC = new JProgressBar (0, 50);
   JProgressBar jpbD = new JProgressBar (0, 50);
  
   // The data03 tables
   // the number of elements in names MUST match the number of columns in data
   // this data shows the automatic formatting and control of
   //    Integer, Double, String and Boolean data types
   // Then the manual control formatting JButton and JProgressBar
   String []  names03 = {"int", "double", "String", "Boolean", "JButton", "JProgressBar"};
   Object [][] data03 = {
       { 334, 12.43, "one"  , true, jbA, jpbA}
      ,{ 134, 15.43, "two"  , true, jbB, jpbB}
      ,{ 534, 17.43, "three", true, jbC, jpbC}
      ,{ 834, 11.43, "four" , true, jbD, jpbD}
   }; // end data03
  
   public NDTable03 (String st) {
      super (st); // use the String constructor of FrameND
     
      // put data and column names into a table model
      NDTableModel03 tm = new NDTableModel03 (data03, names03);
     
      // set up the custom sorter for the JProgressBar based on default sorter
      TableRowSorter <NDTableModel03> trs = new TableRowSorter <> (tm);
      trs.setComparator (5, (a,b) -> ((JProgressBar)a).getValue() - ((JProgressBar)b).getValue());
  
      // create the table, enable sorting, add the custom sorter
      jt = new JTable (tm);
      jt.setAutoCreateRowSorter (true); // make table columns sortable
      jt.setRowSorter (trs);            // use the custom row sorter defined above
     
      // MUST use JScrollPane, otherwise headers won't show
      JScrollPane jsp = new JScrollPane(jt);
      // CENTER is a good place to put resizable objects
      add(jsp, BorderLayout.CENTER);
     
      // custom renderer NDContainerRenderer03 connect to two class: JButton and JProgressBar
      // on the next two lines, without this, the default toString method
      // is used in the rendering, which is hardly very interesting.
      jt.setDefaultRenderer (JProgressBar.class, new NDContainerRenderer03 ());
      jt.setDefaultRenderer (JButton     .class, new NDContainerRenderer03 ());
     
      validate (); // get stuff to show up at start
   } // end String constructor
  
   public static void main (String [] args) {
      NDTable03 nd = new NDTable03 ("table example one");
  
} // end main
} // end class NDTable03

// This is a very general model, can be used pretty much as is in many applications
class NDTableModel03 extends DefaultTableModel {
   public static final long serialVersionUID = 345; // ND: junk
  
// must be implemented, otherwise call made to missing no-parameter super constructor
   public NDTableModel03 (Object[][] oa, String [] sa) {
      super (oa, sa);
  
} // end String[], Object[][] constructor
 
// JTable uses this method to determine the default renderer/
// editor for each cell.  If we didn't implement this method,
// then the columns would implement toString methods on classes
// other than Integer, Double, String, JCheckBox, and JComboBox

// @SuppressWarnings("rawtypes") - better to use <?>
   public Class <?> getColumnClass(int c) {
      return getValueAt(0, c).getClass();
  
} // end method getColumnClass
 
// Don't need to implement this method unless your table is editable.
// Note that the data/cell address is constant even if rows are sorted
//    Event handler: this method is called to handle events in the table
// so no need to go around implementing listeners for cells
   public boolean isCellEditable(int row, int col) {
      if (getValueAt (row, col) instanceof JButton) {
         JProgressBar jpb = (JProgressBar) getValueAt (row, col+1);
         jpb.setValue(jpb.getValue() + 5);
         fireTableCellUpdated (row, col+1); // get the JProgressBar display updated
         return false;
      } // end for 4th column - where the JButtons are
      return true;
  
} // end method isCellEditable
  
} // end class  NDTableModel03

// This is a rather trivial implementation of the
// getTableCellRendererComponent, basically just returns
// the component in the cell, assuming the component really is
// a subclass of java.awt.Component
class NDContainerRenderer03 implements TableCellRenderer {
   public static final long serialVersionUID = 1204; // ND: junk
  
   public Component getTableCellRendererComponent (
        JTable t,
        Object value, // reference to object at this cell location
        boolean isSelected,
        boolean hasFocus,
        int row,
        int col) {
      return (Component) value;
  
} //end interface method getTableCellRendererComponent

} // end class MyTJPBar04

// The following class makes it easy to create multiple windows
// by an application, and end the application when the last one closes
// 3 constructors: (), (String), (String, int, int)
class FrameND extends JFrame {
   public static final long serialVersionUID = 892374; // ND: junk
   static final int WIDTH = 800, HEIGHT = 200;
   static final int DX = 20, DY = 20;
  
   static int windowCount = 0;
   static FrameND prev = null;
  
   public FrameND () {
      windowCount ++;
      setSize (WIDTH, HEIGHT);
      if (windowCount == 1) {
         setLocationRelativeTo (null);
      }
      else {
         java.awt.Point p = prev.getLocation();
         setLocation (p.x + DX, p.y + DY);
      } // end creating cascading frames
      prev = this;
      validate ();
      setTitle ("Frame Number:  " + windowCount);
      setVisible (true);
  
      addWindowListener (
         new java.awt.event.WindowAdapter () {
            public void windowClosing (java.awt.event.WindowEvent e) {
               windowCount --;
               if (windowCount == 0) System.exit (0);
            } // end closing event handler method
         } // end new inner adapter
         ); // end adding anonymous inner class listener
  
} // end no-parameter constructor
  
   public FrameND (String s) {
      this ();
      setTitle (s);
  
} // String constructor
  
   public FrameND (String s, int w, int h) {
      this (s);
      setSize (w, h);
  
} // end String-int-int constructor
  
} // end class FrameND


Example 01

Here's some simple code to support testing various features of JTable:


Code for the display above:

// File: MyTable.java
// Date: Nov 3, 2011
// Author: Nicholas Duchon
// Purpose: Demo JTable class.
//
// Structure:
//    MyTable
//       MyTable ()               : constructor
//       update (String, int, int): change value in a location
//       randomData (int, Class[]): creates array with random data, by class type
//       main (String [])
//       menu (Scanner, MyTable)  : change values in table
//
//    MyTableModel extends DefaultTableModel
//       MyTableModel (Object[][], String [], Class []) : constructor
//       getColumnClass (int)                           : overrides one that always returns String
//       typesToClasses (String [])                     : class array of String names

   import javax.swing.JTable;
   import javax.swing.table.DefaultTableModel;
   import javax.swing.JFrame;
   import javax.swing.JScrollPane;
   import java.awt.BorderLayout;
   import java.util.Scanner;
   import java.util.Random;

   public class MyTable {
      static Random rn = new Random ();
      static int MAX = 1000;
      static int COUNT = 500;
      JTable jt = null;
  
      public MyTable () {
      // Standard JFrame stuff
         JFrame jf = new JFrame ("Testing JTable");
         jf.setSize (500, 500);
         jf.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
         jf.setVisible (true);
     
      // The header columns titles and types
         String [] names   = {"int as string", "double", "int"    , "int"    , "String", "Str"   , "More"  };
         String [] types   = {"String"       , "Double", "Integer", "Integer", "String", "String", "String"};
         Class  [] classes = MyTableModel.typesToClasses (types);
      // The table data
         Object [][] data = randomData (COUNT, classes);
        
      // creating the table, with data and headers
         MyTableModel mtm = new MyTableModel (data, names, classes);
         jt = new JTable (mtm);
      //          for (int i = 0; i < data[0].length; i++)
      //             System.out.printf ("Column %d: %s\n", i, jt.getColumnClass (i));
     
      // MUST use JScrollPane, otherwise headers won't show
         JScrollPane jsp = new JScrollPane(jt);
      // CENTER is a good place to put resizable objects
         jf.add(jsp, BorderLayout.CENTER);
     
         jt.setCellSelectionEnabled (true);
         jt.setAutoCreateRowSorter (true);
      //          jf.validate(); // this seems to work also, sometimes neither this or next is needed, timing?
      //          jf.pack (); // the for loop above stops the display from updating!
      } // end no-parameter constructor
  
      public void update (String s, int r, int c) {
         jt.setValueAt (s, r, c);
      } // end update
  
      public Object [][] randomData (int rows, Class [] names) {
         int cols = names.length;
         Object [][] d = new Object [rows][cols];
         char [] c = new char [3];
         for (int i = 0; i < rows; i++) {
            d[i][0] = "" + rn.nextInt (MAX);
            for (int j = 1; j < cols; j++) {
               if (names[j] == Integer.class) d[i][j] = rn.nextInt (MAX);
               else if (names[j] == Double.class) d[i][j] = rn.nextDouble ();
               else if (names[j] == String.class) {
                  for (int k = 0; k < c.length; k++)
                     c[k] = (char)('a' + rn.nextInt (26));
                  d[i][j] = new String (c);
               }
            } // end for each col
         } // end for each row
         return d;
      } // end randomData
     
      public static void main (String args []) {
         MyTable t = new MyTable ();
         Scanner scan = new Scanner (System.in);
         while (menu(scan, t));
         System.out.println ("Bye");
         System.exit(0);
      } // end main
  
      public static boolean menu (Scanner sc, MyTable t) {
         System.out.print ("Enter string, row, col [q to quit]: ");
         String st = sc.next();
         if (st.equalsIgnoreCase ("q"))
            return false;
         t.update (st, sc.nextInt(), sc.nextInt());
         return true;
      } // end method menu
   } // end class MyTable

   class MyTableModel extends DefaultTableModel {
      Class [] classes;
     
      // so columns headers are not same as class references or class names
      public MyTableModel (Object d[][], String [] n, Class [] c) {
         super (d, n);
         classes = c;
      } // end constructor
     
      public Class getColumnClass (int i) {
         return classes[i];
      } // end overridden method getColumnClass, used by JTable for sorting
  
      public static Class [] typesToClasses (String [] types) {
         Class [] classes = new Class [types.length];
         try {
            for (int i = 0; i < types.length; i++)
               classes [i] = Class.forName ("java.lang." + types[i]);
         }
            catch (ClassNotFoundException e) {
               System.out.println ("Bummer: " + e);
            } // end try/catch
         return classes;
      } // end method namesTo Classes
  
   } // end class MyTableModel

Code - earlier example:

import javax.swing.JTable;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import java.awt.BorderLayout;
import java.util.Scanner;

public class MyTable {
 JTable jt = null;
 
 public MyTable () {
   // Standard JFrame stuff
   JFrame jf = new JFrame ("Testing JTable");
   jf.setSize (500, 500);
   jf.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
   jf.setVisible (true);
   
   // The table data
   Object [][] data = {
        { 110,  912,  13,  14, "one", "two", "three"},
        { 210,  812,  13,  14, "one", "two", "three"},
        { 310,  712,  13,  14, "one", "two", "three"},
        { 410,  612,  13,  14, "one", "two", "three"},
        { 510,  512,  13,  14, "one", "two", "three"},
        { 610,  412,  13,  14, "one", "two", "three"},
        { 710,  312,  13,  14, "one", "two", "three"},
        { 810,  212,  13,  14, "one", "two", "three"},
        { 910,  112,  13,  14, "one", "two", "three"},
        { 110,  112,  13,  14, "one", "two", "three"},
        { 210,  212,  13,  14, "one", "two", "three"},
        { 310,  312,  13,  14, "one", "two", "three"},
        { 410,  412,  13,  14, "one", "two", "three"},
        { 510,  512,  13,  14, "one", "two", "three"},
        { 610,  612,  13,  14, "one", "two", "three"},
        { 710,  712,  13,  14, "one", "two", "three"},
        { 810,  812,  13,  14, "one", "two", "three"},
        { 910,  912,  13,  14, "one", "two", "three"},
        { 110,  112,  13,  14, "one", "two", "three"},
        { 210,  212,  13,  14, "one", "two", "three"},
        { 310,  312,  13,  14, "one", "two", "three"},
        { 420,  422,  23,  24, "one", "two", "three"},
        { 530,  532,  33,  34, "one", "two", "three"},
        { 640,  642,  43,  44, "one", "two", "three"}
        };
        
   // The header columns titles
   String [] names =   {"a", "b", "c", "d", "e"  , "f"  , "g"};
   
   // creating the table, with data and headers
   jt = new JTable (data, names);
   // MUST use JScrollPane, otherwise headers won't show
   JScrollPane jsp = new JScrollPane(jt);
   // CENTER is a good place to put resizable objects
   jf.add(jsp, BorderLayout.CENTER);
 } // end no-parameter constructor
 
 public void update (String s, int r, int c) {
   jt.setValueAt (s, r, c);
 } // end update
 
 public static void main (String args []) {
   MyTable t = new MyTable ();
   Scanner scan = new Scanner (System.in);
   while (menu(scan, t));
   System.out.println ("Bye");
   System.exit(0);
 } // end main
 
 public static boolean menu (Scanner sc, MyTable t) {
   System.out.print ("Enter string, row, col [q to quit]: ");
   String st = sc.next();
   if (st.equalsIgnoreCase ("q")) return false;
   t.update (st, sc.nextInt(), sc.nextInt());
   return true;
 } // end method menu
} // end class MyTable


ND.