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. The JButton's above the table DO accept events for their listeners, as usual.
  4. JProgressBar IS updated in this example, using explicit jt.repaint() calls.
  5. This example does not make the buttons on the table work - the default table listeners prevent the simple JButton listeners in the table from getting events.
  6. 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 JProgressBar display to update.
      1. 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 (jt in this code) somewhere.
  • NDTableModel01 is the table model used in this example.
      1. 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 with Threads

    Comments:

    1. Creates COUNT threads used to update JProgressBars, currently set to 1000.
      1. My computer could only make about 6000 threads before I got errors, you mileage may vary.
    2. New class, MyThread04 extends Thread, just to be simple here.
      1. Class references parent JTable for repainting during run
      2. Also references JProgressBar created in constructor for loop
      3. run method has random delays, along with delays based on thread index
    3. for loop in constructor creates elements of each row (int, double, String, boolean, and JProgressBar).
    4. Sorting threads based on progress bar values already implemented in earlier versions of this code.

    More efficient:

    1. Instead of updating the table using paint, just fire an update for the one cell.
    2. Use the following parameters to the MyThread04 instance:
      1. int or String to identify the particular JProgressBar row
      2. JProgressBar reference
      3. the cell coordinates (row, column) as ints
      4. a reference to the table model, NDTableModel04 in this example
    3. Call the table model method fireTableCellUpdated (int row, int col) method
    4. Of course the call to the constructor needs to be updated as well.
    5. Modified code:
      class MyThread04 extends Thread {

         JProgressBar jpb;
         int id, ix, iy;
         NDTableModel04 tm;
         static java.util.Random rn = new java.util.Random();
        
         public MyThread04 (int ji, JProgressBar jpbp, int ixp, int iyp, NDTableModel04 tmp) {
            super ("t " + ji);
            id = ji;
            jpb = jpbp;
            ix = ixp; iy = iyp;
            tm = tmp;
            start();
        
      } // end constructor
        
         public void run () {
            try {
               for (int v = 0; v < 100; v++) {
                  Thread.sleep (id%10*100 + rn.nextInt (100));
                  jpb.setValue (v);
                  tm.fireTableCellUpdated (ix, iy);
               }
            } catch (Exception e) {
               System.out.println ("Exception in run: " + e); dumpStack();
            }
         } // end run method
        
      } // end new thread definition

    Better delay approacch:

       // in instance variable declarations:
       int MIN = 1000;     // min duration in ms
       int MAX = 100 * 1000;   // max duration in ms
       int DT  = ( MIN + rn.nextInt (MAX - MIN) + 50) / 100;  // 100 steps

       // in constructor:
          jpb.setStringPainted (true);
          jpb.setString ("" + DT);

       // in run method:
                Thread.sleep (DT);

    Code:

    // File: NDTable04.java
    // Date: Dec 17, 2017
    //       Aug 11, 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
    // --- NDTable04 is based on NDTable03
    //   remove button from top panel
    //   not needed now since buttons in table are active
    //   test LOTS of rows

    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 NDTabelModel04 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 NDTable04 extends FrameND {
       public static final long serialVersionUID = 123; // ND: junk
      
       JTable jt;
      
       // Sample data:

       // The data04 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
       // in this version, data04 is initialized in the for loop below
       String []  names04 = {"int", "double", "String", "Boolean", "JButton", "JProgressBar"};
       Object [][] data04;
      
       public NDTable04 (String st) {
          super (st, 800, 1000); // use the String int int constructor of FrameND
         
       //       try {Thread.sleep (10000);}
       //       catch (Exception e) {};
      
          int COUNT = 1000;
          data04 = new Object [COUNT][6];
          JProgressBar jpb;
          Thread t;
          for (int i = 0; i < COUNT; i++) {
             data04[i][0] = 5*i;
             data04[i][1] = 3.0*i;
             data04[i][2] = "name " + i;
             data04[i][3] = true;
             data04[i][4] = new JButton ("b " + i);
             jpb = new JProgressBar ();
             data04[i][5] = jpb;
          } // end for
         
          // put data and column names into a table model
          NDTableModel04 tm = new NDTableModel04 (data04, names04);
         
          // set up the custom sorter for the JProgressBar based on default sorter
          TableRowSorter <NDTableModel04> 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 NDContainerRenderer04 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 NDContainerRenderer04 ());
          jt.setDefaultRenderer (JButton     .class, new NDContainerRenderer04 ());
         
          validate (); // get stuff to show up at start
          setLocationRelativeTo (null);
         
          // create and start threads after JTable is made, since JTable update required to update progress bars
          // at least that's a simple way to do it.
          for (int i = 0; i < COUNT; i++) {
             jpb = (JProgressBar) data04[i][5];
             t = new MyThread04 (i, jpb, jt) ;
          } // end for
      
      
    } // end String constructor
      
       public static void main (String [] args) {
          NDTable04 nd = new NDTable04 ("table example 04");
      
    } // end main
    } // end class NDTable04

    class MyThread04 extends Thread {

       JProgressBar jpb;
       JTable jt;
       int id;
       static java.util.Random rn = new java.util.Random();
      
       public MyThread04 (int ji, JProgressBar jpbp, JTable jtp) {
          super ("t " + ji);
          id = ji;
          jpb = jpbp;
          jt = jtp;
          start();
      
    } // end construcor
      
       public void run () {
          try {
             for (int v = 0; v < 100; v++) {
                Thread.sleep (id%10*100 + rn.nextInt (100));
                jpb.setValue (v);
                jt.repaint ();
             }
          } catch (Exception e) {
             System.out.println ("Exception in run: " + e); dumpStack();
          }
      
    } // end run method
      
    } // end new thread definition


    // This is a very general model, can be used pretty much as is in many applications
    class NDTableModel04 extends DefaultTableModel {
       public static final long serialVersionUID = 345; // ND: junk
      
    // must be implemented, otherwise call made to missing no-parameter super constructor
       public NDTableModel04 (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  NDTableModel04

    // 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 NDContainerRenderer04 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);
      
       // 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);


       jf.validate ();
       jf.setVisible (true);
      } // 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.