Java JTable Fix

Oh, here's the deal - I inherited a Java program with a complex gui that had several annoying traits. One particularly annoying trait had to do with table cell editing.

Starting with a 2x2 matrix of cells, typical user behavior is

Does Java do what you want? Nooooooooo!!!!

It doesn't read the last typed entry.

After tons of Google'ing I finally found a fix. Makes sense after you see it, but it is hardly intuitive & does Sun's Java Tutorial say anything about this, does their online reference say anything about this? Nooooooooo!!!!

Finally found the fix comp.lang.java.gui. Usenet...best place in the world to find stuff you really™ want to know.

It would be ridiculous to post the original code here, but here is an example from Sun's Java Tutorial [I didn't make this a url since I'm sure as soon as I do, Sun in all their wisdom will completely redesign their web site & change it!]. It is called TablePrintDemo from their How to Make Tables tutorial page.

If you are really bored, copy the code below into files whose names are at the top of each one. Compile & execute them them thus:

   javac TablePrintDemo_bad.java
   java TablePrintDemo_bad
  
Into the Kathy Sport cell at location 2, 2 which shows "Knitting", do this: If you do the same test with TablePrintDemo.java, click Cancel on the print dialog - hey, print it if you wish - either way, the cell will show the typed characters & they will be output to the printer & to stdout. Apparently the print dialog causes the typed characters to be read. But merely commenting out the print dialog is enough to "break" the example.

PS:
In Firefox 2.0.0.4, Firefox 1.4.x.x, & SeaMonkey 1.1.2, The code shown below looks just like it does in my java code files.
However, at work I am forced to use Internet Explorer 6.0.2900.2180.xpsp_sp2_gdr.070227-2254 & each code line appears center-justified. Gack!!! Curiously, Konqueror 3.5.7 works like IE - maybe that's why I don't use KDE.

PPS:
Well, it appears I have found the secret to left-justification in IE. Duh!

The Bad
This code's operation is user-antagonistic.
I commented lines & added lines in blue
/*
 * TablePrintDemo_bad.java requires no other files.
 */

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JButton;
import javax.swing.table.AbstractTableModel;
import java.awt.Dimension;
import java.awt.Component;
import javax.swing.BoxLayout;
import java.text.MessageFormat;

public class TablePrintDemo_bad extends JPanel 
                            implements java.awt.event.ActionListener {
    private boolean DEBUG = false;
    private JTable table;

    public TablePrintDemo_bad() {
        super();
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        table = new JTable(new MyTableModel());
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        //Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);

        //Add the scroll pane to this panel.
        add(scrollPane);

        //Add a print button.
        JButton printButton = new JButton("Print");
        printButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        printButton.addActionListener(this);
        add(printButton);

    }

    public void actionPerformed(java.awt.event.ActionEvent ignore)
    {

        // MessageFormat header = new MessageFormat("Page {0,number,integer}");
        // try {
        //     table.print(JTable.PrintMode.FIT_WIDTH, header, null);
        // } catch (java.awt.print.PrinterException e) {
        //     System.err.format("Cannot print %s%n", e.getMessage());
        // }
        String crap = (String) table.getValueAt( 2, 2 ) ;
        System.out.println( "getValueAt( 2, 2 ) returns " +
                            crap ) ;

    }

    class MyTableModel extends AbstractTableModel {
        private String[] columnNames = {"First Name",
                                        "Last Name",
                                        "Sport",
                                        "# of Years",
                                        "Vegetarian"};
        private Object[][] data = {
            {"Mary", "Campione",
             "Snowboarding", new Integer(5), new Boolean(false)},
            {"Alison", "Huml",
             "Rowing", new Integer(3), new Boolean(true)},
            {"Kathy", "Walrath",
             "Knitting", new Integer(2), new Boolean(false)},
            {"Sharon", "Zakhour",
             "Speed reading", new Integer(20), new Boolean(true)},
            {"Philip", "Milne",
             "Pool", new Integer(10), new Boolean(false)},
            {"Isaac", "Rabinovitch", 
                "Nitpicking", new Integer(1000), new Boolean(false)}
        };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

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

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        /*
         * JTable uses this method to determine the default renderer/
         * editor for each cell.  If we didn't implement this method,
         * then the last column would contain text ("true"/"false"),
         * rather than a check box.
         */
        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        /*
         * Don't need to implement this method unless your table's
         * editable.
         */
        public boolean isCellEditable(int row, int col) {
            //Note that the data/cell address is constant,
            //no matter where the cell appears onscreen.
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        /*
         * Don't need to implement this method unless your table's
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            if (DEBUG) {
                System.out.println("Setting value at " + row + "," + col
                                   + " to " + value
                                   + " (an instance of "
                                   + value.getClass() + ")");
            }

            data[row][col] = value;
            fireTableCellUpdated(row, col);

            if (DEBUG) {
                System.out.println("New value of data:");
                printDebugData();
            }
        }

        private void printDebugData() {
            int numRows = getRowCount();
            int numCols = getColumnCount();

            for (int i=0; i < numRows; i++) {
                System.out.print("    row " + i + ":");
                for (int j=0; j < numCols; j++) {
                    System.out.print("  " + data[i][j]);
                }
                System.out.println();
            }
            System.out.println("--------------------------");
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("TablePrintDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        TablePrintDemo_bad newContentPane = new TablePrintDemo_bad();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
The Good
This works as I desire.
I commented lines & added lines in blue

/*
 * TablePrintDemo_fixed.java requires no other files.
 */

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JButton;
import javax.swing.table.AbstractTableModel;
import java.awt.Dimension;
import java.awt.Component;
import javax.swing.BoxLayout;
import java.text.MessageFormat;

import java.awt.event.*;
import javax.swing.CellEditor;

public class TablePrintDemo_fixed extends JPanel 
                            implements java.awt.event.ActionListener,
                                       java.awt.event.FocusListener {
    private boolean DEBUG = false;
    private JTable table;

    public TablePrintDemo_fixed() {
        super();
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        table = new JTable(new MyTableModel());
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        table.addFocusListener( this ) ;

        //Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);

        //Add the scroll pane to this panel.
        add(scrollPane);

        //Add a print button.
        JButton printButton = new JButton("Print");
        printButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        printButton.addActionListener(this);
        add(printButton);

    }

    public void focusGained( FocusEvent ignore ) {
    }

    public void focusLost( FocusEvent e ) {
        if (e.isTemporary())
            return ;
        JTable jt = (JTable) e.getSource() ;
        CellEditor ce = jt.getCellEditor() ;
        if (ce != null)
            ce.stopCellEditing() ;
    }

    public void actionPerformed(java.awt.event.ActionEvent ignore)
    {

        // MessageFormat header = new MessageFormat("Page {0,number,integer}");
        // try {
        //     table.print(JTable.PrintMode.FIT_WIDTH, header, null);
        // } catch (java.awt.print.PrinterException e) {
        //     System.err.format("Cannot print %s%n", e.getMessage());
        // }
        String crap = (String) table.getValueAt( 2, 2 ) ;
        System.out.println( "getValueAt( 2, 2 ) returns " +
                            crap ) ;

    }

    class MyTableModel extends AbstractTableModel {
        private String[] columnNames = {"First Name",
                                        "Last Name",
                                        "Sport",
                                        "# of Years",
                                        "Vegetarian"};
        private Object[][] data = {
            {"Mary", "Campione",
             "Snowboarding", new Integer(5), new Boolean(false)},
            {"Alison", "Huml",
             "Rowing", new Integer(3), new Boolean(true)},
            {"Kathy", "Walrath",
             "Knitting", new Integer(2), new Boolean(false)},
            {"Sharon", "Zakhour",
             "Speed reading", new Integer(20), new Boolean(true)},
            {"Philip", "Milne",
             "Pool", new Integer(10), new Boolean(false)},
            {"Isaac", "Rabinovitch", 
                "Nitpicking", new Integer(1000), new Boolean(false)}
        };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

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

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        /*
         * JTable uses this method to determine the default renderer/
         * editor for each cell.  If we didn't implement this method,
         * then the last column would contain text ("true"/"false"),
         * rather than a check box.
         */
        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        /*
         * Don't need to implement this method unless your table's
         * editable.
         */
        public boolean isCellEditable(int row, int col) {
            //Note that the data/cell address is constant,
            //no matter where the cell appears onscreen.
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        /*
         * Don't need to implement this method unless your table's
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            if (DEBUG) {
                System.out.println("Setting value at " + row + "," + col
                                   + " to " + value
                                   + " (an instance of "
                                   + value.getClass() + ")");
            }

            data[row][col] = value;
            fireTableCellUpdated(row, col);

            if (DEBUG) {
                System.out.println("New value of data:");
                printDebugData();
            }
        }

        private void printDebugData() {
            int numRows = getRowCount();
            int numCols = getColumnCount();

            for (int i=0; i < numRows; i++) {
                System.out.print("    row " + i + ":");
                for (int j=0; j < numCols; j++) {
                    System.out.print("  " + data[i][j]);
                }
                System.out.println();
            }
            System.out.println("--------------------------");
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("TablePrintDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        TablePrintDemo_fixed newContentPane = new TablePrintDemo_fixed();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
Essentially the original code
I removed "package components;"
& added lines in blue
/*
 * TablePrintDemo.java requires no other files.
 */

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JButton;
import javax.swing.table.AbstractTableModel;
import java.awt.Dimension;
import java.awt.Component;
import javax.swing.BoxLayout;
import java.text.MessageFormat;

public class TablePrintDemo extends JPanel 
                            implements java.awt.event.ActionListener {
    private boolean DEBUG = false;
    private JTable table;

    public TablePrintDemo() {
        super();
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        table = new JTable(new MyTableModel());
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        //Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);

        //Add the scroll pane to this panel.
        add(scrollPane);

        //Add a print button.
        JButton printButton = new JButton("Print");
        printButton.setAlignmentX(Component.CENTER_ALIGNMENT);
        printButton.addActionListener(this);
        add(printButton);

    }

    public void actionPerformed(java.awt.event.ActionEvent ignore)
    {
        MessageFormat header = new MessageFormat("Page {0,number,integer}");
        try {
            table.print(JTable.PrintMode.FIT_WIDTH, header, null);
        } catch (java.awt.print.PrinterException e) {
            System.err.format("Cannot print %s%n", e.getMessage());
        }

        String crap = (String) table.getValueAt( 2, 2 ) ;
        System.out.println( "getValueAt( 2, 2 ) returns " +
                            crap ) ;

    }

    class MyTableModel extends AbstractTableModel {
        private String[] columnNames = {"First Name",
                                        "Last Name",
                                        "Sport",
                                        "# of Years",
                                        "Vegetarian"};
        private Object[][] data = {
            {"Mary", "Campione",
             "Snowboarding", new Integer(5), new Boolean(false)},
            {"Alison", "Huml",
             "Rowing", new Integer(3), new Boolean(true)},
            {"Kathy", "Walrath",
             "Knitting", new Integer(2), new Boolean(false)},
            {"Sharon", "Zakhour",
             "Speed reading", new Integer(20), new Boolean(true)},
            {"Philip", "Milne",
             "Pool", new Integer(10), new Boolean(false)},
            {"Isaac", "Rabinovitch", 
                "Nitpicking", new Integer(1000), new Boolean(false)}
        };

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.length;
        }

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

        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        /*
         * JTable uses this method to determine the default renderer/
         * editor for each cell.  If we didn't implement this method,
         * then the last column would contain text ("true"/"false"),
         * rather than a check box.
         */
        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        /*
         * Don't need to implement this method unless your table's
         * editable.
         */
        public boolean isCellEditable(int row, int col) {
            //Note that the data/cell address is constant,
            //no matter where the cell appears onscreen.
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        /*
         * Don't need to implement this method unless your table's
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            if (DEBUG) {
                System.out.println("Setting value at " + row + "," + col
                                   + " to " + value
                                   + " (an instance of "
                                   + value.getClass() + ")");
            }

            data[row][col] = value;
            fireTableCellUpdated(row, col);

            if (DEBUG) {
                System.out.println("New value of data:");
                printDebugData();
            }
        }

        private void printDebugData() {
            int numRows = getRowCount();
            int numCols = getColumnCount();

            for (int i=0; i < numRows; i++) {
                System.out.print("    row " + i + ":");
                for (int j=0; j < numCols; j++) {
                    System.out.print("  " + data[i][j]);
                }
                System.out.println();
            }
            System.out.println("--------------------------");
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("TablePrintDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        TablePrintDemo newContentPane = new TablePrintDemo();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

Robert Geer <bgeer@xmission.com>
Last modified: Mon Sep 17 11:31:16 MDT 2007